session-orchestrator 5.1.0 → 5.3.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.
Files changed (484) hide show
  1. package/.agents/skills/architecture/SKILL.md +3 -1
  2. package/.agents/skills/autopilot/SKILL.md +6 -1
  3. package/.agents/skills/autopilot/agents/openai.yaml +5 -0
  4. package/.agents/skills/bootstrap/SKILL.md +7 -1
  5. package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
  6. package/.agents/skills/brainstorm/SKILL.md +8 -1
  7. package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
  8. package/.agents/skills/claude-md-drift-check/SKILL.md +3 -1
  9. package/.agents/skills/close/SKILL.md +21 -0
  10. package/.agents/skills/close/agents/openai.yaml +5 -0
  11. package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
  12. package/.agents/skills/debug/SKILL.md +7 -1
  13. package/.agents/skills/debug/agents/openai.yaml +5 -0
  14. package/.agents/skills/discovery/SKILL.md +7 -2
  15. package/.agents/skills/discovery/agents/openai.yaml +5 -0
  16. package/.agents/skills/dispatcher/SKILL.md +7 -1
  17. package/.agents/skills/dispatcher/agents/openai.yaml +5 -0
  18. package/.agents/skills/docs-orchestrator/SKILL.md +3 -1
  19. package/.agents/skills/ecosystem-health/SKILL.md +3 -1
  20. package/.agents/skills/eli5/SKILL.md +7 -1
  21. package/.agents/skills/eli5/agents/openai.yaml +5 -0
  22. package/.agents/skills/eval/SKILL.md +7 -2
  23. package/.agents/skills/eval/agents/openai.yaml +5 -0
  24. package/.agents/skills/evolve/SKILL.md +8 -3
  25. package/.agents/skills/evolve/agents/openai.yaml +5 -0
  26. package/.agents/skills/frontmatter-guard/SKILL.md +3 -1
  27. package/.agents/skills/gitlab-ops/SKILL.md +3 -1
  28. package/.agents/skills/gitlab-portfolio/SKILL.md +3 -1
  29. package/.agents/skills/go/SKILL.md +22 -0
  30. package/.agents/skills/go/agents/openai.yaml +5 -0
  31. package/.agents/skills/grill/SKILL.md +7 -1
  32. package/.agents/skills/grill/agents/openai.yaml +5 -0
  33. package/.agents/skills/harness-audit/SKILL.md +20 -0
  34. package/.agents/skills/harness-audit/agents/openai.yaml +5 -0
  35. package/.agents/skills/hook-development/SKILL.md +3 -1
  36. package/.agents/skills/mcp-builder/SKILL.md +3 -1
  37. package/.agents/skills/memory-cleanup/SKILL.md +6 -1
  38. package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
  39. package/.agents/skills/mode-selector/SKILL.md +3 -1
  40. package/.agents/skills/npm-publish/SKILL.md +4 -2
  41. package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
  42. package/.agents/skills/persona-panel/SKILL.md +6 -1
  43. package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
  44. package/.agents/skills/plan/SKILL.md +8 -2
  45. package/.agents/skills/plan/agents/openai.yaml +5 -0
  46. package/.agents/skills/playwright-driver/SKILL.md +3 -1
  47. package/.agents/skills/portfolio/SKILL.md +21 -0
  48. package/.agents/skills/portfolio/agents/openai.yaml +5 -0
  49. package/.agents/skills/quality-gates/SKILL.md +3 -1
  50. package/.agents/skills/reconcile/SKILL.md +6 -1
  51. package/.agents/skills/reconcile/agents/openai.yaml +5 -0
  52. package/.agents/skills/release/SKILL.md +22 -0
  53. package/.agents/skills/release/agents/openai.yaml +5 -0
  54. package/.agents/skills/remote-offload/SKILL.md +3 -1
  55. package/.agents/skills/repo-audit/SKILL.md +6 -1
  56. package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
  57. package/.agents/skills/session/SKILL.md +21 -0
  58. package/.agents/skills/session/agents/openai.yaml +5 -0
  59. package/.agents/skills/session-end/SKILL.md +3 -1
  60. package/.agents/skills/session-plan/SKILL.md +3 -1
  61. package/.agents/skills/session-start/SKILL.md +3 -1
  62. package/.agents/skills/spinout/SKILL.md +6 -1
  63. package/.agents/skills/spinout/agents/openai.yaml +5 -0
  64. package/.agents/skills/sunset-review/SKILL.md +7 -1
  65. package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
  66. package/.agents/skills/templates-ack/SKILL.md +21 -0
  67. package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
  68. package/.agents/skills/test/SKILL.md +21 -0
  69. package/.agents/skills/test/agents/openai.yaml +5 -0
  70. package/.agents/skills/test-runner/SKILL.md +3 -1
  71. package/.agents/skills/tmux-layout/SKILL.md +3 -1
  72. package/.agents/skills/using-orchestrator/SKILL.md +3 -1
  73. package/.agents/skills/ux-grill/SKILL.md +7 -1
  74. package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
  75. package/.agents/skills/vault-mirror/SKILL.md +3 -1
  76. package/.agents/skills/vault-sync/SKILL.md +3 -1
  77. package/.agents/skills/wave-executor/SKILL.md +3 -1
  78. package/.agents/skills/write-executable-plan/SKILL.md +3 -1
  79. package/.claude-plugin/marketplace.json +1 -1
  80. package/.claude-plugin/plugin.json +1 -1
  81. package/.codex-plugin/plugin.json +4 -4
  82. package/.codex-plugin/skills/autopilot/SKILL.md +5 -4
  83. package/.codex-plugin/skills/bootstrap/SKILL.md +8 -4
  84. package/.codex-plugin/skills/brainstorm/SKILL.md +11 -4
  85. package/.codex-plugin/skills/close/SKILL.md +3 -3
  86. package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -1
  87. package/.codex-plugin/skills/debug/SKILL.md +11 -4
  88. package/.codex-plugin/skills/discovery/SKILL.md +8 -4
  89. package/.codex-plugin/skills/dispatcher/SKILL.md +4 -4
  90. package/.codex-plugin/skills/eli5/SKILL.md +9 -4
  91. package/.codex-plugin/skills/eval/SKILL.md +9 -4
  92. package/.codex-plugin/skills/evolve/SKILL.md +9 -4
  93. package/.codex-plugin/skills/go/SKILL.md +3 -3
  94. package/.codex-plugin/skills/grill/SKILL.md +11 -4
  95. package/.codex-plugin/skills/harness-audit/SKILL.md +4 -3
  96. package/.codex-plugin/skills/memory-cleanup/SKILL.md +9 -4
  97. package/.codex-plugin/skills/npm-publish/SKILL.md +1 -1
  98. package/.codex-plugin/skills/persona-panel/SKILL.md +5 -5
  99. package/.codex-plugin/skills/plan/SKILL.md +8 -4
  100. package/.codex-plugin/skills/portfolio/SKILL.md +3 -3
  101. package/.codex-plugin/skills/reconcile/SKILL.md +9 -4
  102. package/.codex-plugin/skills/release/SKILL.md +3 -3
  103. package/.codex-plugin/skills/repo-audit/SKILL.md +6 -4
  104. package/.codex-plugin/skills/session/SKILL.md +1 -1
  105. package/.codex-plugin/skills/spinout/SKILL.md +4 -4
  106. package/.codex-plugin/skills/sunset-review/SKILL.md +5 -4
  107. package/.codex-plugin/skills/test/SKILL.md +3 -3
  108. package/.codex-plugin/skills/ux-grill/SKILL.md +11 -4
  109. package/.cursor/commands/autopilot.md +4 -4
  110. package/.cursor/commands/bootstrap.md +5 -4
  111. package/.cursor/commands/brainstorm.md +5 -4
  112. package/.cursor/commands/close.md +4 -3
  113. package/.cursor/commands/debug.md +4 -4
  114. package/.cursor/commands/discovery.md +4 -4
  115. package/.cursor/commands/dispatcher.md +4 -4
  116. package/.cursor/commands/eli5.md +4 -4
  117. package/.cursor/commands/eval.md +4 -4
  118. package/.cursor/commands/evolve.md +4 -4
  119. package/.cursor/commands/go.md +4 -3
  120. package/.cursor/commands/grill.md +4 -4
  121. package/.cursor/commands/harness-audit.md +3 -3
  122. package/.cursor/commands/memory-cleanup.md +4 -4
  123. package/.cursor/commands/persona-panel.md +4 -4
  124. package/.cursor/commands/plan.md +5 -4
  125. package/.cursor/commands/portfolio.md +3 -3
  126. package/.cursor/commands/reconcile.md +4 -4
  127. package/.cursor/commands/release.md +4 -3
  128. package/.cursor/commands/repo-audit.md +4 -4
  129. package/.cursor/commands/session.md +1 -1
  130. package/.cursor/commands/spinout.md +4 -4
  131. package/.cursor/commands/sunset-review.md +4 -4
  132. package/.cursor/commands/test.md +3 -3
  133. package/.cursor/commands/ux-grill.md +4 -4
  134. package/.cursor/rules/000-session-orchestrator.mdc +0 -2
  135. package/.cursor/rules/010-session-workflow.mdc +2 -2
  136. package/.cursor/rules/050-plan.mdc +1 -1
  137. package/.cursor/skills/bootstrap/SKILL.md +1 -0
  138. package/.cursor/skills/close/SKILL.md +13 -0
  139. package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
  140. package/.cursor/skills/debug/SKILL.md +0 -1
  141. package/.cursor/skills/discovery/SKILL.md +0 -1
  142. package/.cursor/skills/dispatcher/SKILL.md +0 -1
  143. package/.cursor/skills/eli5/SKILL.md +0 -1
  144. package/.cursor/skills/eval/SKILL.md +1 -1
  145. package/.cursor/skills/evolve/SKILL.md +0 -1
  146. package/.cursor/skills/go/SKILL.md +13 -0
  147. package/.cursor/skills/grill/SKILL.md +0 -1
  148. package/.cursor/skills/harness-audit/SKILL.md +12 -0
  149. package/.cursor/skills/npm-publish/SKILL.md +1 -0
  150. package/.cursor/skills/portfolio/SKILL.md +12 -0
  151. package/.cursor/skills/release/SKILL.md +13 -0
  152. package/.cursor/skills/repo-audit/SKILL.md +0 -1
  153. package/.cursor/skills/sunset-review/SKILL.md +0 -1
  154. package/.cursor/skills/test/SKILL.md +12 -0
  155. package/.cursor/skills/ux-grill/SKILL.md +0 -1
  156. package/.cursor-plugin/plugin.json +1 -1
  157. package/.orchestrator/policy/blocked-commands.json +13 -4
  158. package/AGENTS.md +3 -2
  159. package/CHANGELOG.md +197 -0
  160. package/README.md +11 -9
  161. package/SECURITY.md +12 -0
  162. package/agents/dialectic-deriver.md +13 -10
  163. package/agents/eval-judge.md +67 -45
  164. package/agents/skill-applied-judge.md +34 -19
  165. package/commands/session.md +17 -3
  166. package/docs/baseline.md +12 -6
  167. package/docs/ci-setup.md +53 -0
  168. package/docs/codex-setup.md +15 -3
  169. package/docs/components.md +13 -6
  170. package/docs/events-schema.md +59 -9
  171. package/docs/install.md +16 -0
  172. package/docs/persona-panel.md +1 -1
  173. package/docs/pi-setup.md +1 -1
  174. package/docs/rule-authoring.md +135 -14
  175. package/docs/scope-collision-guard.md +2 -0
  176. package/docs/session-config-reference.md +106 -11
  177. package/docs/session-config-template.md +31 -2
  178. package/docs/telemetry.md +2 -0
  179. package/hooks/_lib/hook-import-set.json +125 -8
  180. package/hooks/_lib/subagent-paths.mjs +15 -0
  181. package/hooks/_lib/subagent-transcript.mjs +582 -31
  182. package/hooks/_lib/vcs-create-matcher.mjs +217 -62
  183. package/hooks/config-protection.mjs +11 -3
  184. package/hooks/cwd-change-restore.mjs +11 -3
  185. package/hooks/enforce-commands.mjs +70 -23
  186. package/hooks/enforce-scope.mjs +143 -33
  187. package/hooks/hooks-codex.json +1 -1
  188. package/hooks/hooks.json +1 -1
  189. package/hooks/loop-guard.mjs +11 -3
  190. package/hooks/on-session-end.mjs +72 -25
  191. package/hooks/on-session-start.mjs +48 -11
  192. package/hooks/on-stop.mjs +211 -23
  193. package/hooks/operator-steer.mjs +11 -3
  194. package/hooks/post-bash-issue-budget-refund.mjs +18 -8
  195. package/hooks/post-bash-write-verify.mjs +6 -2
  196. package/hooks/post-edit-import-probe.mjs +17 -9
  197. package/hooks/post-edit-validate.mjs +13 -5
  198. package/hooks/post-subagent-discovery-validator.mjs +98 -13
  199. package/hooks/post-tool-batch-wave-signal.mjs +200 -38
  200. package/hooks/post-tool-failure-corrective-context.mjs +11 -5
  201. package/hooks/post-tooluse-frontend-slop.mjs +10 -4
  202. package/hooks/pre-auq-clarity.mjs +18 -2
  203. package/hooks/pre-bash-destructive-guard.mjs +80 -9
  204. package/hooks/pre-bash-issue-budget.mjs +119 -28
  205. package/hooks/pre-bash-memory-propose-audit.mjs +86 -54
  206. package/hooks/pre-bash-sessions-ledger-guard.mjs +391 -20
  207. package/hooks/pre-bash-staging-fence.mjs +335 -31
  208. package/hooks/pre-bash-templates-first.mjs +19 -14
  209. package/hooks/pre-task-scope-disjoint.mjs +385 -5
  210. package/hooks/skill-invocation-telemetry.mjs +2 -1
  211. package/hooks/subagent-telemetry.mjs +15 -19
  212. package/hooks/wave-scope-commit-guard.mjs +197 -100
  213. package/monitors/monitors.json +1 -1
  214. package/output-styles/wave-summary.md +1 -1
  215. package/package.json +2 -1
  216. package/pi/prompts/autopilot.md +3 -3
  217. package/pi/prompts/bootstrap.md +3 -3
  218. package/pi/prompts/brainstorm.md +3 -3
  219. package/pi/prompts/close.md +2 -2
  220. package/pi/prompts/debug.md +3 -3
  221. package/pi/prompts/discovery.md +3 -3
  222. package/pi/prompts/dispatcher.md +3 -3
  223. package/pi/prompts/eli5.md +3 -3
  224. package/pi/prompts/eval.md +3 -3
  225. package/pi/prompts/evolve.md +3 -3
  226. package/pi/prompts/go.md +2 -2
  227. package/pi/prompts/grill.md +3 -3
  228. package/pi/prompts/harness-audit.md +2 -3
  229. package/pi/prompts/memory-cleanup.md +3 -3
  230. package/pi/prompts/persona-panel.md +3 -3
  231. package/pi/prompts/plan.md +3 -3
  232. package/pi/prompts/portfolio.md +2 -2
  233. package/pi/prompts/reconcile.md +3 -3
  234. package/pi/prompts/release.md +3 -3
  235. package/pi/prompts/repo-audit.md +3 -4
  236. package/pi/prompts/session.md +2 -2
  237. package/pi/prompts/spinout.md +3 -3
  238. package/pi/prompts/sunset-review.md +3 -3
  239. package/pi/prompts/templates-ack.md +1 -1
  240. package/pi/prompts/test.md +3 -3
  241. package/pi/prompts/ux-grill.md +3 -3
  242. package/rules/README.md +1 -1
  243. package/rules/opt-in-domain/prompt-caching.md +1 -1
  244. package/rules/opt-in-stack/backend-data.md +1 -1
  245. package/rules/opt-in-stack/backend.md +3 -3
  246. package/rules/opt-in-stack/frontend.md +1 -1
  247. package/rules/opt-in-stack/security-web.md +3 -3
  248. package/rules/opt-in-stack/swift.md +1 -1
  249. package/scripts/archive-closed-prds.mjs +2 -2
  250. package/scripts/auq-audit.mjs +2 -3
  251. package/scripts/autopilot.mjs +23 -2
  252. package/scripts/backfill-abandoned-sessions.mjs +171 -15
  253. package/scripts/backfill-evidence-digest.mjs +2 -1
  254. package/scripts/backfill-learnings-from-vault.mjs +2 -2
  255. package/scripts/check-package-manager.mjs +2 -2
  256. package/scripts/check-sessions-integrity.mjs +300 -0
  257. package/scripts/ci/assert-vitest-green.mjs +2 -1
  258. package/scripts/dialectic-deriver.mjs +50 -13
  259. package/scripts/emit-session.mjs +77 -32
  260. package/scripts/eval-session.mjs +65 -3
  261. package/scripts/export-hw-learnings.mjs +2 -1
  262. package/scripts/express-path.mjs +1 -1
  263. package/scripts/gc-stale-worktrees.mjs +2 -1
  264. package/scripts/generate-agents-skills.mjs +102 -29
  265. package/scripts/generate-codex-skills.mjs +48 -4
  266. package/scripts/generate-cursor-adapter.mjs +220 -11
  267. package/scripts/generate-hook-import-set.mjs +12 -27
  268. package/scripts/generate-pi-prompts.mjs +183 -13
  269. package/scripts/github-protection-audit.mjs +2 -3
  270. package/scripts/lib/agent-frontmatter.mjs +23 -1
  271. package/scripts/lib/agent-status.mjs +2 -31
  272. package/scripts/lib/auq/clarity.mjs +10 -2
  273. package/scripts/lib/auq/parse.mjs +12 -31
  274. package/scripts/lib/auq/schema.mjs +56 -41
  275. package/scripts/lib/auto-dialectic.mjs +304 -15
  276. package/scripts/lib/autopilot/flags.mjs +12 -1
  277. package/scripts/lib/autopilot/kill-switches.mjs +6 -3
  278. package/scripts/lib/autopilot/loop.mjs +14 -1
  279. package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
  280. package/scripts/lib/ci-status-banner.mjs +376 -16
  281. package/scripts/lib/claude-md-budget-lint.mjs +2 -5
  282. package/scripts/lib/command-blocker.mjs +408 -33
  283. package/scripts/lib/config/dialectic.mjs +12 -3
  284. package/scripts/lib/config/drift-check.mjs +19 -0
  285. package/scripts/lib/config/gate.mjs +74 -0
  286. package/scripts/lib/config/reaper.mjs +162 -0
  287. package/scripts/lib/config.mjs +14 -0
  288. package/scripts/lib/convergence-monitor.mjs +76 -13
  289. package/scripts/lib/cursor-hook-bridge.mjs +2 -2
  290. package/scripts/lib/description-surface.mjs +2 -5
  291. package/scripts/lib/dispatcher/cli.mjs +2 -1
  292. package/scripts/lib/ecosystem-health.mjs +11 -0
  293. package/scripts/lib/ecosystem-wizard.mjs +2 -1
  294. package/scripts/lib/eval/engine.mjs +421 -53
  295. package/scripts/lib/eval/judge.mjs +463 -40
  296. package/scripts/lib/eval/schema.mjs +10 -1
  297. package/scripts/lib/events-rotation.mjs +221 -25
  298. package/scripts/lib/events-schema.mjs +114 -0
  299. package/scripts/lib/events.mjs +524 -5
  300. package/scripts/lib/fetch-baseline.mjs +3 -8
  301. package/scripts/lib/frontmatter-guard.mjs +21 -10
  302. package/scripts/lib/gates/gate-baseline.mjs +27 -2
  303. package/scripts/lib/gates/gate-full.mjs +28 -3
  304. package/scripts/lib/gates/gate-helpers.mjs +243 -21
  305. package/scripts/lib/gates/gate-incremental.mjs +28 -3
  306. package/scripts/lib/gates/gate-per-file.mjs +27 -2
  307. package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +2 -1
  308. package/scripts/lib/gitlab-portfolio/cli.mjs +2 -1
  309. package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
  310. package/scripts/lib/instruction-budget-guard.mjs +332 -50
  311. package/scripts/lib/io.mjs +42 -8
  312. package/scripts/lib/is-main-module.mjs +82 -0
  313. package/scripts/lib/issue-close-strip-labels.mjs +207 -49
  314. package/scripts/lib/js-mask.mjs +197 -0
  315. package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
  316. package/scripts/lib/locks/index.mjs +32 -25
  317. package/scripts/lib/maintenance-due-banner.mjs +122 -91
  318. package/scripts/lib/orphan-reaper.mjs +1588 -0
  319. package/scripts/lib/peer-cards/merger.mjs +48 -10
  320. package/scripts/lib/peer-cards/reader.mjs +78 -2
  321. package/scripts/lib/peer-discovery.mjs +2 -5
  322. package/scripts/lib/playwright-driver/runner.mjs +2 -1
  323. package/scripts/lib/process-group.mjs +899 -0
  324. package/scripts/lib/quality-gate.mjs +107 -28
  325. package/scripts/lib/reconcile/backlog.mjs +368 -0
  326. package/scripts/lib/reconcile/engine.mjs +55 -188
  327. package/scripts/lib/reconcile/rule-expiry-sweep.mjs +884 -0
  328. package/scripts/lib/reconcile/sanitize.mjs +69 -3
  329. package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
  330. package/scripts/lib/resource-probe/parsers.mjs +31 -0
  331. package/scripts/lib/rule-loader.mjs +41 -12
  332. package/scripts/lib/rules-sync.mjs +2 -5
  333. package/scripts/lib/scope-echo.mjs +429 -7
  334. package/scripts/lib/scope-gate.mjs +605 -1
  335. package/scripts/lib/session-close-backfill.mjs +91 -12
  336. package/scripts/lib/session-id.mjs +9 -20
  337. package/scripts/lib/session-invocation.mjs +20 -0
  338. package/scripts/lib/session-schema/constants.mjs +30 -2
  339. package/scripts/lib/session-schema/normalizer.mjs +56 -4
  340. package/scripts/lib/session-schema.mjs +8 -3
  341. package/scripts/lib/session-start-probes.mjs +95 -10
  342. package/scripts/lib/sessions-canonical.mjs +23 -0
  343. package/scripts/lib/sessions-integrity-banner.mjs +7 -1
  344. package/scripts/lib/sessions-staleness-banner.mjs +193 -51
  345. package/scripts/lib/skill-evidence-window.mjs +891 -0
  346. package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
  347. package/scripts/lib/skill-evolution/engine.mjs +18 -9
  348. package/scripts/lib/skill-judge.mjs +45 -3
  349. package/scripts/lib/state-md.mjs +84 -3
  350. package/scripts/lib/sunset/walker.mjs +31 -4
  351. package/scripts/lib/tail-window.mjs +56 -0
  352. package/scripts/lib/telemetry/schema.mjs +30 -0
  353. package/scripts/lib/telemetry/sync.mjs +61 -6
  354. package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
  355. package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
  356. package/scripts/lib/tests-src-ratio.mjs +2 -6
  357. package/scripts/lib/tmux-layout/telemetry-stats.mjs +74 -14
  358. package/scripts/lib/user-invocable-skills.mjs +205 -0
  359. package/scripts/lib/ux-grill/reconcile.mjs +48 -22
  360. package/scripts/lib/validate/check-agents-skills.mjs +26 -15
  361. package/scripts/lib/validate/check-banner-parity.mjs +2 -2
  362. package/scripts/lib/validate/check-cursor-adapter.mjs +3 -2
  363. package/scripts/lib/validate/check-dead-bridge.mjs +2 -2
  364. package/scripts/lib/validate/check-doc-cli-commands.mjs +2 -2
  365. package/scripts/lib/validate/check-entry-guard.mjs +329 -0
  366. package/scripts/lib/validate/check-guard-requires-parity.mjs +2 -2
  367. package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
  368. package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +2 -2
  369. package/scripts/lib/validate/check-learning-provenance.mjs +2 -2
  370. package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
  371. package/scripts/lib/validate/check-rules.mjs +7 -5
  372. package/scripts/lib/validate/check-skill-links.mjs +35 -6
  373. package/scripts/lib/validate/check-skill-script-paths.mjs +241 -29
  374. package/scripts/lib/validate/check-test-git-config-target.mjs +26 -36
  375. package/scripts/lib/validate/check-unicode-safety.mjs +2 -2
  376. package/scripts/lib/validate/check-untracked-test-deps.mjs +9 -104
  377. package/scripts/lib/validate/check-unwired-features.mjs +220 -33
  378. package/scripts/lib/validate/check-validator-registration.mjs +36 -12
  379. package/scripts/lib/validate/check-vcs-repo-flag.mjs +2 -2
  380. package/scripts/lib/validate/confidential-names.mjs +10 -0
  381. package/scripts/lib/validate-vendored-rules.mjs +39 -12
  382. package/scripts/lib/vault-mirror/namespace.mjs +46 -8
  383. package/scripts/lib/vault-mirror/process.mjs +10 -3
  384. package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
  385. package/scripts/lib/vault-status/narrative-mirror.mjs +31 -7
  386. package/scripts/lib/vault-yaml.mjs +118 -0
  387. package/scripts/lib/wave-transcript-tail.mjs +2 -2
  388. package/scripts/lib/worktree/lifecycle.mjs +153 -1
  389. package/scripts/lock-reaper.mjs +2 -1
  390. package/scripts/materialize-wave-scope.mjs +87 -4
  391. package/scripts/migrate-sessions-jsonl.mjs +2 -1
  392. package/scripts/migrate-vault-paths.mjs +2 -3
  393. package/scripts/release-session-lock.mjs +305 -0
  394. package/scripts/release.mjs +109 -39
  395. package/scripts/relocate-vault-corpus.mjs +2 -3
  396. package/scripts/repair-invalid-sessions.mjs +2 -2
  397. package/scripts/resolve-session-invocation.mjs +59 -0
  398. package/scripts/run-quality-gate.mjs +156 -17
  399. package/scripts/session-shape.mjs +2 -2
  400. package/scripts/site-numbers.mjs +35 -11
  401. package/scripts/sweep-expired-rules.mjs +227 -0
  402. package/scripts/validate-plugin.mjs +21 -0
  403. package/scripts/validate-wave-scope.mjs +32 -105
  404. package/scripts/vault-consolidate.mjs +2 -2
  405. package/scripts/vault-mirror.mjs +11 -4
  406. package/scripts/wave-scope-binding.mjs +2 -3
  407. package/skills/_shared/bootstrap-gate.md +1 -1
  408. package/skills/_shared/monitor-patterns.md +1 -1
  409. package/skills/_shared/platform-tools.md +23 -11
  410. package/skills/_shared/research-evidence.md +53 -0
  411. package/skills/_shared/state-ownership.md +3 -0
  412. package/skills/autopilot/SKILL.md +80 -11
  413. package/skills/bootstrap/SKILL.md +51 -1
  414. package/skills/brainstorm/SKILL.md +16 -0
  415. package/skills/claude-md-drift-check/SKILL.md +1 -1
  416. package/skills/claude-md-drift-check/checker.mjs +49 -11
  417. package/{commands/close.md → skills/close/SKILL.md} +9 -3
  418. package/skills/convergence-monitoring/README.md +8 -1
  419. package/skills/convergence-monitoring/SIGNALS.md +50 -6
  420. package/skills/convergence-monitoring/SKILL.md +15 -6
  421. package/skills/debug/SKILL.md +10 -0
  422. package/skills/discovery/SKILL.md +24 -1
  423. package/skills/discovery/probes-session.md +2 -2
  424. package/skills/dispatcher/SKILL.md +38 -7
  425. package/skills/eli5/SKILL.md +11 -0
  426. package/skills/eval/SKILL.md +52 -23
  427. package/skills/eval/rubric-v1.md +1 -0
  428. package/skills/eval/rubric-v2.md +457 -0
  429. package/skills/evolve/SKILL.md +9 -2
  430. package/skills/evolve/references/evolve-dialectic-mode.md +46 -25
  431. package/skills/gitlab-ops/SKILL.md +3 -2
  432. package/{commands/go.md → skills/go/SKILL.md} +9 -1
  433. package/skills/grill/SKILL.md +19 -0
  434. package/{commands/harness-audit.md → skills/harness-audit/SKILL.md} +7 -2
  435. package/skills/hook-development/SKILL.md +46 -41
  436. package/skills/memory-cleanup/SKILL.md +7 -0
  437. package/skills/npm-publish/SKILL.md +2 -2
  438. package/skills/persona-panel/SKILL.md +56 -1
  439. package/skills/persona-panel/persona-format.md +1 -1
  440. package/skills/plan/SKILL.md +28 -1
  441. package/{commands/portfolio.md → skills/portfolio/SKILL.md} +8 -2
  442. package/skills/reconcile/SKILL.md +21 -0
  443. package/{commands/release.md → skills/release/SKILL.md} +16 -2
  444. package/skills/repo-audit/SKILL.md +7 -0
  445. package/skills/session-end/SKILL.md +13 -16
  446. package/skills/session-end/discovery-scan.md +1 -1
  447. package/skills/session-end/phase-3-6-tail.md +55 -9
  448. package/skills/session-end/plan-verification.md +2 -2
  449. package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
  450. package/skills/session-end/session-metrics-write.md +10 -0
  451. package/skills/session-plan/SKILL.md +18 -6
  452. package/skills/session-plan/references/session-plan-task-classification.md +2 -2
  453. package/skills/session-start/SKILL.md +5 -4
  454. package/skills/session-start/phase-8-5-express-path.md +6 -6
  455. package/skills/session-start/references/phase-1-5-session-continuity.md +1 -1
  456. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +1 -1
  457. package/skills/session-start/references/phase-4-ssot-environment-check.md +6 -4
  458. package/skills/spinout/SKILL.md +12 -1
  459. package/skills/sunset-review/SKILL.md +13 -0
  460. package/{commands/test.md → skills/test/SKILL.md} +10 -4
  461. package/skills/ux-grill/SKILL.md +20 -2
  462. package/skills/wave-executor/SKILL.md +14 -7
  463. package/skills/wave-executor/circuit-breaker.md +2 -0
  464. package/skills/wave-executor/references/wave-executor-state-init.md +18 -4
  465. package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
  466. package/skills/wave-executor/references/wave-loop-review.md +17 -1
  467. package/commands/autopilot.md +0 -80
  468. package/commands/bootstrap.md +0 -56
  469. package/commands/brainstorm.md +0 -48
  470. package/commands/debug.md +0 -36
  471. package/commands/discovery.md +0 -32
  472. package/commands/dispatcher.md +0 -59
  473. package/commands/eli5.md +0 -33
  474. package/commands/eval.md +0 -28
  475. package/commands/evolve.md +0 -10
  476. package/commands/grill.md +0 -45
  477. package/commands/memory-cleanup.md +0 -26
  478. package/commands/persona-panel.md +0 -121
  479. package/commands/plan.md +0 -15
  480. package/commands/reconcile.md +0 -23
  481. package/commands/repo-audit.md +0 -24
  482. package/commands/spinout.md +0 -15
  483. package/commands/sunset-review.md +0 -27
  484. package/commands/ux-grill.md +0 -51
