Relationships
#3983 Extension dependencies: the shared import scan reads Babel syntax, not tokens (follow-up to #3969)
Opened by skunk-ape · 10/26/2026
Repo: swamp (packages/extension-scoring and src/). Follow-up to #3969, step 1 of the extension dependency-forms design (Doc: https://claude.ai/artifact/WpRbpqb5djPgDXzydFSohs, section 3.4). Split out of #3969 on 2026-10-26 so that step stays a pure refactor.
Ordering (decided 2026-10-26)
#3969 (pure refactor onto the shared scanner) → this issue (published as a new @swamp-club/extension-scoring version) → #3977 pins that version → #3980 rescores under rubric v5. This must land before #3977 pins the package: the scanner is part of what rubric v5 scores, so landing after the rescore would need rubric v6 and a second bulk rescore, or leave stored v5 rows that a re-push scores differently.
Problem
#3969 makes the shared scoring package's import scanner (@swamp-club/extension-scoring/source: stripSourceComments, importedSpecifiersAt, importedSpecifiers) the one scan push, quality, fmt, the loader and the registry read imports with. That scanner reads tokens, not syntax, and has three known blind spots, each pinned by a named #3969 dependency input vector:
- A quote inside a regex literal before an import on the same line (
const re = /"/; import ms from "ms";) hides the import. Push and the registry both miss a writtennpm:/jsr:import there, so it goes unaudited. A bare import there ships as written, becauseplanBareImportsconfirms each site with a second, Babel pass (vectorscan-regex-quote). - A module that does not parse is read as text instead of refused. #3846 already reports every quality-ignore directive in such a file invalid, so today one quality run reads the same file both ways (vector
scan-unparseable). - The package.json trigger and the bundler's zod scan in
bundle.tsstill read imports with their ownfrom-only and import-position regexes, so a side-effectimport "x"or dynamicimport("x")of a bare name beside a package.json does not select package.json mode (vectorsscan-side-effect-bare-package-json,scan-dynamic-bare-package-json). #3969 kept those regexes, moved intosrc/domain/extensions/dependency_resolution/, to stay behaviour-preserving.
Babel parses of extension source also live in three places, each with its own options: src/domain/extensions/extension_source_ast.ts, the bundle-output zod parse in src/domain/models/bundle.ts, and the quality-ignore directive reader in src/domain/extensions/extension_acceptances.ts (#3846).
Registry impact (measured 2026-10-26)
A scan of the latest version of every published extension (2,061 listed, 2,060 scanned, one yanked; 17,210 code files, tests excluded) found 0 versions whose dependency-audit or secret-docs input would change under a Babel scan: no npm:, jsr: or Markdown import differs. 58 files in 19 extensions differ, all esbuild bundles under extension/bundles/: in each, a regex literal in bundled npm code (one example: a content-type parser regex holding a backtick and quotes) sends stripSourceComments out of step, and it blanks real top-level node: imports that follow (node:vm, node:https, node:net and others), which only Babel finds. An npm: or jsr: import after such a regex would be lost the same way. Two bundles (@tagur/pdf, @dieter/lab-pdf-extractor) do not parse ("'return' outside of function"); the token scanner finds nothing in them either. So the Babel move changes no stored score, and the token scanner has a fourth blind spot: false negatives after a regex literal in large bundled files.
Proposal
packages/extension-scoring/sourcereads import sites from a Babel parse (static, side-effect, dynamic, re-export,typeof import()), keeping its exported functions and spans, so callers do not change. A module that does not parse yields no sites, and push and quality refuse it by name.- Remove the token scanner and
stripSourceComments, the second Babel confirmation pass inplanBareImports, and the remaining regex readers (package.json trigger, zod scan) in favour of the one scan. - One parse configuration for extension source, shared by the scan, the safety rules and the acceptances reader.
- Add cases to the shared
vectors/dependency_audit.json(and secret-docs if affected) for the three blind spots; re-pin the four #3969 input vectors named above.
Decisions needed
@babel/parserbecomes the package's first npm dependency. Its README says it reads specifiers "without parsing" and depends only on@std/semver; it is published to JSR for Deno, Node and Bun consumers.- Whether a scanner change bumps
RUBRIC_VERSION: scores change only for archives that hit a blind spot (0 of 5,415 swamp-extensions source files on 2026-10-26).
Acceptance
- One import scan for push, quality, fmt, the loader and the registry scorer; the four input vectors above change exactly as described, and no other vector changes.
- swamp-club bumps the package and passes the shared vectors (pairs with C1 or its own issue).
- UAT: the existing push, quality, fmt and registry/pull_dependency_record suites diff clean against stable; a scorecard check that an archive with a regex-quoted import is audited.
Out of scope
The canonical shipped form (#3970) and anything else in later steps.
Open
No activity in this phase yet.
Sign in to post a ripple.