session-orchestrator 3.24.0 → 4.0.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 (350) hide show
  1. package/.agents/skills/architecture/SKILL.md +18 -0
  2. package/.agents/skills/autopilot/SKILL.md +17 -0
  3. package/.agents/skills/bootstrap/SKILL.md +20 -0
  4. package/.agents/skills/brainstorm/SKILL.md +22 -0
  5. package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
  6. package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
  7. package/.agents/skills/debug/SKILL.md +22 -0
  8. package/.agents/skills/discovery/SKILL.md +20 -0
  9. package/.agents/skills/dispatcher/SKILL.md +15 -0
  10. package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
  11. package/.agents/skills/ecosystem-health/SKILL.md +20 -0
  12. package/.agents/skills/eli5/SKILL.md +20 -0
  13. package/.agents/skills/eval/SKILL.md +21 -0
  14. package/.agents/skills/evolve/SKILL.md +21 -0
  15. package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
  16. package/.agents/skills/gitlab-ops/SKILL.md +20 -0
  17. package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
  18. package/.agents/skills/grill/SKILL.md +22 -0
  19. package/.agents/skills/hook-development/SKILL.md +15 -0
  20. package/.agents/skills/mcp-builder/SKILL.md +15 -0
  21. package/.agents/skills/memory-cleanup/SKILL.md +21 -0
  22. package/.agents/skills/mode-selector/SKILL.md +17 -0
  23. package/.agents/skills/npm-publish/SKILL.md +16 -0
  24. package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
  25. package/.agents/skills/persona-panel/SKILL.md +17 -0
  26. package/.agents/skills/plan/SKILL.md +20 -0
  27. package/.agents/skills/playwright-driver/SKILL.md +20 -0
  28. package/.agents/skills/quality-gates/SKILL.md +20 -0
  29. package/.agents/skills/reconcile/SKILL.md +21 -0
  30. package/.agents/skills/remote-offload/SKILL.md +20 -0
  31. package/.agents/skills/repo-audit/SKILL.md +16 -0
  32. package/.agents/skills/session-end/SKILL.md +20 -0
  33. package/.agents/skills/session-plan/SKILL.md +20 -0
  34. package/.agents/skills/session-start/SKILL.md +20 -0
  35. package/.agents/skills/spinout/SKILL.md +16 -0
  36. package/.agents/skills/sunset-review/SKILL.md +16 -0
  37. package/.agents/skills/test-runner/SKILL.md +20 -0
  38. package/.agents/skills/tmux-layout/SKILL.md +21 -0
  39. package/.agents/skills/using-orchestrator/SKILL.md +17 -0
  40. package/.agents/skills/vault-mirror/SKILL.md +15 -0
  41. package/.agents/skills/vault-sync/SKILL.md +15 -0
  42. package/.agents/skills/wave-executor/SKILL.md +20 -0
  43. package/.agents/skills/write-executable-plan/SKILL.md +22 -0
  44. package/.claude-plugin/marketplace.json +1 -1
  45. package/.claude-plugin/plugin.json +1 -1
  46. package/.codex-plugin/plugin.json +1 -1
  47. package/.cursor/commands/autopilot.md +2 -2
  48. package/.cursor/commands/bootstrap.md +1 -1
  49. package/.cursor/commands/brainstorm.md +1 -1
  50. package/.cursor/commands/debug.md +1 -1
  51. package/.cursor/commands/discovery.md +1 -1
  52. package/.cursor/commands/dispatcher.md +2 -2
  53. package/.cursor/commands/eli5.md +2 -2
  54. package/.cursor/commands/eval.md +2 -2
  55. package/.cursor/commands/evolve.md +1 -1
  56. package/.cursor/commands/go.md +1 -1
  57. package/.cursor/commands/grill.md +2 -2
  58. package/.cursor/commands/memory-cleanup.md +2 -2
  59. package/.cursor/commands/persona-panel.md +1 -1
  60. package/.cursor/commands/plan.md +1 -1
  61. package/.cursor/commands/portfolio.md +1 -1
  62. package/.cursor/commands/reconcile.md +2 -2
  63. package/.cursor/commands/release.md +2 -2
  64. package/.cursor/commands/session.md +2 -2
  65. package/.cursor/commands/spinout.md +2 -2
  66. package/.cursor/commands/sunset-review.md +2 -2
  67. package/.cursor/commands/templates-ack.md +2 -2
  68. package/.cursor/commands/test.md +2 -2
  69. package/.cursor/skills/brainstorm/SKILL.md +1 -1
  70. package/.cursor/skills/eval/SKILL.md +1 -1
  71. package/.cursor/skills/quality-gates/SKILL.md +1 -1
  72. package/.cursor/skills/remote-offload/SKILL.md +1 -1
  73. package/.orchestrator/policy/blocked-commands.json +121 -0
  74. package/.orchestrator/policy/ecosystem.schema.json +66 -0
  75. package/.orchestrator/policy/quality-gates.example.json +16 -0
  76. package/.orchestrator/policy/quality-gates.schema.json +38 -0
  77. package/.orchestrator/policy/templates-policy.json +27 -0
  78. package/.orchestrator/policy/test-profiles.json +47 -0
  79. package/AGENTS.md +225 -0
  80. package/CHANGELOG.md +1125 -2
  81. package/NOTICE +11 -6
  82. package/README.md +127 -94
  83. package/agents/eval-judge.md +1 -1
  84. package/agents/skill-applied-judge.md +1 -1
  85. package/assets/wave-lifecycle.svg +98 -0
  86. package/commands/release.md +6 -3
  87. package/commands/session.md +18 -3
  88. package/docs/README.md +4 -0
  89. package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
  90. package/docs/baseline.md +67 -0
  91. package/docs/ci-setup.md +108 -62
  92. package/docs/codex-setup.md +65 -21
  93. package/docs/components.md +36 -15
  94. package/docs/cursor-setup.md +6 -2
  95. package/docs/events-schema.md +9 -6
  96. package/docs/instruction-delivery.md +62 -0
  97. package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
  98. package/docs/migration-v4.md +341 -0
  99. package/docs/pi-setup.md +6 -1
  100. package/docs/plugin-architecture-v3.md +1 -1
  101. package/docs/rule-authoring.md +85 -19
  102. package/docs/scope-collision-guard.md +5 -5
  103. package/docs/session-config-reference.md +57 -56
  104. package/docs/session-config-template.md +6 -29
  105. package/docs/telemetry.md +157 -3
  106. package/docs/vault-docs-architecture.md +50 -11
  107. package/hooks/_lib/hook-import-set.json +1487 -0
  108. package/hooks/_lib/subagent-transcript.mjs +562 -0
  109. package/hooks/config-protection.mjs +2 -2
  110. package/hooks/cwd-change-restore.mjs +2 -2
  111. package/hooks/enforce-commands.mjs +69 -0
  112. package/hooks/hooks-codex.json +1 -1
  113. package/hooks/hooks-cursor.json +10 -0
  114. package/hooks/hooks-pi.json +5 -0
  115. package/hooks/hooks.json +6 -1
  116. package/hooks/loop-guard.mjs +3 -3
  117. package/hooks/on-session-end.mjs +2 -2
  118. package/hooks/on-session-start.mjs +103 -2
  119. package/hooks/on-stop.mjs +36 -11
  120. package/hooks/operator-steer.mjs +2 -2
  121. package/hooks/post-bash-write-verify.mjs +85 -0
  122. package/hooks/post-edit-import-probe.mjs +344 -0
  123. package/hooks/post-subagent-discovery-validator.mjs +187 -431
  124. package/hooks/post-tool-batch-wave-signal.mjs +118 -4
  125. package/hooks/post-tool-failure-corrective-context.mjs +2 -2
  126. package/hooks/post-tooluse-frontend-slop.mjs +3 -3
  127. package/hooks/pre-bash-destructive-guard.mjs +39 -13
  128. package/hooks/skill-invocation-telemetry.mjs +17 -5
  129. package/hooks/subagent-telemetry.mjs +13 -4
  130. package/monitors/monitors.json +3 -3
  131. package/package.json +9 -1
  132. package/pi/prompts/session.md +2 -2
  133. package/plugin.json +27 -0
  134. package/scripts/backfill-abandoned-sessions.mjs +50 -4
  135. package/scripts/backfill-learnings-from-vault.mjs +9 -3
  136. package/scripts/dialectic-deriver.mjs +73 -8
  137. package/scripts/export-hw-learnings.mjs +113 -1
  138. package/scripts/generate-agents-skills.mjs +378 -0
  139. package/scripts/generate-cursor-adapter.mjs +45 -8
  140. package/scripts/generate-hook-import-set.mjs +249 -0
  141. package/scripts/lib/agent-status.mjs +13 -2
  142. package/scripts/lib/auto-dream.mjs +38 -36
  143. package/scripts/lib/autonomy/suitability.mjs +6 -0
  144. package/scripts/lib/autopilot/loop.mjs +2 -2
  145. package/scripts/lib/ci-status-banner.mjs +220 -75
  146. package/scripts/lib/codex/plugin-contract.mjs +82 -6
  147. package/scripts/lib/config/auto-dream.mjs +2 -1
  148. package/scripts/lib/config/block-header.mjs +8 -0
  149. package/scripts/lib/config/block-preprocess.mjs +177 -0
  150. package/scripts/lib/config/broken-window.mjs +2 -1
  151. package/scripts/lib/config/cold-start.mjs +2 -1
  152. package/scripts/lib/config/config-protection.mjs +22 -2
  153. package/scripts/lib/config/context-coverage.mjs +2 -1
  154. package/scripts/lib/config/cross-repo.mjs +2 -1
  155. package/scripts/lib/config/custom-phases.mjs +2 -1
  156. package/scripts/lib/config/dialectic.mjs +2 -1
  157. package/scripts/lib/config/discovery-validator.mjs +2 -1
  158. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
  159. package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
  160. package/scripts/lib/config/docs-orchestrator.mjs +2 -1
  161. package/scripts/lib/config/docs-staleness.mjs +2 -1
  162. package/scripts/lib/config/drift-check.mjs +2 -1
  163. package/scripts/lib/config/eval.mjs +2 -1
  164. package/scripts/lib/config/events-rotation.mjs +2 -1
  165. package/scripts/lib/config/evolve.mjs +8 -2
  166. package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
  167. package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
  168. package/scripts/lib/config/handover-gate.mjs +2 -1
  169. package/scripts/lib/config/health-endpoints.mjs +7 -2
  170. package/scripts/lib/config/issue-budget.mjs +2 -1
  171. package/scripts/lib/config/loop-guard.mjs +2 -1
  172. package/scripts/lib/config/memory.mjs +2 -1
  173. package/scripts/lib/config/moc-staleness.mjs +2 -1
  174. package/scripts/lib/config/persona-gate-wave.mjs +2 -1
  175. package/scripts/lib/config/private-config-dir.mjs +67 -0
  176. package/scripts/lib/config/reconcile.mjs +2 -1
  177. package/scripts/lib/config/remote-hosts.mjs +2 -1
  178. package/scripts/lib/config/section-extractor.mjs +7 -1
  179. package/scripts/lib/config/skill-evolution.mjs +2 -1
  180. package/scripts/lib/config/slopcheck.mjs +2 -1
  181. package/scripts/lib/config/state-md-lock.mjs +2 -1
  182. package/scripts/lib/config/templates-first.mjs +2 -1
  183. package/scripts/lib/config/test.mjs +2 -1
  184. package/scripts/lib/config/vault-integration.mjs +7 -1
  185. package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
  186. package/scripts/lib/config/vault-staleness.mjs +2 -1
  187. package/scripts/lib/config/vault-sync.mjs +2 -1
  188. package/scripts/lib/config/verification-auto-fix.mjs +2 -1
  189. package/scripts/lib/config/wave-reviewers.mjs +2 -1
  190. package/scripts/lib/config/worktree-orphans.mjs +2 -1
  191. package/scripts/lib/convergence-monitor.mjs +82 -16
  192. package/scripts/lib/dispatcher/rank.mjs +124 -48
  193. package/scripts/lib/ecosystem-health.mjs +16 -2
  194. package/scripts/lib/eval/engine.mjs +9 -1
  195. package/scripts/lib/eval/session-resolve.mjs +23 -4
  196. package/scripts/lib/events.mjs +22 -6
  197. package/scripts/lib/frontmatter-guard.mjs +131 -13
  198. package/scripts/lib/gates/gate-full.mjs +26 -0
  199. package/scripts/lib/gates/gate-helpers.mjs +76 -0
  200. package/scripts/lib/hardware-pattern-detector.mjs +18 -1
  201. package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
  202. package/scripts/lib/host-identity.mjs +50 -11
  203. package/scripts/lib/instruction-budget-guard.mjs +171 -5
  204. package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
  205. package/scripts/lib/learnings/io.mjs +60 -6
  206. package/scripts/lib/memory-proposals/store.mjs +30 -22
  207. package/scripts/lib/owner-config-banner.mjs +43 -6
  208. package/scripts/lib/owner-config-loader.mjs +21 -10
  209. package/scripts/lib/owner-interview.mjs +3 -3
  210. package/scripts/lib/owner-yaml.mjs +207 -14
  211. package/scripts/lib/platform.mjs +108 -15
  212. package/scripts/lib/plugin-update-banner.mjs +406 -0
  213. package/scripts/lib/project-hygiene.mjs +38 -2
  214. package/scripts/lib/qg-command-drift-banner.mjs +50 -12
  215. package/scripts/lib/quality-gate.mjs +133 -44
  216. package/scripts/lib/reconcile/emitter.mjs +68 -6
  217. package/scripts/lib/reconcile/engine.mjs +13 -4
  218. package/scripts/lib/reconcile/idempotency.mjs +37 -4
  219. package/scripts/lib/reconcile/writer.mjs +40 -18
  220. package/scripts/lib/session-close-backfill.mjs +67 -9
  221. package/scripts/lib/session-id.mjs +12 -23
  222. package/scripts/lib/session-identity/own-session.mjs +125 -10
  223. package/scripts/lib/session-lock-shape.mjs +43 -0
  224. package/scripts/lib/session-lock.mjs +5 -10
  225. package/scripts/lib/session-registry.mjs +25 -9
  226. package/scripts/lib/session-schema/constants.mjs +36 -2
  227. package/scripts/lib/session-schema/validator.mjs +38 -4
  228. package/scripts/lib/session-start-probes.mjs +18 -1
  229. package/scripts/lib/sessions-staleness-banner.mjs +18 -11
  230. package/scripts/lib/skill-health/join.mjs +17 -4
  231. package/scripts/lib/state-md.mjs +78 -0
  232. package/scripts/lib/sunset/walker.mjs +6 -0
  233. package/scripts/lib/telemetry/schema.mjs +181 -9
  234. package/scripts/lib/telemetry/sync.mjs +368 -12
  235. package/scripts/lib/validate/check-agents-skills.mjs +327 -0
  236. package/scripts/lib/validate/check-agents.mjs +3 -3
  237. package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
  238. package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
  239. package/scripts/lib/validate/check-owner-leakage.mjs +281 -20
  240. package/scripts/lib/validate/check-skill-links.mjs +163 -0
  241. package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
  242. package/scripts/lib/validate/check-unwired-features.mjs +0 -2
  243. package/scripts/lib/validate/check-validator-registration.mjs +10 -4
  244. package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
  245. package/scripts/lib/vault-backfill/template.mjs +63 -6
  246. package/scripts/lib/vault-mirror/process.mjs +165 -42
  247. package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
  248. package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
  249. package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
  250. package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
  251. package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
  252. package/scripts/lib/wave-resource-gate.mjs +8 -2
  253. package/scripts/lib/wave-sizing.mjs +4 -1
  254. package/scripts/lib/wave-transcript-tail.mjs +118 -4
  255. package/scripts/materialize-wave-scope.mjs +12 -5
  256. package/scripts/memory-propose.mjs +19 -5
  257. package/scripts/migrate-cold-start-seed.mjs +4 -1
  258. package/scripts/parse-config.mjs +60 -3
  259. package/scripts/release.mjs +337 -29
  260. package/scripts/repair-invalid-sessions.mjs +3 -3
  261. package/scripts/run-quality-gate.mjs +128 -11
  262. package/scripts/sweep-expired-learnings.mjs +90 -0
  263. package/scripts/sync-vault-schema.mjs +3 -1
  264. package/scripts/telemetry.mjs +2 -2
  265. package/scripts/validate-plugin.mjs +161 -0
  266. package/scripts/validate-wave-scope.mjs +28 -8
  267. package/scripts/wave-scope-binding.mjs +215 -0
  268. package/skills/_shared/instruction-file-resolution.md +10 -0
  269. package/skills/_shared/parallel-aware-preamble.md +1 -0
  270. package/skills/_shared/platform-tools.md +1 -1
  271. package/skills/_shared/state-ownership.md +1 -1
  272. package/skills/architecture/SKILL.md +7 -5
  273. package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
  274. package/skills/autopilot/SKILL.md +4 -18
  275. package/skills/claude-md-drift-check/SKILL.md +5 -1
  276. package/skills/claude-md-drift-check/checker.mjs +62 -2
  277. package/skills/convergence-monitoring/SIGNALS.md +55 -0
  278. package/skills/discovery/probes/vault-staleness.mjs +37 -13
  279. package/skills/discovery/probes-arch.md +20 -18
  280. package/skills/dispatcher/SKILL.md +3 -2
  281. package/skills/evolve/SKILL.md +65 -26
  282. package/skills/frontmatter-guard/SKILL.md +11 -5
  283. package/skills/npm-publish/SKILL.md +1 -1
  284. package/skills/reconcile/SKILL.md +33 -0
  285. package/skills/remote-offload/SKILL.md +1 -1
  286. package/skills/session-end/SKILL.md +18 -905
  287. package/skills/session-end/phase-3-6-tail.md +10 -3
  288. package/skills/session-end/plan-verification.md +221 -155
  289. package/skills/session-end/references/phase-2-quality-gate.md +93 -0
  290. package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
  291. package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
  292. package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
  293. package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
  294. package/skills/session-end/references/session-summary-template.md +62 -0
  295. package/skills/session-plan/SKILL.md +49 -0
  296. package/skills/session-start/SKILL.md +22 -904
  297. package/skills/session-start/phase-8-5-express-path.md +1 -1
  298. package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
  299. package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
  300. package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
  301. package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
  302. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
  303. package/skills/session-start/references/phase-4-ssot-environment-check.md +155 -0
  304. package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
  305. package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
  306. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
  307. package/skills/vault-sync/validator.mjs +21 -27
  308. package/skills/wave-executor/SKILL.md +15 -1
  309. package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
  310. package/skills/wave-executor/references/wave-loop-review.md +570 -0
  311. package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
  312. package/skills/wave-executor/wave-loop.md +14 -1309
  313. package/templates/_shared/journey-manifest.md +10 -6
  314. package/.cursor/commands/autopilot-multi.md +0 -14
  315. package/.cursor/commands/contract-version-bump.md +0 -14
  316. package/.cursor/commands/journey-audit.md +0 -14
  317. package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
  318. package/.cursor/skills/daily/SKILL.md +0 -12
  319. package/.cursor/skills/domain-model/SKILL.md +0 -13
  320. package/.cursor/skills/journey-audit/SKILL.md +0 -13
  321. package/.cursor/skills/skill-creator/SKILL.md +0 -13
  322. package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
  323. package/commands/autopilot-multi.md +0 -74
  324. package/commands/contract-version-bump.md +0 -28
  325. package/commands/journey-audit.md +0 -43
  326. package/pi/prompts/autopilot-multi.md +0 -12
  327. package/pi/prompts/contract-version-bump.md +0 -12
  328. package/pi/prompts/journey-audit.md +0 -12
  329. package/scripts/autopilot-multi.mjs +0 -885
  330. package/scripts/backfill-learnings-expires.mjs +0 -196
  331. package/scripts/backfill-learnings.mjs +0 -203
  332. package/scripts/fleet-instruction-scan.mjs +0 -141
  333. package/scripts/lib/autopilot/dep-graph.mjs +0 -417
  334. package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
  335. package/scripts/lib/webhook-url.mjs +0 -105
  336. package/scripts/lifecycle-sim-v6.mjs +0 -347
  337. package/scripts/migrate-learnings-jsonl.mjs +0 -189
  338. package/scripts/migrate-subagents-jsonl.mjs +0 -196
  339. package/scripts/upload-social-preview.mjs +0 -316
  340. package/skills/_shared/model-selection.md +0 -64
  341. package/skills/contract-version-bump/SKILL.md +0 -219
  342. package/skills/daily/SKILL.md +0 -222
  343. package/skills/daily/generate.sh +0 -92
  344. package/skills/daily/templates/daily.md.tpl +0 -36
  345. package/skills/journey-audit/SKILL.md +0 -270
  346. package/skills/skill-creator/SKILL.md +0 -168
  347. package/skills/ubiquitous-language/SKILL.md +0 -97
  348. package/skills/vault-sync/package-lock.json +0 -40
  349. /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
  350. /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