@@ -104,10 +104,17 @@
104
104
  // Import-safety: importing this module MUST NOT execute anything — the isMain
105
105
  // guard at the bottom is the only side-effecting path.
106
106
 
107
- import { pathToFileURL } from 'node:url';
108
107
  import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
109
108
  import { spawnSync } from 'node:child_process';
110
109
  import path from 'node:path';
110
+ import { isMainModule } from '../is-main-module.mjs';
111
+ import { maskSource } from '../js-mask.mjs';
112
+
113
+ // `maskSource` moved to `scripts/lib/js-mask.mjs` (#1388 — three other modules
114
+ // needed the same lexer). `package.json` carries no `exports` map, so every
115
+ // `scripts/lib/**` path is public API: this re-export keeps the OLD import path
116
+ // working for any consumer that already reaches in here.
117
+ export { maskSource } from '../js-mask.mjs';
111
118
 
112
119
  // ---------------------------------------------------------------------------
113
120
  // Constants
@@ -167,108 +174,6 @@ const NON_CALLEES = new Set([
167
174
  'await', 'new', 'do', 'else', 'in', 'of', 'delete', 'void', 'yield', 'import',
168
175
  ]);
169
176
 
170
- // ---------------------------------------------------------------------------
171
- // Source masking — string/template/comment/regex interiors become spaces
172
- // ---------------------------------------------------------------------------
173
-
174
- /**
175
- * Blank out the INTERIOR of every string literal, template literal, comment and
176
- * regex literal, preserving byte offsets and newlines. Delimiters are kept so
177
- * paren/comma balance and identifier boundaries survive.
178
- *
179
- * This is what separates CODE from DATA. A test that embeds fixture source in a
180
- * template literal (this check's own test file does) must not be scanned as
181
- * though that fixture were its own body — masking makes that structural instead
182
- * of relying on a self-exemption marker.
183
- *
184
- * @param {string} text
185
- * @returns {string} same-length masked source
186
- */
187
- export function maskSource(text) {
188
- const out = text.split('');
189
- const n = out.length;
190
- let i = 0;
191
- // Last significant (non-space) char before `i`, used for the regex/division
192
- // disambiguation below.
193
- let prev = '';
194
-
195
- const blank = (from, to) => {
196
- for (let k = from; k < to && k < n; k++) if (out[k] !== '\n') out[k] = ' ';
197
- };
198
-
199
- while (i < n) {
200
- const c = text[i];
201
- const next = text[i + 1];
202
-
203
- // Line comment.
204
- if (c === '/' && next === '/') {
205
- let j = i;
206
- while (j < n && text[j] !== '\n') j++;
207
- blank(i, j);
208
- i = j;
209
- continue;
210
- }
211
- // Block comment.
212
- if (c === '/' && next === '*') {
213
- let j = i + 2;
214
- while (j < n && !(text[j] === '*' && text[j + 1] === '/')) j++;
215
- blank(i, Math.min(j + 2, n));
216
- i = Math.min(j + 2, n);
217
- continue;
218
- }
219
- // Single/double-quoted string — keep the quotes, blank the interior.
220
- if (c === "'" || c === '"') {
221
- let j = i + 1;
222
- while (j < n && text[j] !== c) {
223
- if (text[j] === '\\') j++;
224
- if (text[j] === '\n') break; // unterminated — bail at EOL
225
- j++;
226
- }
227
- blank(i + 1, j);
228
- i = Math.min(j + 1, n);
229
- prev = c;
230
- continue;
231
- }
232
- // Template literal — blank everything including `${…}` substitutions.
233
- if (c === '`') {
234
- let j = i + 1;
235
- while (j < n && text[j] !== '`') {
236
- if (text[j] === '\\') j++;
237
- j++;
238
- }
239
- blank(i + 1, j);
240
- i = Math.min(j + 1, n);
241
- prev = c;
242
- continue;
243
- }
244
- // Regex literal — only when the previous significant char cannot end an
245
- // expression, AND a closing unescaped `/` exists on the SAME line. The
246
- // same-line bound keeps a misread division from swallowing the file.
247
- if (c === '/' && (prev === '' || '(,=:[!&|?{};+-*%~^<>'.includes(prev))) {
248
- let j = i + 1;
249
- let inClass = false;
250
- let closed = -1;
251
- while (j < n && text[j] !== '\n') {
252
- if (text[j] === '\\') { j += 2; continue; }
253
- if (text[j] === '[') inClass = true;
254
- else if (text[j] === ']') inClass = false;
255
- else if (text[j] === '/' && !inClass) { closed = j; break; }
256
- j++;
257
- }
258
- if (closed > i) {
259
- blank(i + 1, closed);
260
- i = closed + 1;
261
- prev = '/';
262
- continue;
263
- }
264
- }
265
-
266
- if (!/\s/.test(c)) prev = c;
267
- i++;
268
- }
269
- return out.join('');
270
- }
271
-
272
177
  // ---------------------------------------------------------------------------
