Skip to main content
← Back to list
01Issue
FeatureOpenSwamp CLIPublic
AssigneesNone

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:

  1. 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 written npm:/jsr: import there, so it goes unaudited. A bare import there ships as written, because planBareImports confirms each site with a second, Babel pass (vector scan-regex-quote).
  2. 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).
  3. The package.json trigger and the bundler's zod scan in bundle.ts still read imports with their own from-only and import-position regexes, so a side-effect import "x" or dynamic import("x") of a bare name beside a package.json does not select package.json mode (vectors scan-side-effect-bare-package-json, scan-dynamic-bare-package-json). #3969 kept those regexes, moved into src/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/source reads 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 in planBareImports, 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/parser becomes 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.

02Bog Flow
◉OPEN○TRIAGED○IN PROGRESS○SHIPPED

Open

10/26/2026, 8:23:26 PM

No activity in this phase yet.

03Sludge Pulse

Sign in to post a ripple.