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
@@ -37,12 +37,16 @@
37
37
  * mistaken import away from filing issues nobody asked for.
38
38
  *
39
39
  * Exports:
40
- * UX_GRILL_LABELS, buildIssueTitle, buildIssueBody, reconcileFindings
40
+ * UX_GRILL_LABELS, buildIssueTitle, buildUxGrillIssueBody, reconcileFindings
41
+ * buildIssueBody — @deprecated alias of buildUxGrillIssueBody (5.2.0 name),
42
+ * removed in 6.0.0
41
43
  */
42
44
 
43
45
  import {
44
46
  createFinding as defaultCreateFinding,
47
+ fingerprintSentinelLine,
45
48
  listExistingFindings as defaultListExisting,
49
+ neutralizeFingerprintSentinel,
46
50
  triageDecision,
47
51
  updateFinding as defaultUpdateFinding,
48
52
  } from '../test-runner/issue-reconcile.mjs';
@@ -69,31 +73,22 @@ const TITLE_MAX_LENGTH = 100;
69
73
  const AUTO_SEVERITIES = Object.freeze(['critical', 'high']);
70
74
 
71
75
  /**
72
- * Neutralise a fingerprint-sentinel literal in free text (#388 SEC-IR-MED-1).
76
+ * Neutralise fingerprint-sentinel literals (#388 SEC-IR-MED-1) and strip the
77
+ * characters `createFinding` rejects at the argv boundary, so a multi-line page
78
+ * title in a finding message cannot turn into a VALIDATION failure for the whole
79
+ * reconcile pass.
73
80
  *
74
- * `issue-reconcile.mjs` applies this to `recommendation` only, inside
75
- * `reconcileFinding` — a body built HERE and handed to `createFinding` is not
76
- * sanitised by anything, so a finding `message` echoing page text could forge
77
- * the authoritative `**Fingerprint:**` line and make the next run's
81
+ * The neutralisation is the SHARED one from `issue-reconcile.mjs` (#1339 P6): a
82
+ * body built HERE and handed to `createFinding` is not sanitised by anything
83
+ * else, so a finding `message` echoing page text could otherwise forge the
84
+ * authoritative `**Fingerprint:**` line and make the next run's
78
85
  * `triageDecision` dedup against the wrong issue.
79
86
  *
80
87
  * @param {unknown} text
81
88
  * @returns {string}
82
89
  */
83
- function sanitizeSentinel(text) {
84
- return String(text ?? '').replace(/\*\*Fingerprint:\*\*/gi, '__Fingerprint__');
85
- }
86
-
87
- /**
88
- * Strip the characters `createFinding` rejects at the argv boundary, so a
89
- * multi-line page title in a finding message cannot turn into a VALIDATION
90
- * failure for the whole reconcile pass.
91
- *
92
- * @param {unknown} text
93
- * @returns {string}
94
- */
95
90
  function oneLine(text) {
96
- return sanitizeSentinel(text).replace(/[\n\r\0]+/g, ' ').trim();
91
+ return neutralizeFingerprintSentinel(text).replace(/[\n\r\0]+/g, ' ').trim();
97
92
  }
98
93
 
99
94
  /**
@@ -144,7 +139,7 @@ export function buildIssueTitle(finding) {
144
139
  * @param {string} [context.rubricHash]
145
140
  * @returns {string}
146
141
  */
