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
@@ -7,6 +7,18 @@
7
7
  * `skills/`; these wrappers keep one maintained body while giving Cursor a
8
8
  * native entry per command/skill (same pattern as `generate-pi-prompts.mjs`).
9
9
  *
10
+ * `.cursor/commands/` is generated from the UNION of two sources: every
11
+ * `commands/*.md`, plus every skill whose frontmatter carries an explicit
12
+ * `user-invocable: true`. The second source exists because the operator-facing
13
+ * slash-command marker moved INTO the skill frontmatter when the command bodies
14
+ * were folded into their same-named `skills/<name>/SKILL.md`. A name present in
15
+ * both sources is a generator ERROR, not a precedence question — two documents
16
+ * claiming one public `/name` is a merge that did not finish.
17
+ *
18
+ * `disable-model-invocation` is propagated from the SOURCE frontmatter and is
19
+ * NOT derived from `user-invocable` — see {@link disablesModelInvocation} for
20
+ * the two grounds and the measurement behind them.
21
+ *
10
22
  * Usage:
11
23
  * node scripts/generate-cursor-adapter.mjs
12
24
  * node scripts/generate-cursor-adapter.mjs --check
@@ -15,6 +27,7 @@
15
27
  import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
16
28
  import path from 'node:path';
17
29
  import { fileURLToPath } from 'node:url';
30
+ import { isUserInvocableValue } from './lib/user-invocable-skills.mjs';
18
31
 
19
32
  const __filename = fileURLToPath(import.meta.url);
20
33
  const SCRIPT_DIR = path.dirname(__filename);
@@ -26,17 +39,22 @@ const CURSOR_SKILLS_DIR = path.join(ROOT, '.cursor', 'skills');
26
39
  const CHECK_ONLY = process.argv.includes('--check');
27
40
  const DESCRIPTION_MAX = 1024;
28
41
 
42
+ /** Memo for {@link skillFlags} — skill name → parsed frontmatter + both markers. */
43
+ const SKILL_FLAGS = new Map();
44
+
29
45
  function isDir(p) {
30
46
  try { return statSync(p).isDirectory(); } catch { return false; }
31
47
  }
32
48
 
33
49
  function commandFiles() {
50
+ if (!existsSync(COMMANDS_DIR)) return [];
34
51
  return readdirSync(COMMANDS_DIR)
35
52
  .filter((name) => name.endsWith('.md'))
36
53
  .sort();
37
54
  }
38
55
 
