session-orchestrator 3.22.0 → 3.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/commands/autopilot-multi.md +14 -0
- package/.cursor/commands/autopilot.md +14 -0
- package/.cursor/commands/bootstrap.md +14 -0
- package/.cursor/commands/brainstorm.md +14 -0
- package/.cursor/commands/close.md +13 -0
- package/.cursor/commands/contract-version-bump.md +14 -0
- package/.cursor/commands/debug.md +14 -0
- package/.cursor/commands/discovery.md +14 -0
- package/.cursor/commands/dispatcher.md +14 -0
- package/.cursor/commands/eli5.md +14 -0
- package/.cursor/commands/eval.md +14 -0
- package/.cursor/commands/evolve.md +14 -0
- package/.cursor/commands/go.md +14 -0
- package/.cursor/commands/grill.md +14 -0
- package/.cursor/commands/harness-audit.md +13 -0
- package/.cursor/commands/journey-audit.md +14 -0
- package/.cursor/commands/memory-cleanup.md +14 -0
- package/.cursor/commands/persona-panel.md +14 -0
- package/.cursor/commands/plan.md +14 -0
- package/.cursor/commands/portfolio.md +14 -0
- package/.cursor/commands/reconcile.md +14 -0
- package/.cursor/commands/release.md +14 -0
- package/.cursor/commands/repo-audit.md +13 -0
- package/.cursor/commands/session.md +14 -0
- package/.cursor/commands/spinout.md +14 -0
- package/.cursor/commands/sunset-review.md +14 -0
- package/.cursor/commands/templates-ack.md +14 -0
- package/.cursor/commands/test.md +14 -0
- package/.cursor/hooks.json +60 -0
- package/.cursor/rules/000-session-orchestrator.mdc +8 -0
- package/.cursor/rules/010-session-workflow.mdc +9 -1
- package/.cursor/rules/020-quality-gates.mdc +1 -1
- package/.cursor/rules/030-wave-execution.mdc +1 -1
- package/.cursor/rules/050-plan.mdc +2 -2
- package/.cursor/rules/070-gitlab-ops.mdc +73 -57
- package/.cursor/rules/080-ecosystem-health.mdc +7 -7
- package/.cursor/skills/architecture/SKILL.md +13 -0
- package/.cursor/skills/autopilot/SKILL.md +12 -0
- package/.cursor/skills/bootstrap/SKILL.md +12 -0
- package/.cursor/skills/brainstorm/SKILL.md +13 -0
- package/.cursor/skills/claude-md-drift-check/SKILL.md +13 -0
- package/.cursor/skills/contract-version-bump/SKILL.md +12 -0
- package/.cursor/skills/convergence-monitoring/SKILL.md +12 -0
- package/.cursor/skills/daily/SKILL.md +12 -0
- package/.cursor/skills/debug/SKILL.md +13 -0
- package/.cursor/skills/discovery/SKILL.md +13 -0
- package/.cursor/skills/dispatcher/SKILL.md +13 -0
- package/.cursor/skills/docs-orchestrator/SKILL.md +13 -0
- package/.cursor/skills/domain-model/SKILL.md +13 -0
- package/.cursor/skills/ecosystem-health/SKILL.md +13 -0
- package/.cursor/skills/eli5/SKILL.md +13 -0
- package/.cursor/skills/eval/SKILL.md +12 -0
- package/.cursor/skills/evolve/SKILL.md +13 -0
- package/.cursor/skills/frontmatter-guard/SKILL.md +13 -0
- package/.cursor/skills/gitlab-ops/SKILL.md +13 -0
- package/.cursor/skills/gitlab-portfolio/SKILL.md +13 -0
- package/.cursor/skills/grill/SKILL.md +13 -0
- package/.cursor/skills/hook-development/SKILL.md +13 -0
- package/.cursor/skills/journey-audit/SKILL.md +13 -0
- package/.cursor/skills/mcp-builder/SKILL.md +13 -0
- package/.cursor/skills/memory-cleanup/SKILL.md +12 -0
- package/.cursor/skills/mode-selector/SKILL.md +13 -0
- package/.cursor/skills/npm-publish/SKILL.md +12 -0
- package/.cursor/skills/peekaboo-driver/SKILL.md +13 -0
- package/.cursor/skills/persona-panel/SKILL.md +12 -0
- package/.cursor/skills/plan/SKILL.md +13 -0
- package/.cursor/skills/playwright-driver/SKILL.md +13 -0
- package/.cursor/skills/quality-gates/SKILL.md +13 -0
- package/.cursor/skills/reconcile/SKILL.md +12 -0
- package/.cursor/skills/repo-audit/SKILL.md +13 -0
- package/.cursor/skills/session-end/SKILL.md +13 -0
- package/.cursor/skills/session-plan/SKILL.md +13 -0
- package/.cursor/skills/session-start/SKILL.md +13 -0
- package/.cursor/skills/skill-creator/SKILL.md +13 -0
- package/.cursor/skills/spinout/SKILL.md +12 -0
- package/.cursor/skills/sunset-review/SKILL.md +13 -0
- package/.cursor/skills/test-runner/SKILL.md +13 -0
- package/.cursor/skills/tmux-layout/SKILL.md +13 -0
- package/.cursor/skills/ubiquitous-language/SKILL.md +13 -0
- package/.cursor/skills/using-orchestrator/SKILL.md +13 -0
- package/.cursor/skills/vault-mirror/SKILL.md +13 -0
- package/.cursor/skills/vault-sync/SKILL.md +13 -0
- package/.cursor/skills/wave-executor/SKILL.md +13 -0
- package/.cursor/skills/write-executable-plan/SKILL.md +13 -0
- package/.mcp.json +4 -1
- package/CHANGELOG.md +168 -0
- package/README.md +18 -15
- package/agents/AGENTS.md +23 -4
- package/agents/code-implementer.md +2 -1
- package/agents/db-specialist.md +2 -1
- package/agents/docs-writer.md +3 -1
- package/agents/eval-judge.md +1 -1
- package/agents/session-reviewer.md +7 -1
- package/agents/test-writer.md +2 -1
- package/agents/ui-developer.md +2 -1
- package/commands/bootstrap.md +2 -2
- package/commands/close.md +3 -1
- package/commands/go.md +1 -1
- package/commands/journey-audit.md +43 -0
- package/docs/USER-GUIDE.md +2 -2
- package/docs/ci-setup.md +14 -0
- package/docs/codex-setup.md +64 -0
- package/docs/components.md +6 -6
- package/docs/cursor-setup.md +26 -47
- package/docs/events-schema.md +76 -4
- package/docs/github-mirror-protection.md +197 -0
- package/docs/pi-setup.md +2 -0
- package/docs/rule-authoring.md +3 -1
- package/docs/scope-collision-guard.md +49 -2
- package/docs/session-config-reference.md +26 -4
- package/docs/session-config-template.md +4 -3
- package/docs/telemetry.md +22 -0
- package/hooks/_lib/lock-bootstrap.mjs +8 -4
- package/hooks/_lib/vcs-create-matcher.mjs +397 -38
- package/hooks/enforce-scope.mjs +64 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +201 -20
- package/hooks/hooks-pi.json +1 -1
- package/hooks/hooks.json +2 -2
- package/hooks/on-session-end.mjs +211 -10
- package/hooks/on-session-start.mjs +214 -11
- package/hooks/on-stop.mjs +48 -9
- package/hooks/post-subagent-discovery-validator.mjs +34 -3
- package/hooks/post-tool-batch-wave-signal.mjs +11 -2
- package/hooks/pre-bash-issue-budget.mjs +117 -4
- package/hooks/pre-bash-sessions-ledger-guard.mjs +159 -0
- package/hooks/pre-bash-staging-fence.mjs +4 -0
- package/hooks/pre-task-scope-disjoint.mjs +368 -35
- package/hooks/skill-invocation-telemetry.mjs +21 -10
- package/monitors/monitors.json +6 -0
- package/package.json +1 -1
- package/pi/prompts/journey-audit.md +12 -0
- package/rules/_index.md +9 -1
- package/rules/always-on/ask-via-tool.md +62 -0
- package/rules/always-on/bash-harness-pitfalls.md +168 -0
- package/rules/always-on/build-value.md +47 -0
- package/rules/always-on/cross-session-messaging.md +59 -0
- package/rules/always-on/loop-and-monitor.md +221 -0
- package/rules/always-on/parallel-sessions.md +142 -12
- package/rules/always-on/receiving-review.md +108 -0
- package/rules/always-on/test-value.md +40 -0
- package/rules/always-on/verification-before-completion.md +77 -0
- package/scripts/archive-closed-prds.mjs +258 -18
- package/scripts/autopilot.mjs +5 -0
- package/scripts/backfill-evidence-digest.mjs +376 -0
- package/scripts/cursor-install.mjs +89 -48
- package/scripts/export-hw-learnings.mjs +143 -2
- package/scripts/express-path.mjs +299 -0
- package/scripts/generate-cursor-adapter.mjs +253 -0
- package/scripts/github-protection-audit.mjs +358 -0
- package/scripts/lib/autopilot/worktree-pipeline.mjs +240 -16
- package/scripts/lib/build-live-signals.mjs +24 -5
- package/scripts/lib/ci-status-banner.mjs +158 -11
- package/scripts/lib/command-blocker.mjs +70 -0
- package/scripts/lib/config/reconcile.mjs +79 -4
- package/scripts/lib/config/section-extractor.mjs +235 -36
- package/scripts/lib/config-schema.mjs +9 -1
- package/scripts/lib/config.mjs +57 -6
- package/scripts/lib/convergence-monitor.mjs +13 -2
- package/scripts/lib/cursor-hook-bridge.mjs +443 -0
- package/scripts/lib/dispatcher/cli.mjs +2 -2
- package/scripts/lib/express-path.mjs +327 -0
- package/scripts/lib/file-lock.mjs +22 -4
- package/scripts/lib/gates/gate-full.mjs +81 -8
- package/scripts/lib/gates/gate-helpers.mjs +76 -15
- package/scripts/lib/git-config-drift.mjs +134 -5
- package/scripts/lib/host-identity.mjs +247 -2
- package/scripts/lib/instruction-budget-guard.mjs +31 -1
- package/scripts/lib/issue-budget.mjs +229 -30
- package/scripts/lib/learnings/io.mjs +55 -10
- package/scripts/lib/learnings/schema.mjs +95 -28
- package/scripts/lib/lock-reaper.mjs +7 -1
- package/scripts/lib/locks/staging-fence-lock.mjs +5 -1
- package/scripts/lib/locks/state-md-lock.mjs +8 -1
- package/scripts/lib/memory-banner.mjs +5 -2
- package/scripts/lib/memory-paths.mjs +15 -6
- package/scripts/lib/mode-selector/scoring.mjs +53 -6
- package/scripts/lib/platform.mjs +72 -9
- package/scripts/lib/plugin-root.mjs +143 -19
- package/scripts/lib/project-hygiene.mjs +43 -3
- package/scripts/lib/quality-gate.mjs +271 -13
- package/scripts/lib/reconcile/emitter.mjs +87 -19
- package/scripts/lib/reconcile/engine.mjs +281 -13
- package/scripts/lib/reconcile/idempotency.mjs +102 -1
- package/scripts/lib/reconcile/renderer.mjs +148 -3
- package/scripts/lib/reconcile/sanitize.mjs +40 -17
- package/scripts/lib/reconcile/writer.mjs +415 -84
- package/scripts/lib/rule-loader.mjs +37 -2
- package/scripts/lib/rules-sync.mjs +51 -8
- package/scripts/lib/scope-gate.mjs +90 -0
- package/scripts/lib/session-close-backfill.mjs +369 -28
- package/scripts/lib/session-discovery.mjs +13 -3
- package/scripts/lib/session-end/phase-skip.mjs +37 -4
- package/scripts/lib/session-end/worktree-cleanup.mjs +154 -7
- package/scripts/lib/session-id.mjs +30 -14
- package/scripts/lib/session-identity/own-session.mjs +159 -0
- package/scripts/lib/session-lock.mjs +85 -30
- package/scripts/lib/session-schema/normalizer.mjs +70 -3
- package/scripts/lib/session-schema/validator.mjs +40 -0
- package/scripts/lib/session-start-probes.mjs +608 -0
- package/scripts/lib/session-transition.mjs +277 -0
- package/scripts/lib/sessions-staleness-banner.mjs +124 -57
- package/scripts/lib/spiral-carryover.mjs +90 -9
- package/scripts/lib/state-md/frontmatter-mutators.mjs +41 -8
- package/scripts/lib/state-md/mission-status.mjs +350 -52
- package/scripts/lib/state-md/yaml-parser.mjs +145 -16
- package/scripts/lib/state-md.mjs +12 -2
- package/scripts/lib/telemetry/sync.mjs +46 -8
- package/scripts/lib/validate/check-agents.mjs +66 -0
- package/scripts/lib/validate/check-cursor-adapter.mjs +102 -0
- package/scripts/lib/validate/check-dead-bridge.mjs +24 -2
- package/scripts/lib/validate/check-doc-cli-commands.mjs +16 -32
- package/scripts/lib/validate/check-hooks-symmetry.mjs +29 -63
- package/scripts/lib/validate/check-playwright-mcp-canary.mjs +13 -22
- package/scripts/lib/validate/check-plugin-monitors.mjs +10 -4
- package/scripts/lib/validate/check-test-value-bans.mjs +165 -17
- package/scripts/lib/validate/check-unwired-features.mjs +340 -32
- package/scripts/lib/validate/repo-files.mjs +275 -0
- package/scripts/lib/validate-vendored-rules.mjs +229 -7
- package/scripts/lib/vault-mirror/process.mjs +99 -43
- package/scripts/lib/vault-mirror/telemetry.mjs +210 -0
- package/scripts/lib/vault-staleness-banner.mjs +76 -6
- package/scripts/lib/vault-status/board-writer.mjs +211 -10
- package/scripts/lib/vault-status/narrative-mirror.mjs +188 -8
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +832 -0
- package/scripts/lib/wave-transcript-tail.mjs +869 -0
- package/scripts/materialize-wave-scope.mjs +209 -12
- package/scripts/mcp-server.sh +11 -2
- package/scripts/parse-config.mjs +65 -0
- package/scripts/token-audit.sh +9 -2
- package/scripts/validate-plugin.mjs +3 -0
- package/scripts/validate-wave-scope.mjs +67 -0
- package/scripts/vault-mirror.mjs +203 -34
- package/skills/_shared/monitor-patterns.md +31 -5
- package/skills/_shared/parallel-aware-auq.md +1 -1
- package/skills/_shared/parallel-aware-preamble.md +4 -2
- package/skills/_shared/platform-tools.md +11 -5
- package/skills/_shared/state-ownership.md +29 -2
- package/skills/autopilot/SKILL.md +5 -1
- package/skills/bootstrap/SKILL.md +3 -3
- package/skills/bootstrap/_shared-template.md +18 -10
- package/skills/bootstrap/deep-template.md +10 -6
- package/skills/bootstrap/fast-template.md +15 -8
- package/skills/bootstrap/standard-template.md +10 -6
- package/skills/claude-md-drift-check/checker.mjs +39 -11
- package/skills/dispatcher/SKILL.md +1 -1
- package/skills/journey-audit/SKILL.md +269 -0
- package/skills/peekaboo-driver/SKILL.md +15 -3
- package/skills/persona-panel/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +41 -1
- package/skills/session-end/SKILL.md +17 -4
- package/skills/session-end/metrics-collection.md +7 -4
- package/skills/session-end/phase-3-6-tail.md +11 -3
- package/skills/session-end/phase-3-7a-recommendations.md +16 -2
- package/skills/session-plan/SKILL.md +6 -1
- package/skills/session-plan/wave-template.md +1 -0
- package/skills/session-start/SKILL.md +30 -16
- package/skills/session-start/phase-7-5-mode-selector.md +15 -3
- package/skills/session-start/phase-8-5-express-path.md +77 -12
- package/skills/vault-sync/validator.mjs +31 -0
- package/skills/wave-executor/SKILL.md +4 -2
- package/skills/wave-executor/circuit-breaker.md +34 -9
- package/skills/wave-executor/wave-loop.md +102 -19
- package/templates/_shared/journey-manifest.md +110 -0
- package/templates/_shared/rules/parallel-sessions.md +0 -77
|
@@ -38,12 +38,20 @@
|
|
|
38
38
|
* it, so nothing turns the YAML into a value.
|
|
39
39
|
*
|
|
40
40
|
* S2 exists because S1 alone is fooled by a mention that reads nothing.
|
|
41
|
-
* `express-path.enabled
|
|
42
|
-
* `scripts/lib/state-md/body-sections.mjs:699`, a
|
|
43
|
-
*
|
|
44
|
-
* `express-path` from config at all
|
|
45
|
-
* `skills/session-start/phase-8-5-express-path.md` prose. S1 called
|
|
46
|
-
* S2
|
|
41
|
+
* The exemplar it was written against was `express-path.enabled`: it passed S1 on
|
|
42
|
+
* the strength of ONE line — `scripts/lib/state-md/body-sections.mjs:699`, a
|
|
43
|
+
* log-message template literal interpolating a value its caller already had —
|
|
44
|
+
* while no parser resolved `express-path` from config at all and the gate lived
|
|
45
|
+
* entirely in `skills/session-start/phase-8-5-express-path.md` prose. S1 called
|
|
46
|
+
* that wired; S2 called it what it was.
|
|
47
|
+
*
|
|
48
|
+
* FIXED 2026-08-23 (#1119): `scripts/lib/config.mjs` now parses the block, and
|
|
49
|
+
* `scripts/lib/express-path.mjs` makes the activation decision AND records it.
|
|
50
|
+
* Verified the same day — `express-path` no longer appears in this checker's
|
|
51
|
+
* stdout or stderr under either signal. **The exemplar is therefore historical.**
|
|
52
|
+
* It is kept because it is the clearest statement of what S2 detects, and because
|
|
53
|
+
* a rule that only cites live instances loses its explanation the moment it works.
|
|
54
|
+
* If you need a CURRENT S2 hit, run the checker; do not assume this one.
|
|
47
55
|
*
|
|
48
56
|
* S2 applies to TOP-LEVEL keys only — a nested key reaches code through its
|
|
49
57
|
* parent — and its premise is structural: every Session Config key has to pass
|
|
@@ -109,6 +117,29 @@
|
|
|
109
117
|
* (`soul-resolve.mjs`, whose only live claim is in `.claude/rules/owner-persona.md`
|
|
110
118
|
* but whose symbols appear in a 2026-06 changelog entry).
|
|
111
119
|
*
|
|
120
|
+
* ## S4 `unreachable-library-module` — the question the machine can answer
|
|
121
|
+
*
|
|
122
|
+
* S3 asks a document a question about GRAMMAR and accepts "the prose names an
|
|
123
|
+
* exported symbol" as wiring. That is generous by design, and it is where the
|
|
124
|
+
* largest instance of this defect class hid: `skills/session-start/SKILL.md`
|
|
125
|
+
* Phase 4 named 19 banner probes and their symbols, so S3 read every one of them
|
|
126
|
+
* as wired — while no hook, npm script, CI job or husky stage reached a single
|
|
127
|
+
* one. S1/S2 could not see them either, being config-key checks.
|
|
128
|
+
*
|
|
129
|
+
* S4 drops the grammar question and asks a reachability one: can any process
|
|
130
|
+
* that ACTUALLY STARTS arrive at this file? See `collectUnreachableLibraryModules`
|
|
131
|
+
* for the four conditions, the deliberate CLI-entrypoint boundary (with the
|
|
132
|
+
* measured cost of the alternative), the cluster-root collapse, and the named
|
|
133
|
+
* residuals. S4 does not subsume S3 and is not subsumed by it: S3 catches a
|
|
134
|
+
* REACHABLE module whose document lies about it, S4 catches an unreachable one
|
|
135
|
+
* whose document is honest about what it should do.
|
|
136
|
+
*
|
|
137
|
+
* Output shape is deliberately different from S1-S3 too. Those report a handful
|
|
138
|
+
* of lines; S4 measured 50 on the live tree (2026-08-23 @ 34321bc — a count, so read it as history) of 2026-08-23, which is a BACKLOG.
|
|
139
|
+
* The CLI therefore prints one aggregate WARN carrying the count, and the full
|
|
140
|
+
* census behind `--list` — see `runCheckUnwiredFeatures`. The `findings` array
|
|
141
|
+
* always carries every finding, so no programmatic consumer loses data.
|
|
142
|
+
*
|
|
112
143
|
* ## Consumer scope, and why "prose-only" is a finding rather than an error
|
|
113
144
|
*
|
|
114
145
|
* Read sites are counted in `scripts/**` and `hooks/**` (`.mjs`/`.js`/`.cjs`),
|
|
@@ -186,7 +217,18 @@ const CONSUMER_DIRS = Object.freeze(['scripts', 'hooks']);
|
|
|
186
217
|
const CODE_EXTENSIONS = Object.freeze(['.mjs', '.js', '.cjs']);
|
|
187
218
|
|
|
188
219
|
/** Directory names excluded from the consumer scan at any depth. */
|
|
189
|
-
|
|
220
|
+
// `worktrees` is here for a measured reason, not by analogy to node_modules.
|
|
221
|
+
// This walk uses `readdirSync`, NOT `git ls-files`, so `.gitignore` does not reach
|
|
222
|
+
// it — and `git worktree add` inside the repo (this repo's own convention is
|
|
223
|
+
// `.claude/worktrees/<name>`) drops a COMPLETE second checkout into the walk.
|
|
224
|
+
// Measured 2026-08-23 with one peer worktree present: +755 `.md` and +1209 `.mjs`
|
|
225
|
+
// files, 133 MB, `collectOrphanedProseModules` at 1727 ms of a 2270 ms total.
|
|
226
|
+
// The cost is the smaller half. The correctness half is worse: the peer's copy of
|
|
227
|
+
// `.claude/rules/owner-persona.md` was counted as an INDEPENDENT document naming
|
|
228
|
+
// the same module, so a finding cited two sources where one exists. Any scanner
|
|
229
|
+
// that walks the filesystem instead of the index has this bug; eight under
|
|
230
|
+
// `scripts/lib/validate/` use `readdirSync`.
|
|
231
|
+
const EXCLUDED_DIRS = Object.freeze(['node_modules', '.git', 'tests', 'test', '__tests__', 'worktrees']);
|
|
190
232
|
|
|
191
233
|
/** Extension carrying prose claims (signal S3). */
|
|
192
234
|
const PROSE_EXTENSIONS = Object.freeze(['.md']);
|
|
@@ -220,6 +262,20 @@ const PROSE_EXCLUDED_FILES = Object.freeze(['CHANGELOG.md', 'STATE.md']);
|
|
|
220
262
|
*/
|
|
221
263
|
const SELF_REL = path.join('scripts', 'lib', 'validate', 'check-unwired-features.mjs');
|
|
222
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Signal S4 entry surfaces: the NON-markdown files that mechanically invoke a
|
|
267
|
+
* module by path. Markdown is deliberately absent — that a SKILL.md names a
|
|
268
|
+
* module is precisely the claim S4 refuses to accept as wiring.
|
|
269
|
+
*/
|
|
270
|
+
const WIRING_FILES = Object.freeze(['package.json', '.gitlab-ci.yml']);
|
|
271
|
+
|
|
272
|
+
/** Directory surfaces for S4, as `[dir, extensions]`. `''` catches husky's extensionless stages. */
|
|
273
|
+
const WIRING_DIRS = Object.freeze([
|
|
274
|
+
['.github', Object.freeze(['.yml', '.yaml'])],
|
|
275
|
+
['.husky', Object.freeze(['', '.sh'])],
|
|
276
|
+
['hooks', Object.freeze(['.json', '.sh'])],
|
|
277
|
+
]);
|
|
278
|
+
|
|
223
279
|
/**
|
|
224
280
|
* The config-parser layer: the files a Session Config key must pass through to
|
|
225
281
|
* become a runtime value. Signal S2 (see header) checks top-level keys against
|
|
@@ -233,10 +289,11 @@ const PARSER_PATHS = Object.freeze([
|
|
|
233
289
|
]);
|
|
234
290
|
|
|
235
291
|
/**
|
|
236
|
-
* Declared-but-unread keys accepted on purpose. Key = full dotted path
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
* wired are all reported so the
|
|
292
|
+
* Declared-but-unread keys accepted on purpose. Key = full dotted path (S1/S2)
|
|
293
|
+
* or module path relative to the plugin root (S4), value = REASON naming the
|
|
294
|
+
* real consumer. See the header for the contract: an empty reason, a key that
|
|
295
|
+
* left every config surface, and a key that got wired are all reported so the
|
|
296
|
+
* list stays short and true.
|
|
240
297
|
*/
|
|
241
298
|
const ALLOWLIST = Object.freeze({
|
|
242
299
|
'auto-skill-dispatch':
|
|
@@ -247,6 +304,13 @@ const ALLOWLIST = Object.freeze({
|
|
|
247
304
|
'dedicated reader outside the parser layer — scripts/lib/instruction-budget-guard.mjs parses this block itself (S2 exemption only; S1 evidence is real)',
|
|
248
305
|
webhooks:
|
|
249
306
|
'dedicated reader outside the parser layer — scripts/lib/webhook-url.mjs resolves these URLs env-first (S2 exemption only; S1 evidence is real)',
|
|
307
|
+
// S4 entry (module path, not a config key). The prose-only state is REAL and
|
|
308
|
+
// recorded here BY NAME rather than left anonymous among the ~50-module S4
|
|
309
|
+
// backlog, where a per-module expectation cannot be reviewed. Named callers
|
|
310
|
+
// let a reviewer check the four sites; the self-draining `allowlist-stale`
|
|
311
|
+
// rule reports this entry the day a .mjs caller makes it reachable.
|
|
312
|
+
[path.join('scripts', 'lib', 'session-transition.mjs')]:
|
|
313
|
+
'prose-only consumer — leaveSourceRoot() is called by 4 skill prose sites (skills/session-start/SKILL.md:45 + :229, skills/_shared/parallel-aware-auq.md, skills/_shared/parallel-aware-preamble.md); the worktree promotion is a coordinator action, not a script (#1069)',
|
|
250
314
|
});
|
|
251
315
|
|
|
252
316
|
/**
|
|
@@ -261,7 +325,8 @@ const ALLOWLIST = Object.freeze({
|
|
|
261
325
|
/**
|
|
262
326
|
* @typedef {{
|
|
263
327
|
* kind: 'unwired-config-key' | 'parser-orphan-config-key' | 'allowlist-missing-reason'
|
|
264
|
-
* | 'allowlist-stale' | 'orphaned-prose-module' | '
|
|
328
|
+
* | 'allowlist-stale' | 'orphaned-prose-module' | 'unreachable-library-module'
|
|
329
|
+
* | 'tool-error',
|
|
265
330
|
* key: string,
|
|
266
331
|
* message: string,
|
|
267
332
|
* }} Finding
|
|
@@ -542,17 +607,48 @@ export function collectOrphanedProseModules(pluginRoot) {
|
|
|
542
607
|
body: readFileSync(absolute, 'utf8'),
|
|
543
608
|
}));
|
|
544
609
|
|
|
610
|
+
// Both membership questions below were nested scans: (1) re-filtered EVERY prose
|
|
611
|
+
// document per module, (2) re-scanned EVERY other module's lines per module.
|
|
612
|
+
// At this repo's size that is 468 x ~700 full-body `includes` plus 468 x 468 x
|
|
613
|
+
// ~300 line tests — measured 1727 ms of `inspectUnwiredFeatures`'s 2270 ms, in a
|
|
614
|
+
// CLI that eight test files spawn under a 30 s hook timeout. Indexing both once
|
|
615
|
+
// is O(corpus) instead of O(corpus^2); the answers are unchanged by construction,
|
|
616
|
+
// and the acceptance criterion for the rewrite was byte-identical CLI output.
|
|
617
|
+
/** basename -> prose docs naming it */
|
|
618
|
+
const claimsByBase = new Map();
|
|
619
|
+
for (const doc of prose) {
|
|
620
|
+
for (const module of modules) {
|
|
621
|
+
if (!doc.body.includes(module.base)) continue;
|
|
622
|
+
const list = claimsByBase.get(module.base);
|
|
623
|
+
if (list) list.push(doc);
|
|
624
|
+
else claimsByBase.set(module.base, [doc]);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
/** basename -> set of module paths referencing it OUTSIDE a comment */
|
|
628
|
+
const referencedBy = new Map();
|
|
629
|
+
for (const other of modules) {
|
|
630
|
+
for (const line of other.lines) {
|
|
631
|
+
if (isCommentLine(line)) continue;
|
|
632
|
+
for (const match of line.matchAll(/[A-Za-z0-9_.-]+\.(?:mjs|js|cjs)/g)) {
|
|
633
|
+
const set = referencedBy.get(match[0]);
|
|
634
|
+
if (set) set.add(other.relative);
|
|
635
|
+
else referencedBy.set(match[0], new Set([other.relative]));
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
545
640
|
for (const module of modules) {
|
|
546
641
|
// (1) named by a live document
|
|
547
|
-
const claims =
|
|
642
|
+
const claims = claimsByBase.get(module.base) ?? [];
|
|
548
643
|
if (claims.length === 0) continue;
|
|
549
644
|
|
|
550
|
-
// (2) no production module references it outside a comment
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
645
|
+
// (2) no production module references it outside a comment.
|
|
646
|
+
// Self-references do not count, which is why the index stores the referring
|
|
647
|
+
// path rather than a bare boolean.
|
|
648
|
+
const referrers = referencedBy.get(module.base);
|
|
649
|
+
const referenced =
|
|
650
|
+
referrers !== undefined &&
|
|
651
|
+
(referrers.size > 1 || !referrers.has(module.relative));
|
|
556
652
|
if (referenced) continue;
|
|
557
653
|
|
|
558
654
|
// (3) not invoked by path
|
|
@@ -580,6 +676,176 @@ export function collectOrphanedProseModules(pluginRoot) {
|
|
|
580
676
|
return { findings, scanned: { modules: modules.length, prose: prose.length } };
|
|
581
677
|
}
|
|
582
678
|
|
|
679
|
+
/**
|
|
680
|
+
* Extract every module-filename token a body mentions, ignoring comment lines.
|
|
681
|
+
*
|
|
682
|
+
* Token extraction beats a substring scan in BOTH directions. It is faster (one
|
|
683
|
+
* pass per file instead of one regex per candidate pair — 468² pair tests on the
|
|
684
|
+
* live tree), and it is more precise: `text.includes('writer.mjs')` is TRUE for
|
|
685
|
+
* `config-writer.mjs`, which silently marks an unrelated module as referenced.
|
|
686
|
+
* The character class stops at `/`, so `'./locks/state-md-lock.mjs'` yields
|
|
687
|
+
* exactly `state-md-lock.mjs`.
|
|
688
|
+
*
|
|
689
|
+
* @param {string[]} lines source lines
|
|
690
|
+
* @returns {Set<string>} module basenames mentioned outside comments
|
|
691
|
+
*/
|
|
692
|
+
function mentionedModuleTokens(lines) {
|
|
693
|
+
/** @type {Set<string>} */
|
|
694
|
+
const tokens = new Set();
|
|
695
|
+
for (const line of lines) {
|
|
696
|
+
if (isCommentLine(line)) continue;
|
|
697
|
+
for (const match of line.matchAll(/[A-Za-z0-9_.-]+\.(?:mjs|js|cjs)/g)) tokens.add(match[0]);
|
|
698
|
+
}
|
|
699
|
+
return tokens;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Signal S4 — library modules no mechanical caller can reach.
|
|
704
|
+
*
|
|
705
|
+
* ## The gap this closes, in one line
|
|
706
|
+
*
|
|
707
|
+
* S3 asks "does a DOCUMENT name a symbol of this module?" and accepts a yes as
|
|
708
|
+
* wiring. S4 asks the question the machine can answer: "can any process that
|
|
709
|
+
* actually starts — a hook, an npm script, a CI job, a husky stage — arrive at
|
|
710
|
+
* this file?" Prose in a SKILL.md is an instruction to an LLM, not a caller: the
|
|
711
|
+
* measured instance is `skills/session-start/SKILL.md` Phase 4, which named 19
|
|
712
|
+
* banner probes that no `.mjs` reached from any entrypoint.
|
|
713
|
+
*
|
|
714
|
+
* ## Entry roots, and why CLI entrypoints are among them
|
|
715
|
+
*
|
|
716
|
+
* Roots are (a) every module named in a NON-markdown wiring surface
|
|
717
|
+
* (`package.json`, `.gitlab-ci.yml`, `.github/workflows/**`, `.husky/**`,
|
|
718
|
+
* `hooks/*.json`) and (b) every CLI entrypoint. Reachability then follows
|
|
719
|
+
* module→module references transitively.
|
|
720
|
+
*
|
|
721
|
+
* (b) is a DELIBERATE boundary, not an oversight. A CLI entrypoint is invoked by
|
|
722
|
+
* path, and "the operator types `/autopilot`, whose skill body runs
|
|
723
|
+
* `node scripts/autopilot.mjs`" IS this repo's architecture — reporting it would
|
|
724
|
+
* indict the design rather than a defect. Measured 2026-08-23: treating CLI
|
|
725
|
+
* entrypoints as non-roots moves the census from 71 to 268 of 468 modules
|
|
726
|
+
* (15.6% → 57.5%), i.e. straight into the broken-instrument band that
|
|
727
|
+
* `.claude/rules/host-resources.md` § HR-101 forbids. A LIBRARY module, by
|
|
728
|
+
* contrast, can only ever be reached by being imported — so "nothing imports it,
|
|
729
|
+
* transitively" is a fact about the machine, not a judgement about prose.
|
|
730
|
+
*
|
|
731
|
+
* ## Cluster roots — one defect, one line
|
|
732
|
+
*
|
|
733
|
+
* Only the ROOT of each unreachable cluster is reported: a module no OTHER
|
|
734
|
+
* unreachable module references. `scripts/lib/owner-config.mjs` has no importer
|
|
735
|
+
* and drags its whole 7-file `owner-config/` subtree down with it; reporting the
|
|
736
|
+
* six interior files would multiply one deletion into seven findings that all
|
|
737
|
+
* disappear together. Measured on the live tree: 71 unreachable modules collapse
|
|
738
|
+
* to 50 roots. This is category separation in the sense of
|
|
739
|
+
* `.claude/rules/development.md` § Guard & Threshold Design — a structural split,
|
|
740
|
+
* never a raised threshold.
|
|
741
|
+
*
|
|
742
|
+
* ## Named residuals
|
|
743
|
+
*
|
|
744
|
+
* - **Basename granularity.** 22 basenames collide across 56 files (7 × `schema.mjs`,
|
|
745
|
+
* 3 × `telemetry.mjs`, …), so a mention of `schema.mjs` marks every `schema.mjs`
|
|
746
|
+
* as referenced. This errs toward WIRED — it can hide a finding, never invent
|
|
747
|
+
* one, which is the right direction for a check whose failure mode is being
|
|
748
|
+
* switched off. Revisit if a real module-resolver (import-specifier resolution
|
|
749
|
+
* relative to the importing file) becomes cheap, or if a collided basename is
|
|
750
|
+
* ever confirmed to mask a true positive.
|
|
751
|
+
* - **Reachable ≠ executed.** A module imported by a hook that never takes that
|
|
752
|
+
* branch reads as wired here. Proving execution needs coverage data, not a graph.
|
|
753
|
+
* - **Reachable from SOME entrypoint is not reachable from the PROMISED one.**
|
|
754
|
+
* This is the sharpest limit and it cost real recall. `ci-status-banner.mjs` is
|
|
755
|
+
* imported by `dispatcher/rank.mjs`, so S4 stays silent — yet CLAUDE.md promises
|
|
756
|
+
* it runs at SESSION-START, and no SessionStart path reached it at `4f6404e`.
|
|
757
|
+
* Same shape for `sessions-integrity-banner.mjs` (via `session-record-repair.mjs`)
|
|
758
|
+
* and `historical-guard.mjs` (via `check-banner-parity.mjs`). Catching those needs
|
|
759
|
+
* a per-entrypoint reachability question ("is X reachable from the SessionStart
|
|
760
|
+
* hook?"), which is a different check with a different root set, not a tightening
|
|
761
|
+
* of this one. Revisit if a second promised-entrypoint claim is ever missed.
|
|
762
|
+
*
|
|
763
|
+
* @param {string} pluginRoot absolute plugin root
|
|
764
|
+
* @returns {{findings: Finding[], scanned: {modules: number, roots: number, unreachable: number}}}
|
|
765
|
+
*/
|
|
766
|
+
export function collectUnreachableLibraryModules(pluginRoot) {
|
|
767
|
+
const absolute = CONSUMER_DIRS.flatMap((dir) => walkCode(path.join(pluginRoot, dir))).sort();
|
|
768
|
+
const modules = absolute.map((file) => {
|
|
769
|
+
const body = readFileSync(file, 'utf8');
|
|
770
|
+
const lines = body.split('\n');
|
|
771
|
+
const relative = path.relative(pluginRoot, file);
|
|
772
|
+
return {
|
|
773
|
+
relative,
|
|
774
|
+
base: path.basename(file),
|
|
775
|
+
entrypoint: isCliEntrypoint(body),
|
|
776
|
+
exports: collectExportedSymbols(body),
|
|
777
|
+
// This file contributes NO edges — the S4 counterpart of the SELF_REL
|
|
778
|
+
// exclusion the S1/S2 corpus already applies, and for the identical
|
|
779
|
+
// reason. Every S4 `ALLOWLIST` key is a module path written here as a
|
|
780
|
+
// string literal, so counting it as a mention marks the allowlisted
|
|
781
|
+
// module reachable, S4 stops reporting it, and the entry is then reported
|
|
782
|
+
// `allowlist-stale` — the check silently blinding itself to exactly the
|
|
783
|
+
// module an operator flagged. Measured 2026-08-28 on the first S4
|
|
784
|
+
// allowlist entry: 52 → 51 unreachable modules plus one bogus stale line.
|
|
785
|
+
mentions: relative === SELF_REL ? new Set() : mentionedModuleTokens(lines),
|
|
786
|
+
};
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
// Non-markdown surfaces that mechanically invoke a module by path.
|
|
790
|
+
const wiringBodies = [
|
|
791
|
+
...WIRING_FILES.map((rel) => path.join(pluginRoot, rel)).filter((file) => existsSync(file)),
|
|
792
|
+
...WIRING_DIRS.flatMap(([dir, extensions]) =>
|
|
793
|
+
walkCode(path.join(pluginRoot, dir), [], extensions, EXCLUDED_DIRS),
|
|
794
|
+
),
|
|
795
|
+
].map((file) => readFileSync(file, 'utf8'));
|
|
796
|
+
const wiringTokens = mentionedModuleTokens(wiringBodies.join('\n').split('\n'));
|
|
797
|
+
|
|
798
|
+
/** @type {Set<string>} */
|
|
799
|
+
const reachable = new Set();
|
|
800
|
+
/** @type {string[]} */
|
|
801
|
+
const stack = [];
|
|
802
|
+
for (const module of modules) {
|
|
803
|
+
if (!module.entrypoint && !wiringTokens.has(module.base)) continue;
|
|
804
|
+
reachable.add(module.relative);
|
|
805
|
+
stack.push(module.relative);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
const byRelative = new Map(modules.map((module) => [module.relative, module]));
|
|
809
|
+
while (stack.length > 0) {
|
|
810
|
+
const current = byRelative.get(/** @type {string} */ (stack.pop()));
|
|
811
|
+
if (!current) continue;
|
|
812
|
+
for (const module of modules) {
|
|
813
|
+
if (reachable.has(module.relative) || !current.mentions.has(module.base)) continue;
|
|
814
|
+
reachable.add(module.relative);
|
|
815
|
+
stack.push(module.relative);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const unreachable = modules.filter(
|
|
820
|
+
(module) => !reachable.has(module.relative) && !module.entrypoint && module.exports.length > 0,
|
|
821
|
+
);
|
|
822
|
+
const unreachableSet = new Set(unreachable.map((module) => module.relative));
|
|
823
|
+
const roots = unreachable.filter(
|
|
824
|
+
(module) =>
|
|
825
|
+
!unreachable.some((other) => other.relative !== module.relative && other.mentions.has(module.base)),
|
|
826
|
+
);
|
|
827
|
+
|
|
828
|
+
const findings = roots.map((module) => {
|
|
829
|
+
const dragged = [...module.mentions].filter(
|
|
830
|
+
(token) => token !== module.base && [...unreachableSet].some((rel) => path.basename(rel) === token),
|
|
831
|
+
);
|
|
832
|
+
const tail = dragged.length > 0 ? `, and drags ${dragged.length} further unreachable module(s)` : '';
|
|
833
|
+
return /** @type {Finding} */ ({
|
|
834
|
+
kind: 'unreachable-library-module',
|
|
835
|
+
key: module.relative,
|
|
836
|
+
message:
|
|
837
|
+
`exports ${module.exports.length} symbol(s) (${module.exports.slice(0, 3).join(', ')}) but no hook, ` +
|
|
838
|
+
`npm script, CI job or husky stage reaches it — transitively${tail}. Only markdown names it, and ` +
|
|
839
|
+
'prose is an instruction to an LLM, not a caller: wire it, delete it, or allowlist it with a reason',
|
|
840
|
+
});
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
return {
|
|
844
|
+
findings,
|
|
845
|
+
scanned: { modules: modules.length, roots: roots.length, unreachable: unreachable.length },
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
|
|
583
849
|
/**
|
|
584
850
|
* Run the full census.
|
|
585
851
|
*
|
|
@@ -598,7 +864,14 @@ export function inspectUnwiredFeatures(pluginRoot) {
|
|
|
598
864
|
const findings = [];
|
|
599
865
|
const result = {
|
|
600
866
|
ok: false,
|
|
601
|
-
summary: {
|
|
867
|
+
summary: {
|
|
868
|
+
declaredKeys: 0,
|
|
869
|
+
consumerFiles: 0,
|
|
870
|
+
unwired: 0,
|
|
871
|
+
allowlisted: 0,
|
|
872
|
+
orphanedModules: 0,
|
|
873
|
+
unreachableModules: 0,
|
|
874
|
+
},
|
|
602
875
|
/** @type {string[]} */
|
|
603
876
|
sourcesScanned: [],
|
|
604
877
|
findings,
|
|
@@ -613,6 +886,8 @@ export function inspectUnwiredFeatures(pluginRoot) {
|
|
|
613
886
|
let parserBody;
|
|
614
887
|
/** @type {ReturnType<typeof collectOrphanedProseModules>} */
|
|
615
888
|
let orphans;
|
|
889
|
+
/** @type {ReturnType<typeof collectUnreachableLibraryModules>} */
|
|
890
|
+
let unreachable;
|
|
616
891
|
try {
|
|
617
892
|
declared = collectDeclaredKeys(pluginRoot);
|
|
618
893
|
corpus = CONSUMER_DIRS.flatMap((dir) => walkCode(path.join(pluginRoot, dir)))
|
|
@@ -630,6 +905,7 @@ export function inspectUnwiredFeatures(pluginRoot) {
|
|
|
630
905
|
.map((absolute) => readFileSync(absolute, 'utf8'))
|
|
631
906
|
.join('\n');
|
|
632
907
|
orphans = collectOrphanedProseModules(pluginRoot);
|
|
908
|
+
unreachable = collectUnreachableLibraryModules(pluginRoot);
|
|
633
909
|
} catch (error) {
|
|
634
910
|
result.toolError = true;
|
|
635
911
|
findings.push({
|
|
@@ -691,6 +967,24 @@ export function inspectUnwiredFeatures(pluginRoot) {
|
|
|
691
967
|
findings.push(issue);
|
|
692
968
|
}
|
|
693
969
|
|
|
970
|
+
// S3 — prose promises a module nothing calls. Reported alongside the config
|
|
971
|
+
// census because it is the same defect class one level out: a claim with no
|
|
972
|
+
// mechanism behind it.
|
|
973
|
+
result.summary.orphanedModules = orphans.findings.length;
|
|
974
|
+
findings.push(...orphans.findings);
|
|
975
|
+
|
|
976
|
+
// S4 — no process that actually starts can reach these. Allowlistable on the
|
|
977
|
+
// same terms as a config key: the entry's reason must name the real consumer.
|
|
978
|
+
for (const finding of unreachable.findings) {
|
|
979
|
+
if (Object.prototype.hasOwnProperty.call(ALLOWLIST, finding.key)) {
|
|
980
|
+
result.summary.allowlisted += 1;
|
|
981
|
+
flagged.add(finding.key);
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
result.summary.unreachableModules += 1;
|
|
985
|
+
findings.push(finding);
|
|
986
|
+
}
|
|
987
|
+
|
|
694
988
|
for (const key of Object.keys(ALLOWLIST).sort()) {
|
|
695
989
|
if (flagged.has(key)) continue;
|
|
696
990
|
findings.push({
|
|
@@ -702,12 +996,6 @@ export function inspectUnwiredFeatures(pluginRoot) {
|
|
|
702
996
|
});
|
|
703
997
|
}
|
|
704
998
|
|
|
705
|
-
// S3 — prose promises a module nothing calls. Reported alongside the config
|
|
706
|
-
// census because it is the same defect class one level out: a claim with no
|
|
707
|
-
// mechanism behind it.
|
|
708
|
-
result.summary.orphanedModules = orphans.findings.length;
|
|
709
|
-
findings.push(...orphans.findings);
|
|
710
|
-
|
|
711
999
|
result.ok = !result.toolError && findings.length === 0;
|
|
712
1000
|
return result;
|
|
713
1001
|
}
|
|
@@ -721,7 +1009,7 @@ export function inspectUnwiredFeatures(pluginRoot) {
|
|
|
721
1009
|
* @param {string} pluginRoot absolute plugin root
|
|
722
1010
|
* @returns {number} 0 = scan completed (with or without findings), 2 = tool error
|
|
723
1011
|
*/
|
|
724
|
-
export function runCheckUnwiredFeatures(pluginRoot) {
|
|
1012
|
+
export function runCheckUnwiredFeatures(pluginRoot, { list = false } = {}) {
|
|
725
1013
|
console.log('--- Check: unwired config keys (declared-but-unread census, WARN-only) ---');
|
|
726
1014
|
const inspection = inspectUnwiredFeatures(pluginRoot);
|
|
727
1015
|
|
|
@@ -732,14 +1020,32 @@ export function runCheckUnwiredFeatures(pluginRoot) {
|
|
|
732
1020
|
return 2;
|
|
733
1021
|
}
|
|
734
1022
|
|
|
735
|
-
const { declaredKeys, consumerFiles, unwired, allowlisted, orphanedModules } =
|
|
1023
|
+
const { declaredKeys, consumerFiles, unwired, allowlisted, orphanedModules, unreachableModules } =
|
|
1024
|
+
inspection.summary;
|
|
1025
|
+
|
|
1026
|
+
// S4 is a BACKLOG, not a per-run alarm: 50 findings on the live tree against
|
|
1027
|
+
// 1-2 WARN lines from every sibling check. Printing all 50 every run is the
|
|
1028
|
+
// "gate that prints 282 lines gets switched off in week two" failure this
|
|
1029
|
+
// file's own header names. So the default carries the NUMBER (which ratchets,
|
|
1030
|
+
// and which a reviewer can compare run to run) plus the first few paths; the
|
|
1031
|
+
// full census is one `--list` away. Nothing is suppressed — only deferred.
|
|
1032
|
+
const s4 = inspection.findings.filter((item) => item.kind === 'unreachable-library-module');
|
|
736
1033
|
for (const item of inspection.findings) {
|
|
1034
|
+
if (!list && item.kind === 'unreachable-library-module') continue;
|
|
737
1035
|
console.log(` WARN: [${item.kind}] ${item.key} — ${item.message}`);
|
|
738
1036
|
}
|
|
1037
|
+
if (!list && s4.length > 0) {
|
|
1038
|
+
console.log(
|
|
1039
|
+
` WARN: [unreachable-library-module] ${s4.length} library module(s) that no hook, npm script, ` +
|
|
1040
|
+
`CI job or husky stage can reach — e.g. ${s4.slice(0, 3).map((item) => item.key).join(', ')}. ` +
|
|
1041
|
+
'Re-run with --list for the full census.',
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
739
1045
|
console.log(
|
|
740
1046
|
` PASS: censused ${declaredKeys} declared key(s) from ${inspection.sourcesScanned.join(' + ') || '(no source)'} ` +
|
|
741
1047
|
`against ${consumerFiles} consumer file(s) — ${unwired} unwired, ${allowlisted} allowlisted, ` +
|
|
742
|
-
`${orphanedModules} prose-orphaned module(s)`,
|
|
1048
|
+
`${orphanedModules} prose-orphaned module(s), ${unreachableModules} unreachable module(s)`,
|
|
743
1049
|
);
|
|
744
1050
|
console.log('');
|
|
745
1051
|
console.log('Results: 1 passed, 0 failed');
|
|
@@ -748,10 +1054,12 @@ export function runCheckUnwiredFeatures(pluginRoot) {
|
|
|
748
1054
|
|
|
749
1055
|
const isMain = import.meta.url === pathToFileURL(process.argv[1] || '').href;
|
|
750
1056
|
if (isMain) {
|
|
751
|
-
const
|
|
1057
|
+
const args = process.argv.slice(2);
|
|
1058
|
+
const pluginRoot = args.find((arg) => !arg.startsWith('-'));
|
|
752
1059
|
if (!pluginRoot) {
|
|
753
|
-
console.error('Usage: check-unwired-features.mjs <plugin-root>');
|
|
1060
|
+
console.error('Usage: check-unwired-features.mjs <plugin-root> [--list]');
|
|
1061
|
+
console.error(' --list print every unreachable-library-module finding instead of the aggregate');
|
|
754
1062
|
process.exit(2);
|
|
755
1063
|
}
|
|
756
|
-
process.exit(runCheckUnwiredFeatures(path.resolve(pluginRoot)));
|
|
1064
|
+
process.exit(runCheckUnwiredFeatures(path.resolve(pluginRoot), { list: args.includes('--list') }));
|
|
757
1065
|
}
|