147
- export function buildIssueBody(finding, { runId, rubricHash } = {}) {
142
+ export function buildUxGrillIssueBody(finding, { runId, rubricHash } = {}) {
148
143
  const evidence = finding?.evidence ?? {};
149
144
  const evidenceLines = Object.entries(evidence)
150
145
  .filter(([, value]) => typeof value === 'string' || Array.isArray(value))
@@ -153,7 +148,7 @@ export function buildIssueBody(finding, { runId, rubricHash } = {}) {
153
148
  return [
154
149
  oneLine(finding?.message) || `${oneLine(finding?.checkId)} at ${oneLine(finding?.locator)}`,
155
150
  '',
156
- `**Fingerprint:** \`${oneLine(finding?.fingerprint)}\``,
151
+ fingerprintSentinelLine(oneLine(finding?.fingerprint)),
157
152
  `**Severity:** ${oneLine(finding?.severity)}`,
158
153
  `**Check:** ${oneLine(finding?.checkId)}`,
159
154
  `**Locator:** \`${oneLine(finding?.locator)}\``,
@@ -168,6 +163,37 @@ export function buildIssueBody(finding, { runId, rubricHash } = {}) {
168
163
  .join('\n');
169
164
  }
170
165
 
166
+ /** One stderr line per process for the deprecated alias below, not one per call. */
167
+ let buildIssueBodyDeprecationWarned = false;
168
+
169
+ /**
170
+ * Deprecated alias of {@link buildUxGrillIssueBody}, shipped under this name in
171
+ * 5.2.0.
172
+ *
173
+ * @deprecated since 5.3.0 — use buildUxGrillIssueBody; removed in 6.0.0.
174
+ * Kept one minor cycle (`.claude/rules/development.md` § Package Lifecycle &
175
+ * Versioning) because `package.json` carries no `exports` map: every packed
176
+ * `scripts/lib/` export is a public deep-import, and a consumer calling this
177
+ * name would otherwise get a runtime TypeError, not a compile error. It
178
+ * delegates to the LOCAL ux-grill body builder — never to the unrelated
179
+ * `buildIssueBody(finding, fp)` of `test-runner/issue-reconcile.mjs`, whose
180
+ * finding shape and second argument differ.
181
+ *
182
+ * @param {object} finding - a `makeFinding` record
183
+ * @param {object} [context] - same as {@link buildUxGrillIssueBody}
184
+ * @returns {string}
185
+ */
186
+ export function buildIssueBody(finding, context) {
187
+ if (!buildIssueBodyDeprecationWarned) {
188
+ buildIssueBodyDeprecationWarned = true;
189
+ console.warn(
190
+ '[deprecated] buildIssueBody from scripts/lib/ux-grill/reconcile.mjs is deprecated since 5.3.0 ' +
191
+ 'and will be removed in 6.0.0 — use buildUxGrillIssueBody instead.',
192
+ );
193
+ }
194
+ return buildUxGrillIssueBody(finding, context);
195
+ }
196
+
171
197
  /**
172
198
  * Collect every string in a finding's `evidence` that looks like an absolute
173
199
  * host path (POSIX `/…` or Windows `C:\…`).
@@ -296,7 +322,7 @@ export async function reconcileFindings({
296
322
  }
297
323
 
298
324
  const title = buildIssueTitle(finding);
299
- const body = buildIssueBody(finding, { runId, rubricHash });
325
+ const body = buildUxGrillIssueBody(finding, { runId, rubricHash });
300
326
  const decision = triageDecision({ fingerprint: finding?.fingerprint, title }, candidates);
301
327
 
302
328
  if (decision.action === 'ignore') {
@@ -11,16 +11,16 @@
11
11
  * inode, or byte-identical).
12
12
  * B. root `plugin.json` — the agent-plugins.org 1.0.0 manifest; its `version`
13
13
  * must equal `package.json`'s and the two existing plugin manifests'.
14
- * C. `.agents/skills/<name>/SKILL.md` — one spec-legal mirror per source
15
- * skill, no orphans, agentskills.io field list only, description ≤ 1024.
14
+ * C. `.agents/skills/<name>/SKILL.md` — one spec-legal mirror per skill or
15
+ * command, no orphans, agentskills.io field list only, description ≤ 1024.
16
16
  *
17
17
  * INDEPENDENCE (load-bearing). This checker deliberately imports NOTHING from
18
18
  * `scripts/generate-agents-skills.mjs` and re-derives the spec field list, the
19
19
  * 1024 cap and the alias rule from the spec, not from the generator. A
20
20
  * generator-vs-generator check only proves the generator is self-consistent —
21
21
  * that is how the Cursor `argument-hint` defect survived. The oracles here are
22
- * the repository's own filesystem (`skills/`, `CLAUDE.md`, `package.json`) and
23
- * the published field list.
22
+ * the repository's own filesystem (`skills/`, `commands/`, `CLAUDE.md`,
23
+ * `package.json`) and the published field list.
24
24
  *
25
25
  * Usage: node scripts/lib/validate/check-agents-skills.mjs <plugin-root>
26
26
  * Exit codes: 0 — all checks passed · 1 — one or more failures.
@@ -195,6 +195,7 @@ console.log('');
195
195
  console.log('--- Check C: .agents/skills/ portable mirror ---');
196
196
  {
197
197
  const skillsDir = join(pluginRoot, 'skills');
198
+ const commandsDir = join(pluginRoot, 'commands');
198
199
  const mirrorDir = join(pluginRoot, '.agents', 'skills');
199
200
 
200
201
  const sourceSkills = existsSync(skillsDir) && statSync(skillsDir).isDirectory()
@@ -204,12 +205,21 @@ console.log('--- Check C: .agents/skills/ portable mirror ---');
204
205
  .filter((n) => existsSync(join(skillsDir, n, 'SKILL.md')))
205
206
  .sort()
206
207
  : [];
208
+ const sources = new Map(sourceSkills.map((name) => [name, `skills/${name}/SKILL.md`]));
209
+ if (existsSync(commandsDir) && statSync(commandsDir).isDirectory()) {
210
+ for (const entry of readdirSync(commandsDir, { withFileTypes: true })) {
211
+ if (!entry.isFile() || !entry.name.endsWith('.md') || /^[._]/.test(entry.name)) continue;
212
+ // Commands own public names when an internal skill has the same name.
213
+ sources.set(entry.name.slice(0, -3), `commands/${entry.name}`);
214
+ }
215
+ }
216
+ const sourceNames = [...sources.keys()].sort();
207
217
 
208
218
  if (!existsSync(mirrorDir)) {
209
- if (sourceSkills.length === 0) {
210
- pass('no skills/ and no .agents/skills/ — nothing to mirror');
219
+ if (sourceNames.length === 0) {
220
+ pass('no skill or command sources and no .agents/skills/ — nothing to mirror');
211
221
  } else {
212
- fail(`.agents/skills/ is missing but ${sourceSkills.length} source skill(s) exist `
222
+ fail(`.agents/skills/ is missing but ${sourceNames.length} source entry(s) exist `
213
223
  + '(remedy: node scripts/generate-agents-skills.mjs)');
214
224
  }
215
225
  } else {
@@ -218,12 +228,12 @@ console.log('--- Check C: .agents/skills/ portable mirror ---');
218
228
  .map((e) => e.name)
219
229
  .sort();
220
230
 
221
- const missing = sourceSkills.filter((n) => !mirrored.includes(n));
222
- const orphans = mirrored.filter((n) => !sourceSkills.includes(n));
223
- if (missing.length === 0) pass(`every source skill has a mirror (${sourceSkills.length})`);
224
- else fail(`${missing.length} source skill(s) have no mirror: ${missing.join(', ')}`);
231
+ const missing = sourceNames.filter((n) => !mirrored.includes(n));
232
+ const orphans = mirrored.filter((n) => !sources.has(n));
233
+ if (missing.length === 0) pass(`every source entry has a mirror (${sourceNames.length})`);
234
+ else fail(`${missing.length} source entry(s) have no mirror: ${missing.join(', ')}`);
225
235
  if (orphans.length === 0) pass('no orphan mirrors');
226
- else fail(`${orphans.length} orphan mirror(s) with no source skill: ${orphans.join(', ')}`);
236
+ else fail(`${orphans.length} orphan mirror(s) with no skill or command source: ${orphans.join(', ')}`);
227
237
 
228
238
  let fmFail = 0;
229
239
  let keyFail = 0;
@@ -303,8 +313,9 @@ console.log('--- Check C: .agents/skills/ portable mirror ---');
303
313
  }
304
314
  }
305
315
 
306
- if (!body.includes(`skills/${name}/SKILL.md`)) {
307
- fail(`.agents/skills/${name}/SKILL.md body does not cite the canonical skills/${name}/SKILL.md `
316
+ const canonical = sources.get(name);
317
+ if (canonical && !body.includes(canonical)) {
318
+ fail(`.agents/skills/${name}/SKILL.md body does not cite the canonical ${canonical} `
308
319
  + '— the mirror is a pointer, and a pointer with no target is dead weight');
309
320
  pointerFail += 1;
310
321
  }
@@ -314,7 +325,7 @@ console.log('--- Check C: .agents/skills/ portable mirror ---');
314
325
  if (keyFail === 0 && mirrored.length > 0) pass('all mirrors use only agentskills.io spec fields');
315
326
  if (descFail === 0 && mirrored.length > 0) pass(`all mirror descriptions are non-empty and ≤ ${DESCRIPTION_MAX} chars`);
316
327
  if (sizeFail === 0 && mirrored.length > 0) pass(`all mirrors ≤ ${MIRROR_MAX_BYTES} bytes (total ${totalBytes} bytes)`);
317
- if (pointerFail === 0 && mirrored.length > 0) pass('all mirrors cite their canonical SKILL.md');
328
+ if (pointerFail === 0 && mirrored.length > 0) pass('all mirrors cite their canonical source');
318
329
  }
319
330
  }
320
331
 
@@ -46,8 +46,8 @@
46
46
  import { execFileSync } from 'node:child_process';
47
47
  import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
48
48
  import path from 'node:path';
49
- import { pathToFileURL } from 'node:url';
50
49
  import { HISTORICAL_GUARD_BANNER } from '../historical-guard.mjs';
50
+ import { isMainModule } from '../is-main-module.mjs';
51
51
 
52
52
  /** Directories whose markdown quotes the banner as coordinator-facing prose. */
53
53
  const SCAN_DIRS = Object.freeze(['skills', 'commands']);
@@ -365,7 +365,7 @@ export function runCheckBannerParity(pluginRoot) {
365
365
  return inspection.toolError ? 2 : 1;
366
366
  }
367
367
 
368
- const isMain = import.meta.url === pathToFileURL(process.argv[1] || '').href;
368
+ const isMain =isMainModule(import.meta.url);
369
369
  if (isMain) {
370
370
  const pluginRoot = process.argv[2];
371
371
  if (!pluginRoot) {
@@ -6,11 +6,11 @@
6
6
  import { spawnSync } from 'node:child_process';
7
7
  import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
8
8
  import { join, relative } from 'node:path';
9
- import { pathToFileURL } from 'node:url';
10
9
 
11
10
  import yaml from 'js-yaml';
12
11
 
13
12
  import { CURSOR_TO_CANONICAL_EVENT } from '../cursor-hook-bridge.mjs';
13
+ import { isMainModule } from '../is-main-module.mjs';
14
14
 
15
15
  /** agentskills.io caps a skill/command description at 1024 characters. */
16
16
  const DESCRIPTION_MAX = 1024;
@@ -177,6 +177,7 @@ function runCli(pluginRoot) {
177
177
  } else {
178
178
  const detail = ((result.stdout ?? '') + (result.stderr ?? '')).trim();
179
179
  fail(`Cursor adapter wrappers are stale${detail ? `: ${detail}` : ''}`);
180
+ console.log(' Remedy: node scripts/generate-cursor-adapter.mjs');
180
181
  }
181
182
  }
182
183
 
@@ -259,6 +260,6 @@ function runCli(pluginRoot) {
259
260
  }
260
261
 
261
262
  // Direct invocation only — importing this module must not run the CLI.
262
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
263
+ if (isMainModule(import.meta.url)) {
263
264
  runCli(process.argv[2]);
264
265
  }
@@ -22,12 +22,12 @@
22
22
  // or process.exit — the isMain guard at the bottom is the only side-effecting
23
23
  // path. The Quality wave imports runCheckDeadBridge directly for unit tests.
24
24
 
25
- import { pathToFileURL } from 'node:url';
26
25
  import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
27
26
  import path from 'node:path';
28
27
  import { DETECTORS } from './dead-bridge-detectors.mjs';
29
28
  import * as corpus from './dead-bridge-corpus.mjs';
30
29
  import { listRepoFiles } from './repo-files.mjs';
30
+ import { isMainModule } from '../is-main-module.mjs';
31
31
 
32
32
  const DEFAULT_EXTS = ['.mjs', '.md'];
33
33
 
@@ -191,7 +191,7 @@ export function runCheckDeadBridge(pluginRoot) {
191
191
 
192
192
  // CLI entry — only when executed directly, never on import (keeps the exports
193
193
  // safe to import from tests without triggering process.exit).
194
- const isMain = import.meta.url === pathToFileURL(process.argv[1] || '').href;
194
+ const isMain =isMainModule(import.meta.url);
195
195
  if (isMain) {
196
196
  const pluginRoot = process.argv[2];
197
197
  if (!pluginRoot) {
@@ -65,9 +65,9 @@
65
65
  import { readFileSync } from 'node:fs';
66
66
  import path from 'node:path';
67
67
  import { spawnSync } from 'node:child_process';
68
- import { pathToFileURL } from 'node:url';
69
68
  import { listRepoFiles } from './repo-files.mjs';
70
69
  import { SHELL_LANGS, forEachLine } from './markdown-fences.mjs';
70
+ import { isMainModule } from '../is-main-module.mjs';
71
71
 
72
72
  /** Directories walked for documentation. Mirrors check-vcs-repo-flag.mjs. */
73
73
  const SCAN_DIRS = Object.freeze([
@@ -461,7 +461,7 @@ export function runCheckDocCliCommands(pluginRoot) {
461
461
  return 0;
462
462
  }
463
463
 
464
- const isMain = import.meta.url === pathToFileURL(process.argv[1] || '').href;
464
+ const isMain =isMainModule(import.meta.url);
465
465
  if (isMain) {
466
466
  const args = process.argv.slice(2).filter((arg) => arg !== '--json');
467
467
  const root = path.resolve(args[0] || process.cwd());
@@ -0,0 +1,329 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-entry-guard.mjs — census of SYMLINK-FRAGILE ESM entry guards.
4
+ *
5
+ * ## The defect class (#1371)
6
+ *
7
+ * A module that runs `main()` only when invoked as a CLI compares "the module I
8
+ * am" against "the script Node was told to run". The two are not the same kind
9
+ * of path: Node resolves `import.meta.url` to the **realpath**, while
10
+ * `process.argv[1]` is the path **as typed**. With a symlink anywhere in the
11
+ * invocation path — a `node_modules/.bin/` shim, a `~/bin` convenience link, a
12
+ * plugin directory symlinked into `~/.claude/plugins/`, or `/tmp` itself (a
13
+ * symlink to `/private/tmp` on macOS) — the comparison is false.
14
+ *
15
+ * The failure mode is the worst shape a defect can have: `main()` never runs,
16
+ * nothing is printed, and the process exits 0. Every caller reads that as
17
+ * success.
18
+ *
19
+ * The fix is `isMainModule()` from `scripts/lib/is-main-module.mjs`, which
20
+ * realpaths BOTH sides. This check is the recurrence guard: it fails the build
21
+ * when a new hand-written comparison appears.
22
+ *
23
+ * ## Oracle
24
+ *
25
+ * Over every tracked `.mjs` under `scripts/` + `hooks/` (via `git ls-files`,
26
+ * so an untracked scratch file is never a finding):
27
+ *
28
+ * 1. strip comments (line comments, trailing `//`, and block comments),
29
+ * 2. split the remainder into statement fragments on `;{}`,
30
+ * 3. inside each fragment mentioning `process.argv[1]`, apply two oracles:
31
+ * a. **comparison form** — take every `===` / `!==` comparison and its two
32
+ * operands (bounded to the adjacent `&&` / `||` segment) and FAIL when
33
+ * one operand mentions `process.argv[1]` and the other mentions
34
+ * `import.meta.url` or `__filename`;
35
+ * b. **bare-basename form** — FAIL on `process.argv[1].endsWith('x.mjs')`
36
+ * (plus the `?.`, `String(...)` and `(argv[1] || '')` receiver
37
+ * variants) against a `.mjs`/`.js`/`.cjs` string literal. This form has
38
+ * no comparison operator at all, so oracle (a) never saw it.
39
+ * 4. In both cases, a fragment wrapping either side in a `realpath*` call is
40
+ * the safe idiom and must not be flagged.
41
+ *
42
+ * Both oracles are anchored on a SYNTAX shape, which is what keeps prose out of
43
+ * the census: `scripts/validate-plugin.mjs`,
44
+ * `scripts/lib/vault-consolidate-fs.mjs`, `hooks/post-edit-import-probe.mjs`
45
+ * and this file's own header all MENTION a broken idiom inside a comment and
46
+ * are excluded by step 1 without needing an allowlist entry.
47
+ * `scripts/lib/baseline-archetypes.mjs` (`await import(process.argv[1])`) is a
48
+ * deliberate cross-process trick that is neither a comparison nor a basename
49
+ * test — out of scope for this check.
50
+ *
51
+ * `scripts/lib/ecosystem-wizard.mjs` (bare `argv[1].endsWith(...)`) and
52
+ * `scripts/lib/fetch-baseline.mjs` (a hybrid `===`-plus-`endsWith` guard, which
53
+ * needed the ALLOWLIST below) were the two hand-written guards this check was
54
+ * built to catch (#1371); both were fixed to `isMainModule()` in #1378. Oracle
55
+ * (b) was added afterwards because reverting the FIRST of them was caught by
56
+ * nothing: the bare form was outside oracle (a) by construction, and neither
57
+ * file's symlink smoke test reproduced its own regression (a same-named file
58
+ * behind a symlinked DIRECTORY still ends with the basename). The allowlist is
59
+ * empty — kept as a frozen mechanism (see its docblock) rather than deleted,
60
+ * since the next hand-written exception is exactly what would re-populate it.
61
+ *
62
+ * `scripts/lib/is-main-module.mjs` itself is excluded: it is the fix, and its
63
+ * docblock quotes every variant it replaces.
64
+ *
65
+ * ## Mode: BLOCKING
66
+ *
67
+ * Unlike the WARN-only censuses in validate-plugin (`check-unwired-features`,
68
+ * `check-learning-provenance`, `check-vcs-repo-flag`), this one blocks. It can
69
+ * do so honestly because the backlog was drained to zero in the same change
70
+ * that introduced it — a blocking gate is only dishonest when it is red on
71
+ * arrival for work nobody intends to do.
72
+ */
73
+
74
+ import { execFileSync } from 'node:child_process';
75
+ import { readFileSync } from 'node:fs';
76
+ import path from 'node:path';
77
+
78
+ import { isMainModule } from '../is-main-module.mjs';
79
+ import { maskSource } from '../js-mask.mjs';
80
+
81
+ /**
82
+ * Files whose `process.argv[1]` comparison the oracle flags but which are NOT
83
+ * the defect class. Every entry carries the reason it is exempt; a stale entry
84
+ * (one the oracle no longer flags) is itself reported, so this list cannot rot
85
+ * into a silent suppression.
86
+ *
87
+ * Empty since #1378: the one entry this list ever carried
88
+ * (`scripts/lib/fetch-baseline.mjs`, whose hybrid guard's `endsWith(basename)`
89
+ * fallback made it non-fragile) was removed when that file was fixed to
90
+ * `isMainModule()` instead of being kept as a hand-verified exception. The
91
+ * mechanism is retained rather than deleted: the stale-entry check below only
92
+ * fires for an entry that is PRESENT and no longer flagged, so an empty object
93
+ * never trips it, and a future hand-written exception can still be recorded
94
+ * here.
95
+ *
96
+ * @type {Record<string, string>}
97
+ */
98
+ const ALLOWLIST = {};
99
+
100
+ /** The one module allowed to contain every broken variant: it documents them. */
101
+ const SELF_EXEMPT = 'scripts/lib/is-main-module.mjs';
102
+
103
+ /**
104
+ * Remove comments while preserving character offsets (so line numbers survive).
105
+ *
106
+ * A thin wrapper over the shared lexer `maskSource` in `keepLiterals` mode:
107
+ * string, template and regex literals are skipped INTACT. That is load-bearing
108
+ * twice over — the V1 idiom \`file://${process.argv[1]}\` carries a `//` that a
109
+ * naive scanner reads as a line comment, and a regex literal must be recognised
110
+ * as one (#1383: a `/` the lexer read as a division let the regex body's `/*`
111
+ * open a block comment that blanked the guard below it — a fail-open miss, for
112
+ * a quote-bearing regex, a `/[/*]/` character class, and a regex after a keyword
113
+ * alike).
114
+ *
115
+ * @param {string} src module source
116
+ * @returns {string} same length, comment bytes replaced by spaces
117
+ */
118
+ export function stripComments(src) {
119
+ return maskSource(src, { keepLiterals: true });
120
+ }
121
+
122
+ /**
123
+ * A `.endsWith('<something>.mjs')` call with a module-basename-shaped literal.
124
+ * Scanned rather than matched in one shot so the RECEIVER can be judged from the
125
+ * text preceding each hit — a `.endsWith()` on anything other than
126
+ * `process.argv[1]` is not this defect class.
127
+ */
128
+ const ENDS_WITH_LITERAL = /endsWith\(\s*(['"`])([^'"`]*)\1\s*\)/g;
129
+
130
+ /**
131
+ * The receiver chain, whitespace-stripped, that makes an `.endsWith()` call a
132
+ * test on the invocation path. Deliberately unanchored at the START so an outer
133
+ * wrapper (`String(...)`, `path.basename(...)`) still matches; anchored at the
134
+ * END so only the call immediately downstream of `process.argv[1]` counts.
135
+ */
136
+ const ARGV_RECEIVER_TAIL = /process\.argv\[1\](?:\|\|''|\|\|"")?\)*\??\.$/;
137
+
138
+ /**
139
+ * Findings for one module body.
140
+ *
141
+ * `kind` distinguishes the two oracles: `comparison` is the
142
+ * `argv[1] === import.meta.url` family, `basename` the bare
143
+ * `argv[1].endsWith('x.mjs')` family. Both are symlink-fragile; only the remedy
144
+ * wording differs.
145
+ *
146
+ * @param {string} src module source
147
+ * @returns {Array<{line: number, text: string, kind: 'comparison'|'basename'}>} one per fragile guard
148
+ */
149
+ export function findFragileGuards(src) {
150
+ // Template substitutions are rewritten `${x}` → `$(x)` BEFORE the fragment
151
+ // split: the V1 idiom `import.meta.url === \`file://${process.argv[1]}\`` puts
152
+ // braces INSIDE the comparison, so splitting on `{}` would tear the statement
153
+ // in two and the most common variant of the defect class (12 of 50 files at
154
+ // ca214376) would never be seen. Length is preserved so line numbers survive.
155
+ const stripped = stripComments(src).replace(/\$\{([^{}]*)\}/g, (_m, inner) => `$(${inner})`);
156
+ const findings = [];
157
+ let cursor = 0;
158
+ for (const fragment of stripped.split(/[;{}]/)) {
159
+ const fragStart = cursor;
160
+ cursor += fragment.length + 1;
161
+ if (!fragment.includes('process.argv[1]')) continue;
162
+
163
+ /** @type {(offset: number) => number} 1-based line of an offset in `fragment` */
164
+ const lineAt = (offset) =>
165
+ stripped.slice(0, Math.min(fragStart + offset, stripped.length)).split('\n').length;
166
+
167
+ if (!/import\.meta\.url|__filename/.test(fragment)) {
168
+ // Oracle (b): no self-path mention at all, so the comparison oracle below
169
+ // cannot see this fragment. The bare basename test lives exactly here.
170
+ if (!/realpath/i.test(fragment)) {
171
+ ENDS_WITH_LITERAL.lastIndex = 0;
172
+ let m;
173
+ while ((m = ENDS_WITH_LITERAL.exec(fragment)) !== null) {
174
+ if (!/\.(?:mjs|js|cjs)$/.test(m[2])) continue;
175
+ const receiver = fragment.slice(0, m.index).replace(/\s+/g, '');
176
+ if (!ARGV_RECEIVER_TAIL.test(receiver)) continue;
177
+ findings.push({
178
+ line: lineAt(m.index),
179
+ text: `process.argv[1]…${m[0]}`,
180
+ kind: 'basename',
181
+ });
182
+ break; // one finding per statement is enough to act on
183
+ }
184
+ }
185
+ continue;
186
+ }
187
+
188
+ const parts = fragment.split(/===|!==/);
189
+ for (let k = 0; k < parts.length - 1; k++) {
190
+ const left = parts[k].split(/&&|\|\|/).pop() ?? '';
191
+ const right = parts[k + 1].split(/&&|\|\|/)[0] ?? '';
192
+ const mentionsArgv = /process\.argv\[1\]/.test(left) || /process\.argv\[1\]/.test(right);
193
+ const mentionsSelf =
194
+ /import\.meta\.url|__filename/.test(left) || /import\.meta\.url|__filename/.test(right);
195
+ if (!mentionsArgv || !mentionsSelf) continue;
196
+ if (/realpath/i.test(left) || /realpath/i.test(right)) continue; // the safe idiom
197
+ const line = lineAt(parts.slice(0, k + 1).join('===').length || 0);
198
+ findings.push({
199
+ line,
200
+ text: `${left.trim()} === ${right.trim()}`.replace(/\s+/g, ' '),
201
+ kind: 'comparison',
202
+ });
203
+ break; // one finding per statement is enough to act on
204
+ }
205
+ }
206
+ return findings;
207
+ }
208
+
209
+ /**
210
+ * Tracked `.mjs` files under `scripts/` and `hooks/`.
211
+ *
212
+ * @param {string} repoRoot absolute repo root
213
+ * @returns {string[]} repo-relative POSIX paths
214
+ */
215
+ function trackedModules(repoRoot) {
216
+ // Pathspecs are DIRECTORIES, not `scripts/**/*.mjs`: git's wildmatch requires
217
+ // `**` to consume at least one path component, so a `scripts/**/*.mjs` spec
218
+ // silently drops every top-level `scripts/*.mjs` AND all of `hooks/*.mjs` —
219
+ // measured 2026-09-16: 61 files matched vs 176 actually tracked. The extension
220
+ // filter therefore lives here, where it cannot be wrong about that.
221
+ //
222
+ // Re-measured 2026-09-17 (the 61/176 pair no longer reproduces at any scope I
223
+ // could reconstruct, so it is kept as-dated rather than edited):
224
+ // `git ls-files -- 'scripts/**/*.mjs' 'hooks/**/*.mjs' | wc -l` → 422 against
225
+ // `git ls-files -- scripts hooks | grep -c '\.mjs$'` → 508. Direction of the
226
+ // trap is unchanged; only the magnitude moved.
227
+ const out = execFileSync('git', ['ls-files', '--', 'scripts', 'hooks'], {
228
+ cwd: repoRoot,
229
+ encoding: 'utf8',
230
+ maxBuffer: 32 * 1024 * 1024,
231
+ });
232
+ return out
233
+ .split('\n')
234
+ .map((l) => l.trim())
235
+ .filter((l) => l.endsWith('.mjs'));
236
+ }
237
+
238
+ /**
239
+ * Run the census.
240
+ *
241
+ * @param {string} repoRoot absolute repo root
242
+ * @returns {Promise<number>} 0 clean · 1 findings · 2 tool error
243
+ */
244
+ export async function runCheckEntryGuard(repoRoot) {
245
+ let files;
246
+ try {
247
+ files = trackedModules(repoRoot);
248
+ } catch (err) {
249
+ console.log(` FAIL: cannot enumerate tracked modules: ${err.message}`);
250
+ console.log('');
251
+ console.log('Results: 0 passed, 1 failed');
252
+ return 2;
253
+ }
254
+
255
+ let failed = 0;
256
+ const flagged = new Set();
257
+ for (const rel of files) {
258
+ if (rel === SELF_EXEMPT) continue;
259
+ let body;
260
+ try {
261
+ body = readFileSync(path.join(repoRoot, rel), 'utf8');
262
+ } catch {
263
+ continue; // deleted between ls-files and read — not this check's business
264
+ }
265
+ const findings = findFragileGuards(body);
266
+ if (findings.length === 0) continue;
267
+ flagged.add(rel);
268
+ if (rel in ALLOWLIST) continue;
269
+ for (const f of findings) {
270
+ const why =
271
+ f.kind === 'basename'
272
+ ? 'a basename test answers "was a file with this name run?", never "was THIS ' +
273
+ 'module run" — it is true for any same-named file (a sibling copy, a ' +
274
+ 'vendored duplicate, a fixture) and resolves no path at all, so main() ' +
275
+ 'fires or stays silent for reasons unrelated to identity'
276
+ : '`import.meta.url` is the realpath, `process.argv[1]` is the path as typed, ' +
277
+ 'so the guard is false under any symlinked invocation and main() silently ' +
278
+ 'never runs (exit 0, no output)';
279
+ console.log(
280
+ ` FAIL: ${rel}:${f.line} — symlink-fragile entry guard \`${f.text}\`: ${why}. ` +
281
+ 'Use `isMainModule(import.meta.url)` from scripts/lib/is-main-module.mjs',
282
+ );
283
+ failed++;
284
+ }
285
+ }
286
+
287
+ const censused = new Set(files);
288
+ for (const [rel, reason] of Object.entries(ALLOWLIST)) {
289
+ if (flagged.has(rel)) continue;
290
+ // Not present at all (a fixture repo, a fork that deleted the file) is not
291
+ // rot — only an entry whose file IS censused and no longer flagged is.
292
+ if (!censused.has(rel)) continue;
293
+ console.log(
294
+ ` FAIL: ${rel} — stale allowlist entry: the oracle no longer flags this file, ` +
295
+ `so the exemption (“${reason}”) suppresses nothing. Remove it.`,
296
+ );
297
+ failed++;
298
+ }
299
+
300
+ if (failed === 0) {
301
+ console.log(
302
+ ` PASS: censused ${files.length} tracked module(s) under scripts/ + hooks/ — ` +
303
+ `0 symlink-fragile entry guard(s), ${Object.keys(ALLOWLIST).length} allowlisted`,
304
+ );
305
+ }
306
+ console.log('');
307
+ console.log(`Results: ${failed === 0 ? 1 : 0} passed, ${failed} failed`);
308
+ return failed === 0 ? 0 : 1;
309
+ }
310
+
311
+ if (isMainModule(import.meta.url)) {
312
+ const argv = process.argv.slice(2);
313
+ const usage =
314
+ 'Usage: check-entry-guard.mjs [<repo-root>]\n' +
315
+ 'Exit: 0 clean · 1 symlink-fragile entry guard(s) found · 2 tool error';
316
+ if (argv.includes('--help')) {
317
+ console.log(usage);
318
+ process.exitCode = 0;
319
+ } else {
320
+ const positional = argv.filter((a) => !a.startsWith('--'));
321
+ const unknown = argv.filter((a) => a.startsWith('--') && a !== '--help');
322
+ if (unknown.length > 0) {
323
+ console.error(`Unknown flag(s): ${unknown.join(', ')}\n${usage}`);
324
+ process.exitCode = 1;
325
+ } else {
326
+ process.exitCode = await runCheckEntryGuard(path.resolve(positional[0] ?? process.cwd()));
327
+ }
328
+ }
329
+ }
@@ -14,8 +14,8 @@
14
14
  import { spawnSync } from 'node:child_process';
15
15
  import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
16
16
  import path from 'node:path';
17
- import { pathToFileURL } from 'node:url';
18
17
  import { parse } from '@babel/parser';
18
+ import { isMainModule } from '../is-main-module.mjs';
19
19
 
20
20
  const HOOKS_RELATIVE_DIR = 'hooks';
21
21
  const MODULE_RELATIVE_PREFIX = 'scripts/lib';
@@ -1137,7 +1137,7 @@ export function runCheckGuardRequiresParity(pluginRoot) {
1137
1137
  return inspection.toolError ? 2 : 1;
1138
1138
  }
1139
1139
 
1140
- const isMain = import.meta.url === pathToFileURL(process.argv[1] || '').href;
1140
+ const isMain =isMainModule(import.meta.url);
1141
1141
  if (isMain) {
1142
1142
  const pluginRoot = process.argv[2];
1143
1143
  if (!pluginRoot) {