39
56
  function skillDirs() {
57
+ if (!existsSync(SKILLS_DIR)) return [];
40
58
  return readdirSync(SKILLS_DIR)
41
59
  .filter((name) => isDir(path.join(SKILLS_DIR, name)) && existsSync(path.join(SKILLS_DIR, name, 'SKILL.md')))
42
60
  .sort();
@@ -44,15 +62,30 @@ function skillDirs() {
44
62
 
45
63
  /**
46
64
  * Parse YAML-ish frontmatter including `>` / `|` folded scalars.
65
+ *
66
+ * A UTF-8 BOM before the opening `---`, or CRLF line endings, used to make the
67
+ * two probes below miss the block entirely: the file parsed as "no
68
+ * frontmatter", so every flag in it (`user-invocable`,
69
+ * `disable-model-invocation`) silently disappeared and the skill was demoted
70
+ * out of `.cursor/commands/` with no diagnostic anywhere. Both are normalised
71
+ * away first — the same treatment `parseAgentFrontmatter`
72
+ * (`scripts/lib/agent-frontmatter.mjs`) gives them.
73
+ *
74
+ * NOT replaced by the shared `parseSkillFrontmatter`: that one returns the
75
+ * `__BLOCK_SCALAR__` sentinel for a `description: >`, which is the form every
76
+ * merged skill actually uses — routing through it would emit the sentinel as
77
+ * the wrapper description.
78
+ *
47
79
  * @param {string} content
48
80
  * @returns {Record<string, string>}
49
81
  */
50
82
  function parseFrontmatter(content) {
51
- if (!content.startsWith('---\n')) return {};
52
- const end = content.indexOf('\n---\n', 4);
83
+ const text = (content.charCodeAt(0) === 0xfeff ? content.slice(1) : content).replace(/\r\n?/g, '\n');
84
+ if (!text.startsWith('---\n')) return {};
85
+ const end = text.indexOf('\n---\n', 4);
53
86
  if (end === -1) return {};
54
87
 
55
- const lines = content.slice(4, end).split('\n');
88
+ const lines = text.slice(4, end).split('\n');
56
89
  const fields = {};
57
90
  let i = 0;
58
91
  while (i < lines.length) {
@@ -158,13 +191,155 @@ Cursor has no Skill tool. When the command says to invoke a skill, Read \`skills
158
191
  `;
159
192
  }
160
193
 
161
- function isUserInvocable(value) {
162
- return value === 'true' || value === true;
194
+ /**
195
+ * Skills that declare themselves operator-facing slash commands.
196
+ *
197
+ * The predicate is `isUserInvocableValue` from `scripts/lib/user-invocable-skills.mjs`
198
+ * — the ONE normaliser for this marker, shared with the Pi and Codex generators
199
+ * and with every counter. The private copy this replaced read only a
200
+ * whitespace-trimmed bare `true`, so `True` and `true # note` were demoted HERE
201
+ * while the shared counter listed them as commands; the SKILL.md path is passed
202
+ * so a demotion WARN names the file.
203
+ *
204
+ * @returns {string[]} skill names, sorted
205
+ */
206
+ function userInvocableSkills() {
207
+ return skillDirs().filter((name) => skillFlags(name).userInvocable);
208
+ }
209
+
210
+ /**
211
+ * Both boolean markers of ONE skill, parsed and judged exactly once.
212
+ *
213
+ * Memoised because the predicate is not side-effect free: a truthy-lookalike
214
+ * value (`yes`, `on`, `1`) emits a stderr WARN, so reading the same key from
215
+ * three call sites emitted the same warning three times — measured 2026-09-18
216
+ * on a two-skill fixture: 3 WARN lines for 2 defects. One skill, one reading,
217
+ * one diagnostic.
218
+ *
219
+ * @param {string} skillName
220
+ * @returns {{file: string, fields: Record<string, string>, userInvocable: boolean,
221
+ * sourceDisablesModel: boolean, disablesModel: boolean}}
222
+ */
223
+ function skillFlags(skillName) {
224
+ const cached = SKILL_FLAGS.get(skillName);
225
+ if (cached) return cached;
226
+ const file = path.join(SKILLS_DIR, skillName, 'SKILL.md');
227
+ const fields = parseFrontmatter(readFileSync(file, 'utf8'));
228
+ const userInvocable = isUserInvocableValue(fields['user-invocable'], file, 'user-invocable');
229
+ const sourceDisablesModel = isUserInvocableValue(
230
+ fields['disable-model-invocation'], file, 'disable-model-invocation',
231
+ );
232
+ const flags = {
233
+ file,
234
+ fields,
235
+ userInvocable,
236
+ sourceDisablesModel,
237
+ // See {@link disablesModelInvocation} for the two OR-ed grounds.
238
+ disablesModel: sourceDisablesModel || !userInvocable,
239
+ };
240
+ SKILL_FLAGS.set(skillName, flags);
241
+ return flags;
242
+ }
243
+
244
+ /**
245
+ * A `.cursor/commands/<name>.md` wrapper for a skill that IS the slash command
246
+ * (`user-invocable: true`). Same contract as {@link renderCommand}, pointing at
247
+ * the skill body instead of a command file.
248
+ *
249
+ * @param {string} skillName
250
+ * @returns {string}
251
+ */
252
+ function renderSkillCommand(skillName) {
253
+ const { fields, sourceDisablesModel } = skillFlags(skillName);
254
+ const description = clampDescription(fields.description || `Session Orchestrator skill: ${skillName}`);
255
+ // Same GH#54 rule as renderCommand: `argument-hint` ALWAYS goes through
256
+ // yamlQuote(), because its canonical authored form (`[mode] [--flag]`) is a
257
+ // YAML flow sequence when emitted bare.
258
+ // The command wrapper carries the SOURCE value only — never the library-skill
259
+ // ground of {@link disablesModelInvocation}, which cannot apply here: this
260
+ // surface exists BECAUSE the skill is `user-invocable: true`. Propagated so an
261
+ // operator-only command stays operator-only on whichever of the two surfaces
262
+ // Cursor reads the policy from (same mapping the Codex adapter makes onto
263
+ // `policy.allow_implicit_invocation`, `scripts/generate-codex-skills.mjs`).
264
+ const frontmatter = [
265
+ '---',
266
+ frontmatterLine('description', yamlQuote(description)),
267
+ frontmatterLine('argument-hint', yamlQuote(fields['argument-hint'])),
268
+ sourceDisablesModel ? 'disable-model-invocation: true' : null,
269
+ '---',
270
+ ].filter(Boolean).join('\n');
271
+
272
+ return `${frontmatter}
273
+
274
+ # /${skillName}
275
+
276
+ Use the Session Orchestrator skill definition at \`skills/${skillName}/SKILL.md\`.
277
+
278
+ Arguments: $ARGUMENTS
279
+
280
+ Read that skill file and follow it exactly. When it references \`$ARGUMENTS\`, substitute the arguments above. Keep all Session Orchestrator platform fallbacks intact.
281
+
282
+ Cursor has no Skill tool. When the skill says to invoke another skill, Read \`skills/<skill-name>/SKILL.md\` and follow it. Supporting files (\`soul.md\`, phase docs) live in that same \`skills/<skill-name>/\` directory.
283
+ `;
284
+ }
285
+
286
+ /**
287
+ * Emit `disable-model-invocation: true` into a Cursor wrapper?
288
+ *
289
+ * TWO independent grounds, OR-ed — the flag is a restriction, so the rule is
290
+ * fail-closed:
291
+ * 1. the SOURCE skill declares it (the single truth for operator-only skills).
292
+ * That flag is an INDEPENDENT axis from `user-invocable`, not its inverse:
293
+ * six skills carry BOTH (measured 2026-09-17: bootstrap, brainstorm, close,
294
+ * go, plan, release), and deriving the wrapper's flag from `user-invocable`
295
+ * alone emitted NO flag for exactly those six — letting a Cursor model
296
+ * auto-invoke `/close`, `/go` and `/release`;
297
+ * 2. the skill is not `user-invocable`, i.e. a library skill whose Cursor
298
+ * wrapper is reached by an explicit Read from a command body, never by a
299
+ * model's own dispatch. This ground predates the #1 fix and is kept
300
+ * deliberately: dropping it would REMOVE the flag from 24 of 50 wrappers
301
+ * (measured 2026-09-17) on no Cursor-side evidence, which is the one
302
+ * direction a guard fix must never move.
303
+ *
304
+ * NAMED CEILING on ground 2 (BV-004). Whether Cursor reads
305
+ * `disable-model-invocation` at all is UNVERIFIED as of 2026-09-18: the key is
306
+ * documented here for Claude Code and Codex only, and `rg
307
+ * "disable-model-invocation" docs .claude .cursor/rules` returns zero Cursor
308
+ * citations. Ground 2 therefore stamps 24 wrappers on the ASSUMPTION that
309
+ * Cursor honours it — kept because stamping a restriction a reader ignores
310
+ * costs nothing, while dropping it would loosen 24 wrappers if the reader does
311
+ * honour it. REVISIT TRIGGER: Cursor documents the key (or a measured Cursor
312
+ * run shows it ignored) — then decide ground 2 on that evidence instead.
313
+ * Pinned by `tests/scripts/generate-cursor-adapter.test.mjs` ("stamps
314
+ * disable-model-invocation on ground 2 alone").
315
+ *
316
+ * Truth table — both readings normalised by `isUserInvocableValue`, so every
317
+ * form a YAML parser reads as `true` (`True`, `"true"`, `true # note`) counts
318
+ * as `true` on BOTH axes. Ground 2 made that load-bearing rather than cosmetic:
319
+ * while this file kept a private bare-`true` predicate, a `True`-marked
320
+ * user-invocable skill read as NOT user-invocable HERE and got the restriction
321
+ * STAMPED, while the shared counter listed it as a command:
322
+ *
323
+ * | source flag | user-invocable | emitted |
324
+ * |-------------|----------------|---------|
325
+ * | true | true | yes (1) |
326
+ * | true | false/absent | yes (1+2) |
327
+ * | false/absent| true | no |
328
+ * | false/absent| false/absent | yes (2) |
329
+ *
330
+ * Both readings come from {@link skillFlags} (`sourceDisablesModel ||
331
+ * !userInvocable`), which parses each SKILL.md once — so a truthy-lookalike
332
+ * value warns once per key per skill instead of once per call site.
333
+ *
334
+ * @param {string} skillName
335
+ * @returns {boolean}
336
+ */
337
+ function disablesModelInvocation(skillName) {
338
+ return skillFlags(skillName).disablesModel;
163
339
  }
164
340
 
165
341
  function renderSkill(skillName) {
166
- const skillPath = path.join(SKILLS_DIR, skillName, 'SKILL.md');
167
- const fields = parseFrontmatter(readFileSync(skillPath, 'utf8'));
342
+ const { fields } = skillFlags(skillName);
168
343
  const description = clampDescription(fields.description || `Session Orchestrator skill: ${skillName}`);
169
344
  // Same rule as renderCommand: no frontmatter value is emitted raw. `name` is
170
345
  // a directory basename today, so it is plain-safe in practice — routing it
@@ -174,7 +349,7 @@ function renderSkill(skillName) {
174
349
  `name: ${yamlQuote(skillName)}`,
175
350
  `description: ${yamlQuote(description)}`,
176
351
  ];
177
- if (!isUserInvocable(fields['user-invocable'])) {
352
+ if (disablesModelInvocation(skillName)) {
178
353
  lines.push('disable-model-invocation: true');
179
354
  }
180
355
  lines.push('---');
@@ -191,12 +366,37 @@ Cursor has no Skill tool. Treat "invoke the ${skillName} skill" as: Read \`skill
191
366
  `;
192
367
  }
193
368
 
369
+ /**
370
+ * The expected `.cursor/commands/` set: every `commands/*.md` PLUS every skill
371
+ * marked `user-invocable: true`.
372
+ *
373
+ * @returns {Map<string, string>} file name → content, sorted by file name
374
+ * @throws {Error} when one public name is claimed by both sources
375
+ */
194
376
  function expectedCommands() {
195
377
  const commands = new Map();
196
378
  for (const commandFile of commandFiles()) {
197
379
  commands.set(commandFile, renderCommand(commandFile));
198
380
  }
199
- return commands;
381
+
382
+ const collisions = [];
383
+ for (const skillName of userInvocableSkills()) {
384
+ const fileName = `${skillName}.md`;
385
+ if (commands.has(fileName)) {
386
+ collisions.push(skillName);
387
+ continue;
388
+ }
389
+ commands.set(fileName, renderSkillCommand(skillName));
390
+ }
391
+ if (collisions.length > 0) {
392
+ throw new Error(
393
+ `${collisions.length} public name(s) claimed by BOTH commands/ and a user-invocable skill: ${collisions.join(', ')}. `
394
+ + 'Exactly one document may own a slash command — delete the commands/<name>.md whose body was folded into skills/<name>/SKILL.md, '
395
+ + 'or drop `user-invocable: true` from the skill.',
396
+ );
397
+ }
398
+
399
+ return new Map([...commands].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)));
200
400
  }
201
401
 
202
402
  function expectedSkills() {
@@ -284,7 +484,16 @@ function writeAll(expectedCmds, expectedSkillsMap) {
284
484
  process.stdout.write(`cursor adapter: wrote ${expectedCmds.size} command(s), ${expectedSkillsMap.size} skill(s)\n`);
285
485
  }
286
486
 
287
- const expectedCmds = expectedCommands();
288
- const expectedSkillsMap = expectedSkills();
487
+ let expectedCmds;
488
+ let expectedSkillsMap;
489
+ try {
490
+ expectedCmds = expectedCommands();
491
+ expectedSkillsMap = expectedSkills();
492
+ } catch (error) {
493
+ // Loud and diagnosable: a source conflict must never degrade into a partial
494
+ // write or a stack trace read as "some node thing went wrong".
495
+ process.stderr.write(`FAIL: ${error.message}\n`);
496
+ process.exit(1);
497
+ }
289
498
  if (CHECK_ONLY) checkAll(expectedCmds, expectedSkillsMap);
290
499
  else writeAll(expectedCmds, expectedSkillsMap);
@@ -24,10 +24,10 @@
24
24
  * write is NEVER performed on a usage error.
25
25
  */
26
26
 
27
- import { readFileSync, writeFileSync, existsSync, statSync, realpathSync } from 'node:fs';
27
+ import { readFileSync, writeFileSync, existsSync, statSync } from 'node:fs';
28
28
  import path from 'node:path';
29
- import { fileURLToPath } from 'node:url';
30
29
  import { execFileSync } from 'node:child_process';
30
+ import { isMainModule } from './lib/is-main-module.mjs';
31
31
 
32
32
  /** The four hook manifests whose entry files seed the crawl. */
33
33
  export const HOOK_MANIFESTS = [
@@ -263,30 +263,15 @@ export function main(argv) {
263
263
  return 0;
264
264
  }
265
265
 
266
- /**
267
- * True when this module was launched as the process entry script.
268
- *
269
- * A string compare of `import.meta.url` against `file://${process.argv[1]}` is
270
- * fragile in exactly the invocations CI and husky use: any symlinked or
271
- * realpath-differing path (measured: `/tmp` → `/private/tmp` on macOS) makes
272
- * the compare false, so the whole CLI body becomes a silent no-op that still
273
- * exits 0 — a fail-OPEN drift gate. A space or `#` in the path breaks the
274
- * hand-built URL the same way. Same shape as `hooks/post-bash-write-verify.mjs`.
275
- *
276
- * @returns {boolean}
277
- */
278
- function invokedAsScript() {
279
- const entry = process.argv[1];
280
- if (!entry) return false;
281
- const self = fileURLToPath(import.meta.url);
282
- try {
283
- return realpathSync(entry) === realpathSync(self);
284
- } catch {
285
- // argv[1] unresolvable (deleted/renamed mid-run) — best-effort raw compare.
286
- return entry === self;
287
- }
288
- }
289
-
290
- if (invokedAsScript()) {
266
+ // Entry guard via the canonical predicate (`scripts/lib/is-main-module.mjs`).
267
+ // A string compare of `import.meta.url` against `file://${process.argv[1]}` is
268
+ // fragile in exactly the invocations CI and husky use: any symlinked or
269
+ // realpath-differing path (measured: `/tmp` → `/private/tmp` on macOS) makes the
270
+ // compare false, so the whole CLI body becomes a silent no-op that still exits 0
271
+ // — a fail-OPEN drift gate. This script is a plain CLI, not a hook, so it may
272
+ // import from `scripts/lib/` (the hooks that still inline the same predicate do
273
+ // so to keep `scripts/lib` off their hot path — see the ceiling comment in
274
+ // `hooks/post-bash-write-verify.mjs`).
275
+ if (isMainModule(import.meta.url)) {
291
276
  process.exit(main(process.argv));
292
277
  }
@@ -6,56 +6,162 @@
6
6
  * cross-harness `$ARGUMENTS` placeholder. The generated wrapper keeps one
7
7
  * maintained command source while giving Pi a native prompt entry per command.
8
8
  *
9
+ * `pi/prompts/` is generated from the UNION of two sources: every
10
+ * `commands/*.md`, plus every skill whose frontmatter carries an explicit
11
+ * `user-invocable: true`. The second source exists because the operator-facing
12
+ * slash-command marker moved INTO the skill frontmatter when the command bodies
13
+ * were folded into their same-named `skills/<name>/SKILL.md`. A name present in
14
+ * both sources is a generator ERROR, not a precedence question — two documents
15
+ * claiming one public `/name` is a merge that did not finish.
16
+ *
9
17
  * Usage:
10
18
  * node scripts/generate-pi-prompts.mjs
11
19
  * node scripts/generate-pi-prompts.mjs --check
12
20
  */
13
21
 
14
- import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
22
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
15
23
  import path from 'node:path';
16
24
  import { fileURLToPath } from 'node:url';
25
+ import { isUserInvocableValue } from './lib/user-invocable-skills.mjs';
17
26
 
18
27
  const __filename = fileURLToPath(import.meta.url);
19
28
  const SCRIPT_DIR = path.dirname(__filename);
20
29
  const ROOT = path.dirname(SCRIPT_DIR);
21
30
  const COMMANDS_DIR = path.join(ROOT, 'commands');
31
+ const SKILLS_DIR = path.join(ROOT, 'skills');
22
32
  const PROMPTS_DIR = path.join(ROOT, 'pi', 'prompts');
23
33
  const CHECK_ONLY = process.argv.includes('--check');
34
+ const DESCRIPTION_MAX = 1024;
35
+
36
+ function isDir(p) {
37
+ try { return statSync(p).isDirectory(); } catch { return false; }
38
+ }
24
39
 
25
40
  function commandFiles() {
41
+ if (!existsSync(COMMANDS_DIR)) return [];
26
42
  return readdirSync(COMMANDS_DIR)
27
43
  .filter((name) => name.endsWith('.md'))
28
44
  .sort();
29
45
  }
30
46
 
47
+ function skillDirs() {
48
+ if (!existsSync(SKILLS_DIR)) return [];
49
+ return readdirSync(SKILLS_DIR)
50
+ .filter((name) => isDir(path.join(SKILLS_DIR, name)) && existsSync(path.join(SKILLS_DIR, name, 'SKILL.md')))
51
+ .sort();
52
+ }
53
+
54
+ /**
55
+ * Parse YAML-ish frontmatter including `>` / `|` folded scalars, returning
56
+ * DECODED values (surrounding quotes stripped). Skill descriptions are folded
57
+ * scalars in practice, which the previous line-at-a-time parser read as the
58
+ * literal `>`. Mirrors `generate-cursor-adapter.mjs`.
59
+ *
60
+ * A UTF-8 BOM before the opening `---`, or CRLF line endings, used to make the
61
+ * two probes below miss the block entirely: the file parsed as "no
62
+ * frontmatter", so `user-invocable` silently disappeared and the skill was
63
+ * demoted out of `pi/prompts/` with no diagnostic anywhere. Both are normalised
64
+ * away first — the same treatment `parseAgentFrontmatter`
65
+ * (`scripts/lib/agent-frontmatter.mjs`) gives them.
66
+ *
67
+ * @param {string} content
68
+ * @returns {Record<string, string>}
69
+ */
31
70
  function parseFrontmatter(content) {
32
- if (!content.startsWith('---\n')) return {};
33
- const end = content.indexOf('\n---\n', 4);
71
+ const text = (content.charCodeAt(0) === 0xfeff ? content.slice(1) : content).replace(/\r\n?/g, '\n');
72
+ if (!text.startsWith('---\n')) return {};
73
+ const end = text.indexOf('\n---\n', 4);
34
74
  if (end === -1) return {};
35
75
 
76
+ const lines = text.slice(4, end).split('\n');
36
77
  const fields = {};
37
- const block = content.slice(4, end).split('\n');
38
- for (const line of block) {
39
- const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
40
- if (!match) continue;
41
- fields[match[1]] = match[2];
78
+ let i = 0;
79
+ while (i < lines.length) {
80
+ const match = lines[i].match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
81
+ if (!match) {
82
+ i += 1;
83
+ continue;
84
+ }
85
+ const key = match[1];
86
+ const raw = match[2];
87
+ if (raw === '>' || raw === '| ' || raw === '|' || raw === '>-' || raw === '|-') {
88
+ const folded = [];
89
+ i += 1;
90
+ while (i < lines.length && (lines[i].startsWith(' ') || lines[i].startsWith('\t') || lines[i] === '')) {
91
+ folded.push(lines[i].replace(/^\s+/, ''));
92
+ i += 1;
93
+ }
94
+ const joiner = raw.startsWith('|') ? '\n' : ' ';
95
+ fields[key] = folded.filter(Boolean).join(joiner).trim();
96
+ continue;
97
+ }
98
+ fields[key] = raw.replace(/^["']|["']$/g, '');
99
+ i += 1;
42
100
  }
43
101
  return fields;
44
102
  }
45
103
 
104
+ /** @see generate-cursor-adapter.mjs — same allow-list, same reasoning. */
105
+ const YAML_SCALAR_LOOKALIKE = /^(?:true|false|yes|no|on|off|null|nan|[-+]?\.?inf|~)$/i;
106
+ const YAML_PLAIN_SAFE = /^[A-Za-z0-9_][A-Za-z0-9 _.,()/-]*$/;
107
+
108
+ /**
109
+ * Render a frontmatter VALUE as YAML. `argument-hint`'s canonical authored form
110
+ * (`[mode] [--flag]`) is a YAML flow SEQUENCE when emitted bare — GH#54, which
111
+ * made Copilot CLI >= 1.0.65 silently drop the file.
112
+ *
113
+ * @param {unknown} value
114
+ * @returns {string|null} the YAML scalar, or `null` when there is nothing to emit
115
+ */
116
+ function yamlQuote(value) {
117
+ if (value === undefined || value === null || value === '') return null;
118
+ const text = String(value);
119
+ if (text.trim() === '') return null;
120
+ if (text !== text.trim()) return JSON.stringify(text);
121
+ if (!YAML_PLAIN_SAFE.test(text)) return JSON.stringify(text);
122
+ if (YAML_SCALAR_LOOKALIKE.test(text)) return JSON.stringify(text);
123
+ if (/^\d/.test(text)) return JSON.stringify(text);
124
+ return text;
125
+ }
126
+
46
127
  function frontmatterLine(key, value) {
47
- if (value === undefined || value === '') return null;
128
+ if (value === undefined || value === '' || value === null) return null;
48
129
  return `${key}: ${value}`;
49
130
  }
50
131
 
132
+ function clampDescription(text) {
133
+ const collapsed = String(text || '').replace(/\s+/g, ' ').trim();
134
+ if (collapsed.length <= DESCRIPTION_MAX) return collapsed;
135
+ return collapsed.slice(0, DESCRIPTION_MAX - 1).trimEnd() + '…';
136
+ }
137
+
138
+ /**
139
+ * Skills that declare themselves operator-facing slash commands.
140
+ *
141
+ * The predicate is `isUserInvocableValue` from `scripts/lib/user-invocable-skills.mjs`
142
+ * — the ONE normaliser for this marker, shared with the Cursor and Codex
143
+ * generators and with every counter. A local copy here is how the quoted-value
144
+ * disagreement arose (`"true"`: wrapper generated on two adapters, counted by
145
+ * neither counter, crash on the third).
146
+ *
147
+ * @returns {string[]} skill names, sorted
148
+ */
149
+ function userInvocableSkills() {
150
+ return skillDirs().filter((name) => {
151
+ const file = path.join(SKILLS_DIR, name, 'SKILL.md');
152
+ const fields = parseFrontmatter(readFileSync(file, 'utf8'));
153
+ return isUserInvocableValue(fields['user-invocable'], file);
154
+ });
155
+ }
156
+
51
157
  function renderPrompt(commandFile) {
52
158
  const commandPath = path.join(COMMANDS_DIR, commandFile);
53
159
  const commandName = commandFile.replace(/\.md$/, '');
54
160
  const fields = parseFrontmatter(readFileSync(commandPath, 'utf8'));
55
161
  const frontmatter = [
56
162
  '---',
57
- frontmatterLine('description', fields.description),
58
- frontmatterLine('argument-hint', fields['argument-hint']),
163
+ frontmatterLine('description', yamlQuote(clampDescription(fields.description))),
164
+ frontmatterLine('argument-hint', yamlQuote(fields['argument-hint'])),
59
165
  '---',
60
166
  ].filter(Boolean).join('\n');
61
167
 
@@ -71,12 +177,68 @@ Read that command file and follow it exactly. When it references \`$ARGUMENTS\`,
71
177
  `;
72
178
  }
73
179
 
180
+ /**
181
+ * A `pi/prompts/<name>.md` wrapper for a skill that IS the slash command
182
+ * (`user-invocable: true`). Same contract as {@link renderPrompt}, pointing at
183
+ * the skill body instead of a command file.
184
+ *
185
+ * @param {string} skillName
186
+ * @returns {string}
187
+ */
188
+ function renderSkillPrompt(skillName) {
189
+ const skillPath = path.join(SKILLS_DIR, skillName, 'SKILL.md');
190
+ const fields = parseFrontmatter(readFileSync(skillPath, 'utf8'));
191
+ const description = clampDescription(fields.description || `Session Orchestrator skill: ${skillName}`);
192
+ const frontmatter = [
193
+ '---',
194
+ frontmatterLine('description', yamlQuote(description)),
195
+ frontmatterLine('argument-hint', yamlQuote(fields['argument-hint'])),
196
+ '---',
197
+ ].filter(Boolean).join('\n');
198
+
199
+ return `${frontmatter}
200
+
201
+ # /${skillName}
202
+
203
+ Use the Session Orchestrator skill definition at \`skills/${skillName}/SKILL.md\`.
204
+
205
+ Arguments: $@
206
+
207
+ Read that skill file and follow it exactly. When it references \`$ARGUMENTS\`, substitute the arguments above. Keep all Session Orchestrator platform fallbacks intact.
208
+ `;
209
+ }
210
+
211
+ /**
212
+ * The expected `pi/prompts/` set: every `commands/*.md` PLUS every skill marked
213
+ * `user-invocable: true`.
214
+ *
215
+ * @returns {Map<string, string>} file name -> content, sorted by file name
216
+ * @throws {Error} when one public name is claimed by both sources
217
+ */
74
218
  function expectedPrompts() {
75
219
  const prompts = new Map();
76
220
  for (const commandFile of commandFiles()) {
77
221
  prompts.set(commandFile, renderPrompt(commandFile));
78
222
  }
79
- return prompts;
223
+
224
+ const collisions = [];
225
+ for (const skillName of userInvocableSkills()) {
226
+ const fileName = `${skillName}.md`;
227
+ if (prompts.has(fileName)) {
228
+ collisions.push(skillName);
229
+ continue;
230
+ }
231
+ prompts.set(fileName, renderSkillPrompt(skillName));
232
+ }
233
+ if (collisions.length > 0) {
234
+ throw new Error(
235
+ `${collisions.length} public name(s) claimed by BOTH commands/ and a user-invocable skill: ${collisions.join(', ')}. `
236
+ + 'Exactly one document may own a slash command — delete the commands/<name>.md whose body was folded into skills/<name>/SKILL.md, '
237
+ + 'or drop `user-invocable: true` from the skill.',
238
+ );
239
+ }
240
+
241
+ return new Map([...prompts].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)));
80
242
  }
81
243
 
82
244
  function checkPrompts(expected) {
@@ -122,6 +284,14 @@ function writePrompts(expected) {
122
284
  console.log(`pi prompts: wrote ${expected.size} file(s)`);
123
285
  }
124
286
 
125
- const expected = expectedPrompts();
287
+ let expected;
288
+ try {
289
+ expected = expectedPrompts();
290
+ } catch (error) {
291
+ // Loud and diagnosable: a source conflict must never degrade into a partial
292
+ // write or a stack trace read as "some node thing went wrong".
293
+ console.error(`FAIL: ${error.message}`);
294
+ process.exit(1);
295
+ }
126
296
  if (CHECK_ONLY) checkPrompts(expected);
127
297
  else writePrompts(expected);
@@ -34,8 +34,8 @@
34
34
 
35
35
  import { execFile as _execFile } from 'node:child_process';
36
36
  import { promisify } from 'node:util';
37
- import { pathToFileURL } from 'node:url';
38
37
  import { resolveRepoSpec as _resolveRepoSpec, resolveRepoHost as _resolveRepoHost } from './lib/vcs-repo-spec.mjs';
38
+ import { isMainModule } from './lib/is-main-module.mjs';
39
39
 
40
40
  const execFileAsync = promisify(_execFile);
41
41
 
@@ -347,8 +347,7 @@ export async function auditGithubBranchProtection(opts = {}, deps = {}) {
347
347
  }
348
348
 
349
349
  // CLI entry — only when run directly, not when imported (e.g. by tests).
350
- const isMain =
351
- process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
350
+ const isMain =isMainModule(import.meta.url);
352
351
 
353
352
  if (isMain) {
354
353
  auditGithubBranchProtection({ repoRoot: process.cwd() }).then((result) => {