@@ -4,99 +4,261 @@
4
4
  // Exit 0 = all checks passed, 1 = at least one failure.
5
5
 
6
6
  import { spawnSync } from 'node:child_process';
7
- import { existsSync, readFileSync } from 'node:fs';
8
- import { join } from 'node:path';
7
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
8
+ import { join, relative } from 'node:path';
9
+ import { pathToFileURL } from 'node:url';
10
+
11
+ import yaml from 'js-yaml';
9
12
 
10
13
  import { CURSOR_TO_CANONICAL_EVENT } from '../cursor-hook-bridge.mjs';
11
14
 
12
- const pluginRoot = process.argv[2];
13
- if (!pluginRoot) {
14
- console.error('Usage: check-cursor-adapter.mjs <plugin-root>');
15
- process.exit(1);
16
- }
15
+ /** agentskills.io caps a skill/command description at 1024 characters. */
16
+ const DESCRIPTION_MAX = 1024;
17
17
 
18
- let passed = 0;
19
- let failed = 0;
20
-
21
- function pass(msg) { console.log(` PASS: ${msg}`); passed += 1; }
22
- function fail(msg) { console.log(` FAIL: ${msg}`); failed += 1; }
23
-
24
- console.log('--- Check 1: generated Cursor command and skill wrappers ---');
25
-
26
- const generator = join(pluginRoot, 'scripts', 'generate-cursor-adapter.mjs');
27
- if (!existsSync(generator)) {
28
- fail('scripts/generate-cursor-adapter.mjs exists');
29
- } else {
30
- const result = spawnSync(process.execPath, [generator, '--check'], {
31
- cwd: pluginRoot,
32
- encoding: 'utf8',
33
- stdio: ['ignore', 'pipe', 'pipe'],
34
- });
35
- if (result.status === 0) {
36
- pass('.cursor/commands and .cursor/skills are up to date with commands/ and skills/');
37
- } else {
38
- const detail = ((result.stdout ?? '') + (result.stderr ?? '')).trim();
39
- fail(`Cursor adapter wrappers are stale${detail ? `: ${detail}` : ''}`);
18
+ /**
19
+ * Locate a top-level frontmatter key's line number (1-based) so a violation can
20
+ * be reported as `file:line` rather than `file`.
21
+ *
22
+ * @param {string} content raw file content
23
+ * @param {string} key frontmatter key
24
+ * @returns {number} 1-based line number, or 1 when the key is absent
25
+ */
26
+ function frontmatterKeyLine(content, key) {
27
+ const lines = content.split('\n');
28
+ const needle = new RegExp(`^${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*:`);
29
+ for (let i = 0; i < lines.length; i += 1) {
30
+ if (i > 0 && lines[i] === '---') break; // end of frontmatter
31
+ if (needle.test(lines[i])) return i + 1;
40
32
  }
33
+ return 1;
41
34
  }
42
35
 
43
- console.log('');
44
- console.log('--- Check 2: .cursor/hooks.json native manifest ---');
36
+ /**
37
+ * Validate ONE generated artefact's frontmatter against the Cursor/agentskills
38
+ * spec, independent of the generator that produced it.
39
+ *
40
+ * @param {string} filePath absolute path to the artefact
41
+ * @param {string} rel path to report violations against
42
+ * @param {{requireName: boolean}} opts
43
+ * @returns {string[]} violation strings, empty when the artefact is conformant
44
+ */
45
+ function validateArtefactFrontmatter(filePath, rel, { requireName }) {
46
+ const violations = [];
47
+ const content = readFileSync(filePath, 'utf8');
48
+
49
+ if (!content.startsWith('---\n')) {
50
+ return [`${rel}:1 — no YAML frontmatter block (file must start with '---')`];
51
+ }
52
+ const end = content.indexOf('\n---\n', 3);
53
+ if (end === -1) {
54
+ return [`${rel}:1 — frontmatter block is never closed`];
55
+ }
45
56
 
46
- const hooksPath = join(pluginRoot, '.cursor', 'hooks.json');
47
- if (!existsSync(hooksPath)) {
48
- fail('.cursor/hooks.json exists');
49
- } else {
50
- let hooksJson;
57
+ let fm;
51
58
  try {
52
- hooksJson = JSON.parse(readFileSync(hooksPath, 'utf8'));
53
- pass('.cursor/hooks.json is valid JSON');
59
+ fm = yaml.load(content.slice(4, end + 1), { schema: yaml.CORE_SCHEMA });
54
60
  } catch (err) {
55
- fail(`.cursor/hooks.json is not valid JSON: ${err.message}`);
56
- hooksJson = null;
61
+ return [`${rel}:1 — frontmatter is not parseable YAML: ${err.message.split('\n')[0]}`];
62
+ }
63
+ if (fm === null || typeof fm !== 'object' || Array.isArray(fm)) {
64
+ return [`${rel}:1 — frontmatter does not parse to a mapping`];
57
65
  }
58
66
 
59
- if (hooksJson) {
60
- if (hooksJson.version === 1) {
61
- pass('.cursor/hooks.json version is 1');
62
- } else {
63
- fail('.cursor/hooks.json version must be 1');
67
+ // `argument-hint`, when present, MUST be a string. Its canonical authored
68
+ // form is `[mode] [--flag]`, which YAML reads as a flow SEQUENCE unless the
69
+ // generator quotes it — GH#54, which made Copilot CLI >= 1.0.65 silently drop
70
+ // the file. Type, not presence, is the assertion: an array here is the bug.
71
+ if (Object.hasOwn(fm, 'argument-hint')) {
72
+ const hint = fm['argument-hint'];
73
+ if (typeof hint !== 'string') {
74
+ violations.push(
75
+ `${rel}:${frontmatterKeyLine(content, 'argument-hint')} — argument-hint must be a string, got ${Array.isArray(hint) ? 'array' : typeof hint} (GH#54: an unquoted [a] [b] is a YAML flow sequence)`,
76
+ );
77
+ } else if (hint.trim() === '') {
78
+ violations.push(`${rel}:${frontmatterKeyLine(content, 'argument-hint')} — argument-hint is present but empty`);
64
79
  }
80
+ }
65
81
 
66
- const declared = Object.keys(hooksJson.hooks || {});
67
- const expected = Object.keys(CURSOR_TO_CANONICAL_EVENT);
68
- const missing = expected.filter((event) => !declared.includes(event));
69
- const extra = declared.filter((event) => !expected.includes(event));
70
- if (missing.length === 0 && extra.length === 0) {
71
- pass(`.cursor/hooks.json events match CURSOR_TO_CANONICAL_EVENT (${expected.length})`);
72
- } else {
73
- if (missing.length > 0) fail(`.cursor/hooks.json missing events: ${missing.join(', ')}`);
74
- if (extra.length > 0) fail(`.cursor/hooks.json extra events: ${extra.join(', ')}`);
82
+ const required = requireName ? ['name', 'description'] : ['description'];
83
+ for (const key of required) {
84
+ const value = fm[key];
85
+ if (typeof value !== 'string' || value.trim() === '') {
86
+ violations.push(
87
+ `${rel}:${frontmatterKeyLine(content, key)} ${key} must be a non-empty string, got ${value === undefined ? 'nothing' : (Array.isArray(value) ? 'array' : typeof value)}`,
88
+ );
89
+ }
90
+ }
91
+
92
+ if (typeof fm.description === 'string' && fm.description.length > DESCRIPTION_MAX) {
93
+ violations.push(
94
+ `${rel}:${frontmatterKeyLine(content, 'description')} — description is ${fm.description.length} chars, over the ${DESCRIPTION_MAX} limit (agentskills.io)`,
95
+ );
96
+ }
97
+
98
+ return violations;
99
+ }
100
+
101
+ /**
102
+ * Validate every generated Cursor artefact under `<pluginRoot>/.cursor`.
103
+ *
104
+ * This deliberately does NOT run the generator. Comparing generator output to
105
+ * generator output — which is all `--check` does — cannot see a defect the
106
+ * generator itself emits: it would report "up to date" while every file is
107
+ * broken, and a snapshot-style guard would freeze the broken files as the
108
+ * expectation. This reads the artefacts and judges them against the spec.
109
+ *
110
+ * @param {string} pluginRoot
111
+ * @returns {{files: number, violations: string[]}}
112
+ */
113
+ export function validateCursorArtefacts(pluginRoot) {
114
+ const violations = [];
115
+ let files = 0;
116
+
117
+ const commandsDir = join(pluginRoot, '.cursor', 'commands');
118
+ if (existsSync(commandsDir)) {
119
+ for (const name of readdirSync(commandsDir).filter((n) => n.endsWith('.md')).sort()) {
120
+ const filePath = join(commandsDir, name);
121
+ files += 1;
122
+ violations.push(
123
+ ...validateArtefactFrontmatter(filePath, relative(pluginRoot, filePath), { requireName: false }),
124
+ );
75
125
  }
126
+ }
127
+
128
+ const skillsDir = join(pluginRoot, '.cursor', 'skills');
129
+ if (existsSync(skillsDir)) {
130
+ for (const name of readdirSync(skillsDir).sort()) {
131
+ const filePath = join(skillsDir, name, 'SKILL.md');
132
+ let isDir;
133
+ try { isDir = statSync(join(skillsDir, name)).isDirectory(); } catch { isDir = false; }
134
+ if (!isDir || !existsSync(filePath)) continue;
135
+ files += 1;
136
+ violations.push(
137
+ ...validateArtefactFrontmatter(filePath, relative(pluginRoot, filePath), { requireName: true }),
138
+ );
139
+ }
140
+ }
141
+
142
+ return { files, violations };
143
+ }
76
144
 
77
- const bridgeRef = 'scripts/lib/cursor-hook-bridge.mjs';
78
- const allPointAtBridge = declared.every((event) => {
79
- const entries = Array.isArray(hooksJson.hooks[event]) ? hooksJson.hooks[event] : [];
80
- return entries.some((entry) => typeof entry.command === 'string' && entry.command.includes(bridgeRef) && entry.command.includes(`--event ${event}`));
145
+ /**
146
+ * CLI entry point. Kept behind a direct-invocation guard so the module can be
147
+ * imported for {@link validateCursorArtefacts} without running (and exiting).
148
+ *
149
+ * @param {string|undefined} pluginRoot
150
+ * @returns {never}
151
+ */
152
+ function runCli(pluginRoot) {
153
+ if (!pluginRoot) {
154
+ console.error('Usage: check-cursor-adapter.mjs <plugin-root>');
155
+ process.exit(1);
156
+ }
157
+
158
+ let passed = 0;
159
+ let failed = 0;
160
+
161
+ function pass(msg) { console.log(` PASS: ${msg}`); passed += 1; }
162
+ function fail(msg) { console.log(` FAIL: ${msg}`); failed += 1; }
163
+
164
+ console.log('--- Check 1: generated Cursor command and skill wrappers ---');
165
+
166
+ const generator = join(pluginRoot, 'scripts', 'generate-cursor-adapter.mjs');
167
+ if (!existsSync(generator)) {
168
+ fail('scripts/generate-cursor-adapter.mjs exists');
169
+ } else {
170
+ const result = spawnSync(process.execPath, [generator, '--check'], {
171
+ cwd: pluginRoot,
172
+ encoding: 'utf8',
173
+ stdio: ['ignore', 'pipe', 'pipe'],
81
174
  });
82
- if (allPointAtBridge) {
83
- pass('.cursor/hooks.json commands invoke cursor-hook-bridge.mjs with --event');
175
+ if (result.status === 0) {
176
+ pass('.cursor/commands and .cursor/skills are up to date with commands/ and skills/');
84
177
  } else {
85
- fail('.cursor/hooks.json commands must invoke scripts/lib/cursor-hook-bridge.mjs --event <name>');
178
+ const detail = ((result.stdout ?? '') + (result.stderr ?? '')).trim();
179
+ fail(`Cursor adapter wrappers are stale${detail ? `: ${detail}` : ''}`);
86
180
  }
87
181
  }
88
- }
89
182
 
90
- console.log('');
91
- console.log('--- Check 3: Cursor hook bridge module ---');
183
+ console.log('');
184
+ console.log('--- Check 1b: generated artefacts conform to the frontmatter spec ---');
185
+
186
+ // Check 1 above runs the generator against its own output. That comparison is
187
+ // structurally blind to a defect the generator EMITS — it reported "up to
188
+ // date" while 24 of 28 command wrappers carried an array-shaped
189
+ // `argument-hint` (GH#54). Check 1b judges the artefacts against the spec
190
+ // instead, so a generator bug surfaces as a failure rather than as a snapshot.
191
+ const { files: artefactCount, violations } = validateCursorArtefacts(pluginRoot);
192
+ if (violations.length === 0) {
193
+ pass(`${artefactCount} generated Cursor artefact(s) have spec-conformant frontmatter`);
194
+ } else {
195
+ for (const violation of violations) process.stderr.write(`FAIL: ${violation}\n`);
196
+ fail(`${violations.length} generated Cursor artefact(s) violate the frontmatter spec (see stderr for file:line)`);
197
+ }
198
+
199
+ console.log('');
200
+ console.log('--- Check 2: .cursor/hooks.json native manifest ---');
201
+
202
+ const hooksPath = join(pluginRoot, '.cursor', 'hooks.json');
203
+ if (!existsSync(hooksPath)) {
204
+ fail('.cursor/hooks.json exists');
205
+ } else {
206
+ let hooksJson;
207
+ try {
208
+ hooksJson = JSON.parse(readFileSync(hooksPath, 'utf8'));
209
+ pass('.cursor/hooks.json is valid JSON');
210
+ } catch (err) {
211
+ fail(`.cursor/hooks.json is not valid JSON: ${err.message}`);
212
+ hooksJson = null;
213
+ }
214
+
215
+ if (hooksJson) {
216
+ if (hooksJson.version === 1) {
217
+ pass('.cursor/hooks.json version is 1');
218
+ } else {
219
+ fail('.cursor/hooks.json version must be 1');
220
+ }
221
+
222
+ const declared = Object.keys(hooksJson.hooks || {});
223
+ const expected = Object.keys(CURSOR_TO_CANONICAL_EVENT);
224
+ const missing = expected.filter((event) => !declared.includes(event));
225
+ const extra = declared.filter((event) => !expected.includes(event));
226
+ if (missing.length === 0 && extra.length === 0) {
227
+ pass(`.cursor/hooks.json events match CURSOR_TO_CANONICAL_EVENT (${expected.length})`);
228
+ } else {
229
+ if (missing.length > 0) fail(`.cursor/hooks.json missing events: ${missing.join(', ')}`);
230
+ if (extra.length > 0) fail(`.cursor/hooks.json extra events: ${extra.join(', ')}`);
231
+ }
232
+
233
+ const bridgeRef = 'scripts/lib/cursor-hook-bridge.mjs';
234
+ const allPointAtBridge = declared.every((event) => {
235
+ const entries = Array.isArray(hooksJson.hooks[event]) ? hooksJson.hooks[event] : [];
236
+ return entries.some((entry) => typeof entry.command === 'string' && entry.command.includes(bridgeRef) && entry.command.includes(`--event ${event}`));
237
+ });
238
+ if (allPointAtBridge) {
239
+ pass('.cursor/hooks.json commands invoke cursor-hook-bridge.mjs with --event');
240
+ } else {
241
+ fail('.cursor/hooks.json commands must invoke scripts/lib/cursor-hook-bridge.mjs --event <name>');
242
+ }
243
+ }
244
+ }
245
+
246
+ console.log('');
247
+ console.log('--- Check 3: Cursor hook bridge module ---');
248
+
249
+ const bridgePath = join(pluginRoot, 'scripts', 'lib', 'cursor-hook-bridge.mjs');
250
+ if (existsSync(bridgePath)) {
251
+ pass('scripts/lib/cursor-hook-bridge.mjs exists');
252
+ } else {
253
+ fail('scripts/lib/cursor-hook-bridge.mjs exists');
254
+ }
92
255
 
93
- const bridgePath = join(pluginRoot, 'scripts', 'lib', 'cursor-hook-bridge.mjs');
94
- if (existsSync(bridgePath)) {
95
- pass('scripts/lib/cursor-hook-bridge.mjs exists');
96
- } else {
97
- fail('scripts/lib/cursor-hook-bridge.mjs exists');
256
+ console.log('');
257
+ console.log(`Results: ${passed} passed, ${failed} failed`);
258
+ process.exit(failed > 0 ? 1 : 0);
98
259
  }
99
260
 
100
- console.log('');
101
- console.log(`Results: ${passed} passed, ${failed} failed`);
102
- process.exit(failed > 0 ? 1 : 0);
261
+ // 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
+ runCli(process.argv[2]);
264
+ }
@@ -66,11 +66,33 @@ const DOCUMENTED_ASYMMETRIES = {
66
66
  // A handler missing on a shared event and NOT listed here → FAIL.
67
67
  handlerAsymmetries: {
68
68
  codex: {
69
- // Codex 0.144.4 has no payload adapter (docs/codex-setup.md § Hook
70
- // Surface and Trust): Edit/Write handlers expect Claude payload shapes,
71
- // Bash guards expect tool_name === 'Bash'. Wiring them would be a silent
72
- // no-op, not enforcement (#919-P2 class) — "pretending the payloads are
73
- // compatible would create false enforcement".
69
+ // WHY these stay unwired corrected 2026-09-06 against the shipped
70
+ // codex-cli 0.144.4 binary, because the previous rationale here was
71
+ // measurably wrong and pointed at the wrong repair.
72
+ //
73
+ // REFUTED: "no Codex bridge delivers tool_name (grep scripts/lib/codex/
74
+ // *.mjs -> 0 matches)". That grep measured OUR adapter code, not the
75
+ // Codex payload contract. There is no bridge because none is needed:
76
+ // `pre-tool-use.command.input`, embedded verbatim in the binary, lists
77
+ // `tool_name` and `tool_input` among its REQUIRED fields (with `cwd`,
78
+ // `hook_event_name`, `model`, `permission_mode`, `session_id`,
79
+ // `tool_use_id`, `transcript_path`, `turn_id`). The output envelope is
80
+ // `hookSpecificOutput.{hookEventName,permissionDecision,
81
+ // permissionDecisionReason}` — byte-identical to what `emitDeny()`
82
+ // writes, and Codex's own error strings show it enforces exactly that
83
+ // shape ("PreToolUse hook returned permissionDecision:deny without a
84
+ // non-empty permissionDecisionReason").
85
+ //
86
+ // THE ACTUAL BLOCKER is the tool-name VOCABULARY, not the payload shape.
87
+ // Codex ships no `Bash`, `Edit`, `Write` or `MultiEdit` tool — it has
88
+ // `shell` / `exec_command` / `unified_exec` / `apply_patch`
89
+ // (`strings -a <codex> | grep -c '"Bash"'` -> 0). Every handler below
90
+ // opens with an equality gate on a Claude tool name and returns
91
+ // `emitAllow()` otherwise, so wiring one produces a hook that runs,
92
+ // matches nothing and allows everything: FALSE enforcement, which is
93
+ // strictly worse than the registered gap (#919-P2 class). The repair is
94
+ // a tool-name map (see CODEX_TOOL_NAMES in scripts/lib/codex/
95
+ // plugin-contract.mjs), not a manifest entry.
74
96
  PreToolUse: [
75
97
  // pre-task-scope-disjoint (#1020): NOT ported by construction. The hook
76
98
  // matches the `Agent` dispatch tool, which this platform does not have —
@@ -94,25 +116,32 @@ const DOCUMENTED_ASYMMETRIES = {
94
116
  'pre-bash-destructive-guard.mjs',
95
117
  'pre-bash-staging-fence.mjs',
96
118
  'pre-bash-memory-propose-audit.mjs',
97
- // pre-bash-sessions-ledger-guard (#958): same Bash-payload dependency
98
- // as the guards above — it reads tool_name === 'Bash' and
99
- // tool_input.command, neither of which any Codex bridge delivers
100
- // (measured 2026-07-31: `grep -rn "tool_name" scripts/lib/codex/*.mjs`
101
- // 0 matches, exit 1). A manifest entry would be a silent no-op, not
102
- // enforcement — #919-P2 class. Gap registered, not faked.
119
+ // pre-bash-sessions-ledger-guard (#958): same tool-name-vocabulary
120
+ // dependency as the guards above — it gates on tool_name === 'Bash',
121
+ // and Codex names its shell tools `shell` / `exec_command` /
122
+ // `unified_exec`. The fields it reads ARE delivered; the value it
123
+ // compares against never occurs. A manifest entry would be a silent
124
+ // no-op, not enforcement — #919-P2 class. Gap registered, not faked.
103
125
  'pre-bash-sessions-ledger-guard.mjs',
104
126
  'pre-bash-templates-first.mjs',
105
127
  'pre-bash-issue-budget.mjs',
106
128
  'enforce-commands.mjs',
107
129
  ],
108
- // post-bash-write-verify (#942): needs tool_name === 'Bash', which no
109
- // Codex bridge delivers documented exception until an adapter exists.
110
- // Measured 2026-07-31: `grep -rn "tool_name" scripts/lib/codex/*.mjs`
111
- // → 0 matches (exit 1), so a manifest entry here would be a silent
112
- // no-op, not enforcement. Same #919-P2 class as the PreToolUse block.
130
+ // post-bash-write-verify (#942): gates on tool_name === 'Bash', a value
131
+ // Codex never emits (its shell tools are `shell` / `exec_command` /
132
+ // `unified_exec`). Documented exception until a tool-name map exists —
133
+ // a manifest entry today would be a silent no-op, not enforcement. Same
134
+ // #919-P2 class as the PreToolUse block. `post-tool-use.command.input`
135
+ // at 0.144.4 does require `tool_name`, `tool_input` AND `tool_response`,
136
+ // so the port is a rename map, not a payload bridge.
113
137
  PostToolUse: [
114
138
  'post-edit-validate.mjs',
115
139
  'post-tooluse-frontend-slop.mjs',
140
+ // post-edit-import-probe (#1224): same missing Edit/Write payload
141
+ // adapter as post-edit-validate — the hook reads tool_name and
142
+ // tool_input.file_path, neither of which any Codex bridge delivers.
143
+ // A manifest entry would be a silent no-op, not enforcement.
144
+ 'post-edit-import-probe.mjs',
116
145
  'post-bash-write-verify.mjs',
117
146
  ],
118
147
  SubagentStart: ['subagent-telemetry.mjs'],