273
178
  // Tiny source scanners (offset-aligned against the masked text)
274
179
  // ---------------------------------------------------------------------------
@@ -938,7 +843,7 @@ export function runCheckUntrackedTestDeps(repoRoot) {
938
843
  return findings.length > 0 ? 1 : 0;
939
844
  }
940
845
 
941
- const isMain = import.meta.url === pathToFileURL(process.argv[1] || '').href;
846
+ const isMain =isMainModule(import.meta.url);
942
847
  if (isMain) {
943
848
  const root = process.argv[2];
944
849
  if (!root) {
@@ -53,6 +53,30 @@
53
53
  * a rule that only cites live instances loses its explanation the moment it works.
54
54
  * If you need a CURRENT S2 hit, run the checker; do not assume this one.
55
55
  *
56
+ * S6 `parser-only-config-key` — the key IS read, but the ONLY file that
57
+ * reads it is its own block parser
58
+ * `scripts/lib/config/<root>.mjs`.
59
+ *
60
+ * S6 is the mirror image of S2, and it exists because S1 counts the PARSER as a
61
+ * consumer: `CONSUMER_DIRS` is `scripts/` + `hooks/`, and every block parser
62
+ * lives under `scripts/lib/config/`. A key its parser resolves and nothing else
63
+ * reads is `compact-nudge` wearing a value object. Measured 2026-09-22 on this
64
+ * tree: `reaper.max-hook-latency-ms` and `reaper.false-alarm-window` both passed
65
+ * S1 on `scripts/lib/config/reaper.mjs` alone while `scripts/lib/orphan-reaper.mjs`
66
+ * used same-named HARDCODED defaults and never saw the parsed value.
67
+ *
68
+ * Why the signal is this narrow — one read file, and it is the key's OWN parser.
69
+ * The blunt rule ("the parser layer never counts") was measured first and
70
+ * REJECTED: it flips 102 of 263 declared keys to unwired, because the ordinary
71
+ * shape here is a consumer that reads the camelCase FIELD (`cfg.driftCheck.mode`)
72
+ * while this census matches the kebab TOKEN. 102 of 263 is the broken instrument
73
+ * `.claude/rules/host-resources.md` HR-101 forbids. The narrow rule reports 32 —
74
+ * a backlog, printed as an aggregate like S4 and enumerated under `--list`.
75
+ *
76
+ * S6 carries no camelCase fallback ON PURPOSE: adding one re-hides both reaper
77
+ * keys, whose camelCase names exist in `orphan-reaper.mjs` as that module's own
78
+ * defaults. A name is not a read.
79
+ *
56
80
  * S2 applies to TOP-LEVEL keys only — a nested key reaches code through its
57
81
  * parent — and its premise is structural: every Session Config key has to pass
58
82
  * through the parser layer to become a value. Measured 2026-08-08: 84 of 89
@@ -265,9 +289,9 @@
265
289
 
266
290
  import { existsSync, readFileSync, readdirSync } from 'node:fs';
267
291
  import path from 'node:path';
268
- import { pathToFileURL } from 'node:url';
269
292
 
270
293
  import { sizingSubject } from '../learnings/sizing-subject.mjs';
294
+ import { isMainModule } from '../is-main-module.mjs';
271
295
 
272
296
  /** Documented config surface — every `yaml` fence in this file is a declaration. */
273
297
  const TEMPLATE_REL = 'docs/session-config-template.md';
@@ -393,6 +417,29 @@ const PARSER_PATHS = Object.freeze([
393
417
  path.join('scripts', 'parse-config.mjs'),
394
418
  ]);
395
419
 
420
+ /**
421
+ * The per-block parser directory: `scripts/lib/config/<block>.mjs` turns ONE
422
+ * Session Config block's YAML into a value object and does nothing else.
423
+ *
424
+ * Signal S6 below is about this directory specifically, which is why it is not
425
+ * folded into {@link PARSER_PATHS}.
426
+ */
427
+ const BLOCK_PARSER_DIR = path.join('scripts', 'lib', 'config');
428
+
429
+ /**
430
+ * The key's OWN block parser, by convention `scripts/lib/config/<root>.mjs`.
431
+ *
432
+ * Deliberately an exact name match and not "anything under the parser dir": a
433
+ * sibling block's parser reading the key is still a second file, and S6 only
434
+ * fires when there is exactly ONE reader and it is the key's own.
435
+ *
436
+ * @param {string} root top-level key of the block
437
+ * @returns {string} plugin-root-relative path
438
+ */
439
+ function ownBlockParserPath(root) {
440
+ return path.join(BLOCK_PARSER_DIR, `${root}.mjs`);
441
+ }
442
+
396
443
  /**
397
444
  * Declared-but-unread keys accepted on purpose. Key = full dotted path (S1/S2)
398
445
  * or module path relative to the plugin root (S4), value = REASON naming the
@@ -420,7 +467,8 @@ const ALLOWLIST = Object.freeze({
420
467
 
421
468
  /**
422
469
  * @typedef {{
423
- * kind: 'unwired-config-key' | 'parser-orphan-config-key' | 'allowlist-missing-reason'
470
+ * kind: 'unwired-config-key' | 'parser-orphan-config-key' | 'parser-only-config-key'
471
+ * | 'allowlist-missing-reason'
424
472
  * | 'allowlist-stale' | 'orphaned-prose-module' | 'unreachable-library-module'
425
473
  * | 'coordinator-invoked-module' | 'hand-keyed-learning-subject'
426
474
  * | 'tool-error',
@@ -652,19 +700,57 @@ export function collectExportedSymbols(body) {
652
700
  return [...names];
653
701
  }
654
702
 
703
+ /**
704
+ * Re-export targets of a barrel: the specifiers of its `export * from '…'`
705
+ * (and `export * as ns from '…'`) lines.
706
+ *
707
+ * `collectExportedSymbols` cannot see these — a star re-export names no symbol —
708
+ * so a PURE barrel reads as `exports: []`. That does not merely hide the barrel:
709
+ * the S4 population predicate below requires at least one export, so the barrel
710
+ * never enters `unreachable` at all, and is therefore unavailable as the CLUSTER
711
+ * ROOT of the module it re-exports. The target is then reported as its own root.
712
+ *
713
+ * Measured 2026-09-16 on the live tree: `scripts/lib/worktree.mjs` is exactly
714
+ * `export * from './worktree/index.mjs';`, its only importers (`workspace.mjs`,
715
+ * `worktree-freshness.mjs`) are themselves unreachable — yet S4 reported
716
+ * `scripts/lib/worktree/index.mjs`, an interior file, instead of the cluster.
717
+ *
718
+ * Only the POPULATION is affected. Edge propagation already works: the BFS walks
719
+ * `mentions`, and a star re-export line is not a comment, so a barrel that IS
720
+ * reachable already marks its target reachable (measured the same day:
721
+ * `worktree.mjs` carries `index.mjs` in `mentions`).
722
+ *
723
+ * Anchored at column 0 like the export-symbol grammar above, so a docblock line
724
+ * (`* export * from …`) and an indented string never count.
725
+ *
726
+ * @param {string} body module source
727
+ * @returns {string[]} re-exported module specifiers, as written
728
+ */
729
+ export function collectReExportTargets(body) {
730
+ return [
731
+ ...body.matchAll(/^export\s+\*(?:\s+as\s+[A-Za-z0-9_$]+)?\s+from\s+['"]([^'"]+)['"]/gm),
732
+ ].map((match) => match[1]);
733
+ }
734
+
655
735
  /**
656
736
  * Whether a module is a CLI entrypoint rather than a library.
657
737
  *
658
738
  * An entrypoint is invoked by path (npm script, hook wiring, CI job), so having
659
739
  * no importer is its normal state and says nothing about being wired.
660
740
  *
741
+ * `isMainModule(` is part of the grammar because the #1371 sweep replaced ~50
742
+ * hand-written `process.argv[1] === import.meta.url` guards with the shared
743
+ * `scripts/lib/is-main-module.mjs` predicate. Without this alternative a swept
744
+ * CLI matches none of the other branches and silently reclassifies as a library
745
+ * candidate, so S4 reports it as an `unreachable-library-module` root.
746
+ *
661
747
  * @param {string} body module source
662
748
  * @returns {boolean}
663
749
  */
664
750
  export function isCliEntrypoint(body) {
665
751
  return (
666
752
  body.startsWith('#!') ||
667
- /import\.meta\.url\s*===|require\.main\s*===\s*module|process\.argv\[1\]/.test(body)
753
+ /import\.meta\.url\s*===|require\.main\s*===\s*module|process\.argv\[1\]|isMainModule\(/.test(body)
668
754
  );
669
755
  }
670
756
 
@@ -845,7 +931,9 @@ function mentionedModuleTokens(lines) {
845
931
  * switched off. Revisit if a real module-resolver (import-specifier resolution
846
932
  * relative to the importing file) becomes cheap, or if a collided basename is
847
933
  * ever confirmed to mask a true positive. The `coordinator-invoked-module`
848
- * DOWNGRADE is exempt, and since #1293 so is the CLUSTER-ROOT filter: in both
934
+ * DOWNGRADE is exempt, and since #1293 so is the CLUSTER-ROOT filter (since
935
+ * #1298 its predicate also drives the "drags N" count, which had collapsed
936
+ * two dragged `index.mjs` into one): in both
849
937
  * a colliding basename must be named with its `dirname/base` suffix, because
850
938
  * those matches move a module OUT of the reportable class and would otherwise
851
939
  * hide a true unreachable sibling. Measured cost of leaving the root filter
@@ -890,6 +978,10 @@ export function collectUnreachableLibraryModules(pluginRoot) {
890
978
  edgeOnly,
891
979
  entrypoint: isCliEntrypoint(body),
892
980
  exports: collectExportedSymbols(body),
981
+ // Star re-exports name no symbol, so `exports` is empty for a pure
982
+ // barrel — see collectReExportTargets for why that silently promoted an
983
+ // interior module to a root.
984
+ reExports: collectReExportTargets(body),
893
985
  // This file contributes NO edges — the S4 counterpart of the SELF_REL
894
986
  // exclusion the S1/S2 corpus already applies, and for the identical
895
987
  // reason. Every S4 `ALLOWLIST` key is a module path written here as a
@@ -947,8 +1039,15 @@ export function collectUnreachableLibraryModules(pluginRoot) {
947
1039
  }
948
1040
  }
949
1041
 
1042
+ // A module with NO public surface at all is not a finding — nothing can be
1043
+ // wired to it. A pure `export * from` barrel HAS a surface (everything its
1044
+ // target exports), so it belongs in the population: otherwise it cannot be
1045
+ // the cluster root of the module it re-exports (collectReExportTargets).
950
1046
  const unreachable = modules.filter(
951
- (module) => !reachable.has(module.relative) && !module.entrypoint && module.exports.length > 0,
1047
+ (module) =>
1048
+ !reachable.has(module.relative) &&
1049
+ !module.entrypoint &&
1050
+ (module.exports.length > 0 || module.reExports.length > 0),
952
1051
  );
953
1052
  const unreachableSet = new Set(unreachable.map((module) => module.relative));
954
1053
  // Basename census, shared by the root filter below and the downgrade half
@@ -965,23 +1064,69 @@ export function collectUnreachableLibraryModules(pluginRoot) {
965
1064
  const basenameCount = new Map();
966
1065
  for (const module of modules) basenameCount.set(module.base, (basenameCount.get(module.base) ?? 0) + 1);
967
1066
 
968
- const roots = unreachable.filter((module) => {
969
- // A bare-basename mention only suppresses when the basename is UNIQUE
970
- // (see the census above). When it collides, the mentioning module must name
971
- // the `dirname/base` form in its body — otherwise ONE unreachable module
972
- // mentioning bare `index.mjs` masks EVERY differently-pathed `index.mjs`
973
- // root at once (#1293: `locks/index.mjs` + `worktree/index.mjs` were masked
974
- // by a third unreachable module until that module was deleted for an
975
- // unrelated reason).
976
- const ambiguous = (basenameCount.get(module.base) ?? 0) > 1;
977
- const qualified = module.relative.split(path.sep).slice(-2).join('/');
978
- const qualifiedRe = ambiguous ? tokenMatcher(qualified) : null;
979
- return !unreachable.some(
980
- (other) =>
981
- other.relative !== module.relative &&
982
- other.mentions.has(module.base) &&
983
- (qualifiedRe === null || qualifiedRe.test(other.rawBody)),
984
- );
1067
+ // One `dirname/base` matcher per unreachable module whose basename collides,
1068
+ // built once: `references` below runs per (mentioner, target) pair.
1069
+ /** @type {Map<string, RegExp>} */
1070
+ const qualifiedMatchers = new Map();
1071
+ for (const module of unreachable) {
1072
+ if ((basenameCount.get(module.base) ?? 0) < 2) continue;
1073
+ qualifiedMatchers.set(module.relative, tokenMatcher(module.relative.split(path.sep).slice(-2).join('/')));
1074
+ }
1075
+ /**
1076
+ * Does `mentioner` reference the unreachable module `target`? A bare-basename
1077
+ * mention only counts when the basename is UNIQUE (see the census above).
1078
+ * When it collides, the mentioning module must name the `dirname/base` form
1079
+ * in its body — otherwise ONE unreachable module mentioning bare `index.mjs`
1080
+ * references EVERY differently-pathed `index.mjs` at once (#1293:
1081
+ * `locks/index.mjs` + `worktree/index.mjs` were masked as roots by a third
1082
+ * unreachable module until that module was deleted for an unrelated reason).
1083
+ *
1084
+ * The ONE predicate for both consumers — the cluster-root filter and the drag
1085
+ * count of the finding message — so the two cannot disagree about who
1086
+ * references whom. They did (#1298): the drag count compared bare basenames,
1087
+ * so a root importing `a/index.mjs` AND `b/index.mjs` read "drags 1".
1088
+ *
1089
+ * @param {(typeof modules)[number]} mentioner
1090
+ * @param {(typeof modules)[number]} target an `unreachable` module
1091
+ * @returns {boolean}
1092
+ */
1093
+ const references = (mentioner, target) => {
1094
+ if (!mentioner.mentions.has(target.base)) return false;
1095
+ const qualifiedRe = qualifiedMatchers.get(target.relative);
1096
+ return qualifiedRe === undefined || qualifiedRe.test(mentioner.rawBody);
1097
+ };
1098
+
1099
+ const roots = unreachable.filter(
1100
+ (module) => !unreachable.some((other) => other.relative !== module.relative && references(other, module)),
1101
+ );
1102
+
1103
+ // A pure `export *` barrel enters the population above so it can HEAD its own
1104
+ // cluster, but reporting it needs one more condition. S3 already exempts this
1105
+ // exact shape by name (`autopilot-telemetry.mjs`, condition 4): a star
1106
+ // re-export has zero NAMED symbols, so every "does prose name one of its
1107
+ // exports?" test is vacuously FALSE — including S4's own coordinator-invoked
1108
+ // downgrade below, which iterates `module.exports`. A reported pure barrel
1109
+ // could therefore never be downgraded, only allowlisted: a finding with no
1110
+ // legitimate exit. The category split is the barrel's TARGET. A barrel over a
1111
+ // module that is itself unreachable heads a genuinely dead cluster and is
1112
+ // reported; a barrel over LIVE code is a backward-compat shim, which is a
1113
+ // different (and much weaker) finding than "nothing can reach this feature".
1114
+ // Measured 2026-09-16: without this split `scripts/lib/autopilot-telemetry.mjs`
1115
+ // — `export * from './autopilot/telemetry.mjs'`, target reachable, sole
1116
+ // importer a test — became a new permanent WARN.
1117
+ // Ceiling: a dead shim over live code is now invisible to S4 as well as to S3.
1118
+ // Revisit if a stale back-compat shim is ever confirmed to have outlived its
1119
+ // last importer unnoticed — that wants its own check, not a looser S4.
1120
+ const reportableRoots = roots.filter((module) => {
1121
+ if (module.exports.length > 0) return true;
1122
+ return module.reExports.some((spec) => {
1123
+ if (!spec.startsWith('.')) {
1124
+ // Aliased/bare specifier: fall back to the basename granularity this
1125
+ // file already documents as its named residual.
1126
+ return [...unreachableSet].some((rel) => path.basename(rel) === path.basename(spec));
1127
+ }
1128
+ return unreachableSet.has(path.normalize(path.join(path.dirname(module.relative), spec)));
1129
+ });
985
1130
  });
986
1131
 
987
1132
  // Category split (see § Category split in the doc block above): an INSTRUCTION
@@ -997,7 +1142,7 @@ export function collectUnreachableLibraryModules(pluginRoot) {
997
1142
  .map((file) => ({ relative: path.relative(pluginRoot, file), body: readFileSync(file, 'utf8') }));
998
1143
 
999
1144
  let coordinatorInvoked = 0;
1000
- const findings = roots.map((module) => {
1145
+ const findings = reportableRoots.map((module) => {
1001
1146
  // Docs write POSIX separators regardless of host; `path.relative` does not.
1002
1147
  const relativePosix = module.relative.split(path.sep).join('/');
1003
1148
  const qualified = relativePosix.split('/').slice(-2).join('/');
@@ -1028,15 +1173,22 @@ export function collectUnreachableLibraryModules(pluginRoot) {
1028
1173
  'architecture. Re-check only if that instruction is ever removed',
1029
1174
  });
1030
1175
  }
1031
- const dragged = [...module.mentions].filter(
1032
- (token) => token !== module.base && [...unreachableSet].some((rel) => path.basename(rel) === token),
1033
- );
1176
+ // Counted per MODULE through the root filter's own predicate, never per
1177
+ // basename token: `mentions` is a Set of basenames, so two dragged
1178
+ // `index.mjs` files would collapse into one (#1298).
1179
+ const dragged = unreachable.filter((other) => other.relative !== module.relative && references(module, other));
1034
1180
  const tail = dragged.length > 0 ? `, and drags ${dragged.length} further unreachable module(s)` : '';
1181
+ // A pure barrel reports its re-export surface; naming "0 symbol(s)" there
1182
+ // would read as a checker bug rather than as the barrel it is.
1183
+ const surface =
1184
+ module.exports.length > 0
1185
+ ? `exports ${module.exports.length} symbol(s) (${module.exports.slice(0, 3).join(', ')})`
1186
+ : `re-exports ${module.reExports.length} module(s) (${module.reExports.slice(0, 3).join(', ')})`;
1035
1187
  return /** @type {Finding} */ ({
1036
1188
  kind: 'unreachable-library-module',
1037
1189
  key: module.relative,
1038
1190
  message:
1039
- `exports ${module.exports.length} symbol(s) (${module.exports.slice(0, 3).join(', ')}) but no hook, ` +
1191
+ `${surface} but no hook, ` +
1040
1192
  `npm script, CI job or husky stage reaches it — transitively${tail}. No instruction surface names ` +
1041
1193
  'one of its exports either: wire it, delete it, or allowlist it with a reason',
1042
1194
  });
@@ -1046,7 +1198,7 @@ export function collectUnreachableLibraryModules(pluginRoot) {
1046
1198
  findings,
1047
1199
  scanned: {
1048
1200
  modules: modules.length,
1049
- roots: roots.length,
1201
+ roots: reportableRoots.length,
1050
1202
  unreachable: unreachable.length,
1051
1203
  coordinatorInvoked,
1052
1204
  },
@@ -1175,6 +1327,7 @@ export function inspectUnwiredFeatures(pluginRoot) {
1175
1327
  orphanedModules: 0,
1176
1328
  unreachableModules: 0,
1177
1329
  coordinatorInvokedModules: 0,
1330
+ parserOnly: 0,
1178
1331
  handKeyedSubjects: 0,
1179
1332
  judgedSubjects: 0,
1180
1333
  },
@@ -1234,7 +1387,7 @@ export function inspectUnwiredFeatures(pluginRoot) {
1234
1387
 
1235
1388
  for (const key of [...declared.keys.keys()].sort()) {
1236
1389
  const meta = /** @type {DeclaredKey} */ (declared.keys.get(key));
1237
- const { code, comment } = countReadSites(meta, corpus);
1390
+ const { code, comment, files: readFiles } = countReadSites(meta, corpus);
1238
1391
 
1239
1392
  /** @type {Finding | null} */
1240
1393
  let issue = null;
@@ -1247,6 +1400,21 @@ export function inspectUnwiredFeatures(pluginRoot) {
1247
1400
  `declared in ${meta.sources.join(' + ')} but no .mjs under ${CONSUMER_DIRS.join('/ or ')}/ ` +
1248
1401
  `reads it${commentNote} — wire it, delete it, or allowlist it with a reason`,
1249
1402
  };
1403
+ } else if (readFiles.length === 1 && readFiles[0] === ownBlockParserPath(meta.root)) {
1404
+ // S6 — the ONLY file that reads this key is the parser that produced it.
1405
+ // A parser is not a consumer: it turns YAML into a field nobody then
1406
+ // reads, which is `compact-nudge` wearing a value object. Measured
1407
+ // 2026-09-22: `reaper.max-hook-latency-ms` and `reaper.false-alarm-window`
1408
+ // both passed S1 on the strength of `scripts/lib/config/reaper.mjs`
1409
+ // alone, while `orphan-reaper.mjs` used its OWN hardcoded defaults of the
1410
+ // same name and never received the parsed value.
1411
+ issue = {
1412
+ kind: 'parser-only-config-key',
1413
+ key,
1414
+ message:
1415
+ `read only by its own block parser (${readFiles[0]}) — a parser is not a consumer; ` +
1416
+ 'nothing outside the config layer reads the parsed value',
1417
+ };
1250
1418
  } else if (key === meta.root && !tokenMatcher(key).test(parserBody)) {
1251
1419
  issue = {
1252
1420
  kind: 'parser-orphan-config-key',
@@ -1272,7 +1440,8 @@ export function inspectUnwiredFeatures(pluginRoot) {
1272
1440
  continue;
1273
1441
  }
1274
1442
 
1275
- result.summary.unwired += 1;
1443
+ if (issue.kind === 'parser-only-config-key') result.summary.parserOnly += 1;
1444
+ else result.summary.unwired += 1;
1276
1445
  findings.push(issue);
1277
1446
  }
1278
1447
 
@@ -1342,6 +1511,7 @@ export function runCheckUnwiredFeatures(pluginRoot, { list = false } = {}) {
1342
1511
  consumerFiles,
1343
1512
  unwired,
1344
1513
  allowlisted,
1514
+ parserOnly,
1345
1515
  orphanedModules,
1346
1516
  unreachableModules,
1347
1517
  coordinatorInvokedModules,
@@ -1361,8 +1531,16 @@ export function runCheckUnwiredFeatures(pluginRoot, { list = false } = {}) {
1361
1531
  // architecture — an instruction surface tells an LLM to call the module.
1362
1532
  // Measured 2026-09-07: 46 of the 52 findings the single S4 class carried.
1363
1533
  // Printing 46 WARN lines for the design is the broken instrument HR-101 forbids.
1364
- const DEFERRED = Object.freeze(['unreachable-library-module', 'coordinator-invoked-module']);
1534
+ //
1535
+ // `parser-only-config-key` (S6) is deferred on the S4 terms too: 32 findings
1536
+ // on the live tree is a backlog to work down, not a per-run alarm.
1537
+ const DEFERRED = Object.freeze([
1538
+ 'unreachable-library-module',
1539
+ 'coordinator-invoked-module',
1540
+ 'parser-only-config-key',
1541
+ ]);
1365
1542
  const s4 = inspection.findings.filter((item) => item.kind === 'unreachable-library-module');
1543
+ const s6 = inspection.findings.filter((item) => item.kind === 'parser-only-config-key');
1366
1544
  for (const item of inspection.findings) {
1367
1545
  if (!list && DEFERRED.includes(item.kind)) continue;
1368
1546
  console.log(` WARN: [${item.kind}] ${item.key} — ${item.message}`);
@@ -1381,10 +1559,19 @@ export function runCheckUnwiredFeatures(pluginRoot, { list = false } = {}) {
1381
1559
  );
1382
1560
  }
1383
1561
 
1562
+ if (!list && s6.length > 0) {
1563
+ console.log(
1564
+ ` WARN: [parser-only-config-key] ${s6.length} declared key(s) whose only reader is their own ` +
1565
+ `block parser — e.g. ${s6.slice(0, 3).map((item) => item.key).join(', ')}. ` +
1566
+ 'Re-run with --list for the full census.',
1567
+ );
1568
+ }
1569
+
1384
1570
  console.log(
1385
1571
  ` PASS: censused ${declaredKeys} declared key(s) from ${inspection.sourcesScanned.join(' + ') || '(no source)'} ` +
1386
1572
  `against ${consumerFiles} consumer file(s) — ${unwired} unwired, ${allowlisted} allowlisted, ` +
1387
- `${orphanedModules} prose-orphaned module(s), ${unreachableModules} unreachable module(s), ` +
1573
+ `${parserOnly} parser-only, ${orphanedModules} prose-orphaned module(s), ` +
1574
+ `${unreachableModules} unreachable module(s), ` +
1388
1575
  `${coordinatorInvokedModules} coordinator-invoked module(s), ${handKeyedSubjects} hand-keyed ` +
1389
1576
  `learning subject(s) of ${judgedSubjects} judged`,
1390
1577
  );
@@ -1393,7 +1580,7 @@ export function runCheckUnwiredFeatures(pluginRoot, { list = false } = {}) {
1393
1580
  return 0;
1394
1581
  }
1395
1582
 
1396
- const isMain = import.meta.url === pathToFileURL(process.argv[1] || '').href;
1583
+ const isMain = isMainModule(import.meta.url);
1397
1584
  if (isMain) {
1398
1585
  const args = process.argv.slice(2);
1399
1586
  const pluginRoot = args.find((arg) => !arg.startsWith('-'));
@@ -51,15 +51,21 @@
51
51
  * skill body) and nowhere in the three RUN surfaces above still reports
52
52
  * UNREGISTERED — being documented is not being run. REVISIT if a fourth run
53
53
  * surface (a new CI job file, a different git hook) is ever added: extend
54
- * `RUN_SURFACES`, do not special-case it here. The comment stripper's own
55
- * quote-tracking is a single flat state — an escaped quote (`\"`) inside a
56
- * double-quoted string is not honoured, and a template-literal's `${...}`
57
- * interpolation is not walked separately. Both failure directions lean
58
- * toward treating MORE text as "inside a string" than a real parser would,
59
- * which can only make the stripper MISS a comment (false "still
60
- * registered"), never manufacture a false UNREGISTERED the direction this
61
- * checker's own false-positive history (the paragraph above) already
62
- * measured as the live hazard.
54
+ * `RUN_SURFACES`, do not special-case it here.
55
+ *
56
+ * THE "NEVER A FALSE UNREGISTERED" CLAIM WAS FALSE (#1388, measured
57
+ * 2026-09-18 @ 20a4cbff). This header used to argue that the hand-rolled
58
+ * stripper could only ever MISS a comment, never manufacture a false
59
+ * UNREGISTERED. It had no regex-literal branch, so
60
+ * `const re = /\/*$/;` in `scripts/validate-plugin.mjs` opened a BLOCK
61
+ * COMMENT at the regex's `/*` and swallowed every `runCheck(...)` line below
62
+ * it precisely a false UNREGISTERED, for every checker registered after
63
+ * that point. The `.mjs` surface is therefore lexed by the shared
64
+ * `maskSource` (`scripts/lib/js-mask.mjs`), which recognises regex literals;
65
+ * only the `#`-comment shell/YAML surfaces (`.husky/pre-commit`,
66
+ * `.gitlab-ci.yml`) still use the small local walker, whose flat
67
+ * quote-tracking ceiling (no `\"` escape handling, no `${...}` walk) is
68
+ * real but bounded to those two files.
63
69
  *
64
70
  * Usage: check-validator-registration.mjs <repo-root>
65
71
  * Output: ` PASS: …` / ` FAIL: …` lines (two leading spaces), then
@@ -72,8 +78,9 @@
72
78
 
73
79
  import { existsSync, readFileSync } from 'node:fs';
74
80
  import path from 'node:path';
75
- import { pathToFileURL } from 'node:url';
76
81
  import { enumerateRepoFiles } from './enumerate-repo-files.mjs';
82
+ import { isMainModule } from '../is-main-module.mjs';
83
+ import { maskSource } from '../js-mask.mjs';
77
84
 
78
85
  /** Marker line inside a checker's own header — declares deliberate CLI-only status. */
79
86
  export const STANDALONE_MARKER = /^\s*\/\/\s*registration:\s*standalone\b(?:\s+(.*))?$/m;
@@ -113,11 +120,28 @@ function commentStyleForSurface(rel) {
113
120
  * truncating the line early. See the header NAMED CEILING for what this
114
121
  * quote-tracking deliberately does not attempt.
115
122
  *
123
+ * The JS style (`//` + `/* *\/`) is delegated to the shared lexer
124
+ * {@link maskSource} in `keepLiterals` mode — it is the only one of the three
125
+ * RUN_SURFACES that can contain a REGEX LITERAL, and a stripper without a
126
+ * regex branch reads `/\/*$/` as a block-comment opener and swallows every
127
+ * registration below it (#1388, see the header). `maskSource` blanks comment
128
+ * bytes to SPACES rather than deleting them, which is strictly safer for the
129
+ * substring match below: deletion could splice two fragments into a basename
130
+ * that was never written. The `#` style stays local — `maskSource` models
131
+ * JavaScript, not sh/YAML.
132
+ *
116
133
  * @param {string} text
117
134
  * @param {{lineComment: string, blockComment: boolean, quoteChars: string[]}} style
118
- * @returns {string}
135
+ * @returns {string} TWO comment models behind one signature, and they differ in
136
+ * shape: the JS style (`//` + `/* *\/`, delegated to {@link maskSource})
137
+ * returns a string of the SAME LENGTH as `text`, with comment bytes blanked to
138
+ * spaces; the `#` style (sh/YAML, the local walker) returns a SHORTER string,
139
+ * with the comment text deleted to end-of-line (the newline itself is kept).
140
+ * Only the substring match below is common to both — do not rely on offsets
141
+ * surviving the `#` style.
119
142
  */
120
143
  export function stripComments(text, { lineComment, blockComment, quoteChars }) {
144
+ if (lineComment === '//' && blockComment) return maskSource(text, { keepLiterals: true });
121
145
  let out = '';
122
146
  let i = 0;
123
147
  let inQuote = null;
@@ -243,7 +267,7 @@ export function runCheckValidatorRegistration(repoRoot) {
243
267
  return fail > 0 ? 1 : 0;
244
268
  }
245
269
 
246
- const isMain = import.meta.url === pathToFileURL(process.argv[1] || '').href;
270
+ const isMain =isMainModule(import.meta.url);
247
271
  if (isMain) {
248
272
  const root = process.argv[2];
249
273
  if (!root) {
@@ -193,8 +193,8 @@
193
193
 
194
194
  import { existsSync, readFileSync, readdirSync } from 'node:fs';
195
195
  import path from 'node:path';
196
- import { pathToFileURL } from 'node:url';
197
196
  import { SHELL_LANGS, forEachLine } from './markdown-fences.mjs';
197
+ import { isMainModule } from '../is-main-module.mjs';
198
198
 
199
199
  /** Directories whose content is scanned. Root-level `*.md` is added separately. */
200
200
  const SCAN_DIRS = Object.freeze([
@@ -906,7 +906,7 @@ export function runCheckVcsRepoFlag(pluginRoot) {
906
906
  return 0;
907
907
  }
908
908
 
909
- const isMain = import.meta.url === pathToFileURL(process.argv[1] || '').href;
909
+ const isMain =isMainModule(import.meta.url);
910
910
  if (isMain) {
911
911
  const argv = process.argv.slice(2);
912
912
  const flags = new Set(argv.filter((a) => a.startsWith('--')));