chrome-extension: convert the classic-content-script rule to a check (content-script-module-syntax) - #578
Merged
Conversation
The chrome-extension pack's prose has always said a content script is injected as a *classic* script — a top-level `import`/`export` in one throws `Cannot use import statement outside a module`, in the HOST PAGE's console rather than the extension's, so the script silently never runs and nothing in the developer's own devtools says why. That is a static signature in the artifact, so it converts (prose-to-checks sweep). New blocking rule `content-script-module-syntax` in packs/chrome-extension: - the judged file set comes from the MANIFEST — the paths a `content_scripts` entry's `js` array names, plus the same array on a `chrome.scripting.registerContentScripts(...)` call — never every source file, because module syntax is correct everywhere else in an extension; - comments are stripped and string/template literal CONTENTS blanked before the judgement, so prose describing the trap and a code string a script builds can't fire it; - dynamic `import(...)` — the documented remedy — and `import.meta` are excluded by construction; - self-gates to [] when the repo carries no extension manifest. Fixture test in packs-tests/chrome-extension/: three violating fixtures (static import, static export, a registerContentScripts-declared file) all go red when the rule is neutered, four FP guards stay quiet (the classic dynamic-import loader, module syntax in a comment/string, the module service worker and page scripts, a repo with no manifest). Deletion test applied to the standing prose paragraph: it STAYS whole. The finding's why/fix carry the rule, the host-page-console diagnostic and the classic-loader remedy, but the paragraph also carries a second rule the check does not enforce — listing the module's whole transitive import graph under `web_accessible_resources` gated to the target origins, and having a test walk that graph. A paragraph carrying more than the check is not redundant, so it is not trimmed. Refs #572 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XMzqJX8ePDphhC1PNK5tHX
This was referenced Jul 30, 2026
This was referenced Jul 31, 2026
missingbulb
added a commit
that referenced
this pull request
Jul 31, 2026
Four lessons from the 2026-07-30 window (#594, #591, #583, #586, #578, #581), captured on the home's own local pack. One converts to a check: - `pack-discovery-entry-await` (blocking, `.claudinite/local/packs/claudinite/`) — pack discovery imports every `packs/<name>/pack.mjs` and every `<pack>/skills/<skill>/checks.mjs` on disk *before* activation is consulted, so a module in that graph that is also a CLI entry point is re-imported while still evaluating; a top-level `await` in its entry block then never settles and Node exits 13 having run nothing. That is #581 verbatim, and its fail-soft caller made it silent in every repo. The rule walks the static import graph from the real manifests, judges only the entry block (comments and string bodies blanked first), and stays quiet on the seven CLI workers that legitimately await at top level because none is in that graph. Red-first: with `run()` neutered the two violating fixtures fail and the five guards stay green. Three stay prose in that pack's `RULES.md` — none has a false-positive-free static signature: - green canon CI is not evidence about a consumer (#592's four same-day cases); name the consumer-side evidence in the PR instead. - a pack's `RULES.md` is injected into every declaring session, so an idea nobody can act on belongs in `docs/` — and a pack file may not link there, since `docs/` is outside the vendor set (#591, the #424 class). - pick a scheduled task's cadence from how often its signal actually moves; a `sharedMount` precondition fires nearly every night on a member (#583). `packs/README.md`'s derived corpus tally moves 67 → 68, as `packs-tests/catalog-tally.test.mjs` requires of any rule added anywhere. Full suite `node --test $(git ls-files '*.test.mjs')` → 1029 pass, 0 fail. `check_the_world` → 0 blocking, 9 advisory (all pre-existing `file-placement`). Refs #599 Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fulfils the prose-to-checks sweep dispatched in #572 (a task dispatch, so no auto-close).
What was swept
packs/and.claudinite/local/packs/— every pack'sRULES.mdand theSKILL.mds beside them — read against the check-the-world question fromengine/checks/DESIGN.md: does the rule constrain a static signature in the repo artifact? Dedupe was against the live rule catalog (packRules, 65 rules before this change).Most of what's left in the prose backlog is correctly prose: runtime browser divergence (
web-speech,node's jsdom rules), in-flight judgment (spec-driven-product,basics,research-project), deployment-time platform behaviour (aws-sam), and thepythonpack's own header says its residue is "architecture judgment that has no false-positive-free static signature". One rule stood out as always-testable and unconverted.The prose converted
packs/chrome-extension/RULES.md:The manifest names the injected files, and a static
import/exportin one is a static signature in the source. The failure mode is exactly what a check is for: the script silently never runs, and the SyntaxError lands in a console the extension developer isn't looking at.The check:
content-script-module-syntax(blocking)packs/chrome-extension/content-script-module-syntax.mjs, registered in that pack'spack.mjs. Blocking because it is a defect, not a direction — the content script does not run at all.Parsed, not grepped (the skill's rule — grepping
^importacross an extension would flag the whole codebase, since module syntax is correct everywhere except this one injection surface):content_scriptsentry'sjsarray names, resolved against the extension root — plus thejsstring literals on achrome.scripting.registerContentScripts(...)call. A module the content script dynamically imports is never in that set.import(...)— the documented remedy — andimport.metaare excluded by construction: the match requires a specifier or a binding right after the keyword, never an opening paren.[]when the repo carries nomanifest.jsondeclaringmanifest_version.The finding's
fixcarries the remedy the prose teaches: register a tiny classic loader whose only statement isimport(chrome.runtime.getURL('…')), then list that module and its transitive import graph underweb_accessible_resources.The proving fixture (see-it-fail)
packs-tests/chrome-extension/content-script-module-syntax.test.mjs— 7 tests. Neuteringrun()to return[]turns all three violating fixtures red and leaves the four guards green, which is the see-it-fail evidence:importin a manifest-declared content scriptlib.jsitself stays quiet)export defaultregisterContentScriptsimport.metaDeletion test — the paragraph STAYS
Applied per the skill, and it discriminates rather than deletes here. The finding's
whycarries the rule and the host-page-console diagnostic; itsfixcarries the classic-loader remedy. But the paragraph also carries a second rule the check does not enforce — listing the module's whole transitive import graph underweb_accessible_resourcesgated to the target origins, and having a test walk that graph so a newly added import can't fall out of the list. A paragraph carrying more than the check is not redundant, so it is not trimmed.Surface
Bounded to the conversion: the new rule module, its
pack.mjsline, its fixture test, the pack README's enforcement row, and the corpus tally inpacks/README.md(65 → 66, whichpacks-tests/catalog-tally.test.mjsguards).Green before opening
node --test $(git ls-files '*.test.mjs')→ 1008 pass, 0 fail (1001 before, +7 new).node engine/checks/check_the_world.mjs→ exit 0, 0 blocking, 9 advisory — all pre-existingfile-placementadvisories inengine/, none touched by this change.Generated by Claude Code