session-orchestrator 3.24.0 → 4.0.1

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 (435) 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 +3 -2
  47. package/.codex-plugin/skills/architecture/SKILL.md +20 -0
  48. package/.codex-plugin/skills/autopilot/SKILL.md +21 -0
  49. package/.codex-plugin/skills/autopilot/agents/openai.yaml +5 -0
  50. package/.codex-plugin/skills/bootstrap/SKILL.md +22 -0
  51. package/.codex-plugin/skills/bootstrap/agents/openai.yaml +5 -0
  52. package/.codex-plugin/skills/brainstorm/SKILL.md +22 -0
  53. package/.codex-plugin/skills/brainstorm/agents/openai.yaml +5 -0
  54. package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +17 -0
  55. package/.codex-plugin/skills/close/SKILL.md +21 -0
  56. package/.codex-plugin/skills/close/agents/openai.yaml +5 -0
  57. package/.codex-plugin/skills/convergence-monitoring/SKILL.md +24 -0
  58. package/.codex-plugin/skills/debug/SKILL.md +21 -0
  59. package/.codex-plugin/skills/debug/agents/openai.yaml +5 -0
  60. package/.codex-plugin/skills/discovery/SKILL.md +21 -0
  61. package/.codex-plugin/skills/discovery/agents/openai.yaml +5 -0
  62. package/.codex-plugin/skills/dispatcher/SKILL.md +21 -0
  63. package/.codex-plugin/skills/dispatcher/agents/openai.yaml +5 -0
  64. package/.codex-plugin/skills/docs-orchestrator/SKILL.md +20 -0
  65. package/.codex-plugin/skills/ecosystem-health/SKILL.md +22 -0
  66. package/.codex-plugin/skills/eli5/SKILL.md +21 -0
  67. package/.codex-plugin/skills/eli5/agents/openai.yaml +5 -0
  68. package/.codex-plugin/skills/eval/SKILL.md +21 -0
  69. package/.codex-plugin/skills/eval/agents/openai.yaml +5 -0
  70. package/.codex-plugin/skills/evolve/SKILL.md +21 -0
  71. package/.codex-plugin/skills/evolve/agents/openai.yaml +5 -0
  72. package/.codex-plugin/skills/frontmatter-guard/SKILL.md +17 -0
  73. package/.codex-plugin/skills/gitlab-ops/SKILL.md +22 -0
  74. package/.codex-plugin/skills/gitlab-portfolio/SKILL.md +17 -0
  75. package/.codex-plugin/skills/go/SKILL.md +22 -0
  76. package/.codex-plugin/skills/go/agents/openai.yaml +5 -0
  77. package/.codex-plugin/skills/grill/SKILL.md +21 -0
  78. package/.codex-plugin/skills/grill/agents/openai.yaml +5 -0
  79. package/.codex-plugin/skills/harness-audit/SKILL.md +19 -0
  80. package/.codex-plugin/skills/harness-audit/agents/openai.yaml +5 -0
  81. package/.codex-plugin/skills/hook-development/SKILL.md +17 -0
  82. package/.codex-plugin/skills/mcp-builder/SKILL.md +17 -0
  83. package/.codex-plugin/skills/memory-cleanup/SKILL.md +21 -0
  84. package/.codex-plugin/skills/memory-cleanup/agents/openai.yaml +5 -0
  85. package/.codex-plugin/skills/mode-selector/SKILL.md +19 -0
  86. package/.codex-plugin/skills/npm-publish/SKILL.md +18 -0
  87. package/.codex-plugin/skills/peekaboo-driver/SKILL.md +20 -0
  88. package/.codex-plugin/skills/persona-panel/SKILL.md +22 -0
  89. package/.codex-plugin/skills/persona-panel/agents/openai.yaml +5 -0
  90. package/.codex-plugin/skills/plan/SKILL.md +22 -0
  91. package/.codex-plugin/skills/plan/agents/openai.yaml +5 -0
  92. package/.codex-plugin/skills/playwright-driver/SKILL.md +22 -0
  93. package/.codex-plugin/skills/portfolio/SKILL.md +21 -0
  94. package/.codex-plugin/skills/portfolio/agents/openai.yaml +5 -0
  95. package/.codex-plugin/skills/quality-gates/SKILL.md +22 -0
  96. package/.codex-plugin/skills/reconcile/SKILL.md +21 -0
  97. package/.codex-plugin/skills/reconcile/agents/openai.yaml +5 -0
  98. package/.codex-plugin/skills/release/SKILL.md +22 -0
  99. package/.codex-plugin/skills/release/agents/openai.yaml +5 -0
  100. package/.codex-plugin/skills/remote-offload/SKILL.md +22 -0
  101. package/.codex-plugin/skills/repo-audit/SKILL.md +19 -0
  102. package/.codex-plugin/skills/repo-audit/agents/openai.yaml +5 -0
  103. package/.codex-plugin/skills/session/SKILL.md +21 -0
  104. package/.codex-plugin/skills/session/agents/openai.yaml +5 -0
  105. package/.codex-plugin/skills/session-end/SKILL.md +22 -0
  106. package/.codex-plugin/skills/session-plan/SKILL.md +22 -0
  107. package/.codex-plugin/skills/session-start/SKILL.md +22 -0
  108. package/.codex-plugin/skills/spinout/SKILL.md +21 -0
  109. package/.codex-plugin/skills/spinout/agents/openai.yaml +5 -0
  110. package/.codex-plugin/skills/sunset-review/SKILL.md +21 -0
  111. package/.codex-plugin/skills/sunset-review/agents/openai.yaml +5 -0
  112. package/.codex-plugin/skills/templates-ack/SKILL.md +21 -0
  113. package/.codex-plugin/skills/templates-ack/agents/openai.yaml +5 -0
  114. package/.codex-plugin/skills/test/SKILL.md +21 -0
  115. package/.codex-plugin/skills/test/agents/openai.yaml +5 -0
  116. package/.codex-plugin/skills/test-runner/SKILL.md +22 -0
  117. package/.codex-plugin/skills/tmux-layout/SKILL.md +23 -0
  118. package/.codex-plugin/skills/using-orchestrator/SKILL.md +19 -0
  119. package/.codex-plugin/skills/vault-mirror/SKILL.md +17 -0
  120. package/.codex-plugin/skills/vault-sync/SKILL.md +17 -0
  121. package/.codex-plugin/skills/wave-executor/SKILL.md +22 -0
  122. package/.codex-plugin/skills/write-executable-plan/SKILL.md +24 -0
  123. package/.cursor/commands/autopilot.md +2 -2
  124. package/.cursor/commands/bootstrap.md +1 -1
  125. package/.cursor/commands/brainstorm.md +1 -1
  126. package/.cursor/commands/debug.md +1 -1
  127. package/.cursor/commands/discovery.md +1 -1
  128. package/.cursor/commands/dispatcher.md +2 -2
  129. package/.cursor/commands/eli5.md +2 -2
  130. package/.cursor/commands/eval.md +2 -2
  131. package/.cursor/commands/evolve.md +1 -1
  132. package/.cursor/commands/go.md +1 -1
  133. package/.cursor/commands/grill.md +2 -2
  134. package/.cursor/commands/memory-cleanup.md +2 -2
  135. package/.cursor/commands/persona-panel.md +1 -1
  136. package/.cursor/commands/plan.md +1 -1
  137. package/.cursor/commands/portfolio.md +1 -1
  138. package/.cursor/commands/reconcile.md +2 -2
  139. package/.cursor/commands/release.md +2 -2
  140. package/.cursor/commands/session.md +2 -2
  141. package/.cursor/commands/spinout.md +2 -2
  142. package/.cursor/commands/sunset-review.md +2 -2
  143. package/.cursor/commands/templates-ack.md +2 -2
  144. package/.cursor/commands/test.md +2 -2
  145. package/.cursor/skills/brainstorm/SKILL.md +1 -1
  146. package/.cursor/skills/eval/SKILL.md +1 -1
  147. package/.cursor/skills/quality-gates/SKILL.md +1 -1
  148. package/.cursor/skills/remote-offload/SKILL.md +1 -1
  149. package/.cursor-plugin/plugin.json +30 -0
  150. package/.orchestrator/policy/blocked-commands.json +121 -0
  151. package/.orchestrator/policy/ecosystem.schema.json +66 -0
  152. package/.orchestrator/policy/quality-gates.example.json +16 -0
  153. package/.orchestrator/policy/quality-gates.schema.json +38 -0
  154. package/.orchestrator/policy/templates-policy.json +27 -0
  155. package/.orchestrator/policy/test-profiles.json +47 -0
  156. package/AGENTS.md +225 -0
  157. package/CHANGELOG.md +1314 -2
  158. package/NOTICE +11 -6
  159. package/README.md +135 -94
  160. package/agents/eval-judge.md +1 -1
  161. package/agents/skill-applied-judge.md +1 -1
  162. package/assets/wave-lifecycle.svg +98 -0
  163. package/commands/release.md +6 -3
  164. package/commands/session.md +18 -3
  165. package/docs/README.md +4 -0
  166. package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
  167. package/docs/baseline.md +67 -0
  168. package/docs/ci-setup.md +108 -62
  169. package/docs/codex-setup.md +107 -29
  170. package/docs/components.md +38 -16
  171. package/docs/cursor-setup.md +6 -2
  172. package/docs/events-schema.md +9 -6
  173. package/docs/instruction-delivery.md +69 -0
  174. package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
  175. package/docs/migration-v4.md +365 -0
  176. package/docs/pi-setup.md +6 -1
  177. package/docs/plugin-architecture-v3.md +1 -1
  178. package/docs/rule-authoring.md +85 -19
  179. package/docs/scope-collision-guard.md +5 -5
  180. package/docs/session-config-reference.md +57 -56
  181. package/docs/session-config-template.md +6 -29
  182. package/docs/telemetry.md +157 -3
  183. package/docs/vault-docs-architecture.md +50 -11
  184. package/hooks/_lib/hook-import-set.json +1488 -0
  185. package/hooks/_lib/subagent-transcript.mjs +562 -0
  186. package/hooks/config-protection.mjs +2 -2
  187. package/hooks/cwd-change-restore.mjs +2 -2
  188. package/hooks/enforce-commands.mjs +69 -0
  189. package/hooks/hooks-codex.json +1 -1
  190. package/hooks/hooks-cursor.json +10 -0
  191. package/hooks/hooks-pi.json +5 -0
  192. package/hooks/hooks.json +6 -1
  193. package/hooks/loop-guard.mjs +3 -3
  194. package/hooks/on-session-end.mjs +2 -2
  195. package/hooks/on-session-start.mjs +103 -2
  196. package/hooks/on-stop.mjs +60 -14
  197. package/hooks/operator-steer.mjs +2 -2
  198. package/hooks/post-bash-write-verify.mjs +85 -0
  199. package/hooks/post-edit-import-probe.mjs +344 -0
  200. package/hooks/post-subagent-discovery-validator.mjs +187 -431
  201. package/hooks/post-tool-batch-wave-signal.mjs +118 -4
  202. package/hooks/post-tool-failure-corrective-context.mjs +2 -2
  203. package/hooks/post-tooluse-frontend-slop.mjs +3 -3
  204. package/hooks/pre-bash-destructive-guard.mjs +39 -13
  205. package/hooks/skill-invocation-telemetry.mjs +17 -5
  206. package/hooks/subagent-telemetry.mjs +13 -4
  207. package/monitors/monitors.json +3 -3
  208. package/package.json +9 -1
  209. package/pi/prompts/session.md +2 -2
  210. package/scripts/backfill-abandoned-sessions.mjs +50 -4
  211. package/scripts/backfill-learnings-from-vault.mjs +9 -3
  212. package/scripts/dialectic-deriver.mjs +73 -8
  213. package/scripts/export-hw-learnings.mjs +113 -1
  214. package/scripts/generate-agents-skills.mjs +378 -0
  215. package/scripts/generate-codex-skills.mjs +246 -0
  216. package/scripts/generate-cursor-adapter.mjs +45 -8
  217. package/scripts/generate-hook-import-set.mjs +292 -0
  218. package/scripts/lib/agent-status.mjs +13 -2
  219. package/scripts/lib/auto-dream.mjs +38 -36
  220. package/scripts/lib/autonomy/suitability.mjs +6 -0
  221. package/scripts/lib/autopilot/loop.mjs +2 -2
  222. package/scripts/lib/ci-status-banner.mjs +220 -75
  223. package/scripts/lib/codex/plugin-contract.mjs +88 -6
  224. package/scripts/lib/config/auto-dream.mjs +2 -1
  225. package/scripts/lib/config/block-header.mjs +8 -0
  226. package/scripts/lib/config/block-preprocess.mjs +177 -0
  227. package/scripts/lib/config/broken-window.mjs +2 -1
  228. package/scripts/lib/config/cold-start.mjs +2 -1
  229. package/scripts/lib/config/config-protection.mjs +22 -2
  230. package/scripts/lib/config/context-coverage.mjs +2 -1
  231. package/scripts/lib/config/cross-repo.mjs +2 -1
  232. package/scripts/lib/config/custom-phases.mjs +2 -1
  233. package/scripts/lib/config/dialectic.mjs +2 -1
  234. package/scripts/lib/config/discovery-validator.mjs +2 -1
  235. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
  236. package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
  237. package/scripts/lib/config/docs-orchestrator.mjs +2 -1
  238. package/scripts/lib/config/docs-staleness.mjs +2 -1
  239. package/scripts/lib/config/drift-check.mjs +2 -1
  240. package/scripts/lib/config/eval.mjs +2 -1
  241. package/scripts/lib/config/events-rotation.mjs +2 -1
  242. package/scripts/lib/config/evolve.mjs +8 -2
  243. package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
  244. package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
  245. package/scripts/lib/config/handover-gate.mjs +2 -1
  246. package/scripts/lib/config/health-endpoints.mjs +7 -2
  247. package/scripts/lib/config/host-paths.mjs +20 -4
  248. package/scripts/lib/config/issue-budget.mjs +2 -1
  249. package/scripts/lib/config/loop-guard.mjs +2 -1
  250. package/scripts/lib/config/memory.mjs +2 -1
  251. package/scripts/lib/config/moc-staleness.mjs +2 -1
  252. package/scripts/lib/config/persona-gate-wave.mjs +2 -1
  253. package/scripts/lib/config/private-config-dir.mjs +67 -0
  254. package/scripts/lib/config/reconcile.mjs +2 -1
  255. package/scripts/lib/config/remote-hosts.mjs +2 -1
  256. package/scripts/lib/config/section-extractor.mjs +7 -1
  257. package/scripts/lib/config/skill-evolution.mjs +2 -1
  258. package/scripts/lib/config/slopcheck.mjs +2 -1
  259. package/scripts/lib/config/state-md-lock.mjs +2 -1
  260. package/scripts/lib/config/templates-first.mjs +2 -1
  261. package/scripts/lib/config/test.mjs +2 -1
  262. package/scripts/lib/config/vault-integration.mjs +7 -1
  263. package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
  264. package/scripts/lib/config/vault-staleness.mjs +2 -1
  265. package/scripts/lib/config/vault-sync.mjs +2 -1
  266. package/scripts/lib/config/verification-auto-fix.mjs +2 -1
  267. package/scripts/lib/config/wave-reviewers.mjs +2 -1
  268. package/scripts/lib/config/worktree-orphans.mjs +2 -1
  269. package/scripts/lib/convergence-monitor.mjs +82 -16
  270. package/scripts/lib/dispatcher/rank.mjs +124 -48
  271. package/scripts/lib/ecosystem-health.mjs +16 -2
  272. package/scripts/lib/eval/engine.mjs +9 -1
  273. package/scripts/lib/eval/session-resolve.mjs +23 -4
  274. package/scripts/lib/events.mjs +22 -6
  275. package/scripts/lib/frontmatter-guard.mjs +131 -13
  276. package/scripts/lib/gates/gate-full.mjs +30 -0
  277. package/scripts/lib/gates/gate-helpers.mjs +76 -0
  278. package/scripts/lib/hardware-pattern-detector.mjs +18 -1
  279. package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
  280. package/scripts/lib/host-identity.mjs +50 -11
  281. package/scripts/lib/instruction-budget-guard.mjs +171 -5
  282. package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
  283. package/scripts/lib/learnings/io.mjs +60 -6
  284. package/scripts/lib/memory-proposals/store.mjs +30 -22
  285. package/scripts/lib/owner-config-banner.mjs +41 -6
  286. package/scripts/lib/owner-config-loader.mjs +21 -10
  287. package/scripts/lib/owner-interview.mjs +3 -3
  288. package/scripts/lib/owner-yaml.mjs +215 -15
  289. package/scripts/lib/platform.mjs +108 -15
  290. package/scripts/lib/plugin-update-banner.mjs +414 -0
  291. package/scripts/lib/project-hygiene.mjs +38 -2
  292. package/scripts/lib/qg-command-drift-banner.mjs +50 -12
  293. package/scripts/lib/quality-gate.mjs +133 -44
  294. package/scripts/lib/reconcile/emitter.mjs +68 -6
  295. package/scripts/lib/reconcile/engine.mjs +51 -11
  296. package/scripts/lib/reconcile/idempotency.mjs +37 -4
  297. package/scripts/lib/reconcile/writer.mjs +40 -18
  298. package/scripts/lib/session-close-backfill.mjs +67 -9
  299. package/scripts/lib/session-id.mjs +12 -23
  300. package/scripts/lib/session-identity/own-session.mjs +125 -10
  301. package/scripts/lib/session-lock-shape.mjs +43 -0
  302. package/scripts/lib/session-lock.mjs +5 -10
  303. package/scripts/lib/session-registry.mjs +25 -9
  304. package/scripts/lib/session-schema/constants.mjs +64 -3
  305. package/scripts/lib/session-schema/validator.mjs +38 -4
  306. package/scripts/lib/session-start-probes.mjs +30 -1
  307. package/scripts/lib/sessions-staleness-banner.mjs +18 -11
  308. package/scripts/lib/skill-health/join.mjs +17 -4
  309. package/scripts/lib/state-md.mjs +78 -0
  310. package/scripts/lib/sunset/walker.mjs +6 -0
  311. package/scripts/lib/telemetry/schema.mjs +202 -9
  312. package/scripts/lib/telemetry/sync.mjs +368 -12
  313. package/scripts/lib/telemetry-flush-health-banner.mjs +211 -0
  314. package/scripts/lib/validate/check-agents-skills.mjs +327 -0
  315. package/scripts/lib/validate/check-agents.mjs +3 -3
  316. package/scripts/lib/validate/check-codex-skills.mjs +191 -0
  317. package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
  318. package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
  319. package/scripts/lib/validate/check-owner-leakage.mjs +319 -22
  320. package/scripts/lib/validate/check-skill-links.mjs +193 -0
  321. package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
  322. package/scripts/lib/validate/check-test-git-config-target.mjs +192 -12
  323. package/scripts/lib/validate/check-unwired-features.mjs +163 -15
  324. package/scripts/lib/validate/check-validator-registration.mjs +10 -4
  325. package/scripts/lib/validate/confidential-names.mjs +95 -30
  326. package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
  327. package/scripts/lib/validate/repo-files.mjs +48 -14
  328. package/scripts/lib/vault-backfill/template.mjs +63 -6
  329. package/scripts/lib/vault-mirror/process.mjs +165 -42
  330. package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
  331. package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
  332. package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
  333. package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
  334. package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
  335. package/scripts/lib/wave-resource-gate.mjs +8 -2
  336. package/scripts/lib/wave-sizing.mjs +4 -1
  337. package/scripts/lib/wave-transcript-tail.mjs +118 -4
  338. package/scripts/materialize-wave-scope.mjs +12 -5
  339. package/scripts/memory-propose.mjs +19 -5
  340. package/scripts/migrate-cold-start-seed.mjs +4 -1
  341. package/scripts/parse-config.mjs +60 -3
  342. package/scripts/release.mjs +430 -31
  343. package/scripts/repair-invalid-sessions.mjs +3 -3
  344. package/scripts/run-quality-gate.mjs +128 -11
  345. package/scripts/site-numbers.mjs +344 -8
  346. package/scripts/sweep-expired-learnings.mjs +90 -0
  347. package/scripts/sync-vault-schema.mjs +3 -1
  348. package/scripts/telemetry.mjs +2 -2
  349. package/scripts/validate-plugin.mjs +164 -0
  350. package/scripts/validate-wave-scope.mjs +28 -8
  351. package/scripts/wave-scope-binding.mjs +215 -0
  352. package/skills/_shared/instruction-file-resolution.md +10 -0
  353. package/skills/_shared/parallel-aware-preamble.md +1 -0
  354. package/skills/_shared/platform-tools.md +1 -1
  355. package/skills/_shared/state-ownership.md +1 -1
  356. package/skills/architecture/SKILL.md +7 -5
  357. package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
  358. package/skills/autopilot/SKILL.md +4 -18
  359. package/skills/claude-md-drift-check/SKILL.md +5 -1
  360. package/skills/claude-md-drift-check/checker.mjs +62 -2
  361. package/skills/convergence-monitoring/SIGNALS.md +55 -0
  362. package/skills/discovery/probes/vault-staleness.mjs +37 -13
  363. package/skills/discovery/probes-arch.md +20 -18
  364. package/skills/dispatcher/SKILL.md +3 -2
  365. package/skills/evolve/SKILL.md +65 -26
  366. package/skills/frontmatter-guard/SKILL.md +11 -5
  367. package/skills/npm-publish/SKILL.md +1 -1
  368. package/skills/reconcile/SKILL.md +33 -0
  369. package/skills/remote-offload/SKILL.md +1 -1
  370. package/skills/session-end/SKILL.md +18 -905
  371. package/skills/session-end/phase-3-6-tail.md +10 -3
  372. package/skills/session-end/plan-verification.md +221 -155
  373. package/skills/session-end/references/phase-2-quality-gate.md +93 -0
  374. package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
  375. package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
  376. package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
  377. package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
  378. package/skills/session-end/references/session-summary-template.md +62 -0
  379. package/skills/session-plan/SKILL.md +49 -0
  380. package/skills/session-start/SKILL.md +22 -904
  381. package/skills/session-start/phase-8-5-express-path.md +1 -1
  382. package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
  383. package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
  384. package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
  385. package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
  386. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
  387. package/skills/session-start/references/phase-4-ssot-environment-check.md +160 -0
  388. package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
  389. package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
  390. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
  391. package/skills/vault-sync/SKILL.md +10 -0
  392. package/skills/vault-sync/validator.mjs +21 -27
  393. package/skills/wave-executor/SKILL.md +15 -1
  394. package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
  395. package/skills/wave-executor/references/wave-loop-review.md +570 -0
  396. package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
  397. package/skills/wave-executor/wave-loop.md +14 -1309
  398. package/templates/_shared/journey-manifest.md +10 -6
  399. package/.cursor/commands/autopilot-multi.md +0 -14
  400. package/.cursor/commands/contract-version-bump.md +0 -14
  401. package/.cursor/commands/journey-audit.md +0 -14
  402. package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
  403. package/.cursor/skills/daily/SKILL.md +0 -12
  404. package/.cursor/skills/domain-model/SKILL.md +0 -13
  405. package/.cursor/skills/journey-audit/SKILL.md +0 -13
  406. package/.cursor/skills/skill-creator/SKILL.md +0 -13
  407. package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
  408. package/commands/autopilot-multi.md +0 -74
  409. package/commands/contract-version-bump.md +0 -28
  410. package/commands/journey-audit.md +0 -43
  411. package/pi/prompts/autopilot-multi.md +0 -12
  412. package/pi/prompts/contract-version-bump.md +0 -12
  413. package/pi/prompts/journey-audit.md +0 -12
  414. package/scripts/autopilot-multi.mjs +0 -885
  415. package/scripts/backfill-learnings-expires.mjs +0 -196
  416. package/scripts/backfill-learnings.mjs +0 -203
  417. package/scripts/fleet-instruction-scan.mjs +0 -141
  418. package/scripts/lib/autopilot/dep-graph.mjs +0 -417
  419. package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
  420. package/scripts/lib/webhook-url.mjs +0 -105
  421. package/scripts/lifecycle-sim-v6.mjs +0 -347
  422. package/scripts/migrate-learnings-jsonl.mjs +0 -189
  423. package/scripts/migrate-subagents-jsonl.mjs +0 -196
  424. package/scripts/upload-social-preview.mjs +0 -316
  425. package/skills/_shared/model-selection.md +0 -64
  426. package/skills/contract-version-bump/SKILL.md +0 -219
  427. package/skills/daily/SKILL.md +0 -222
  428. package/skills/daily/generate.sh +0 -92
  429. package/skills/daily/templates/daily.md.tpl +0 -36
  430. package/skills/journey-audit/SKILL.md +0 -270
  431. package/skills/skill-creator/SKILL.md +0 -168
  432. package/skills/ubiquitous-language/SKILL.md +0 -97
  433. package/skills/vault-sync/package-lock.json +0 -40
  434. /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
  435. /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
@@ -0,0 +1,378 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * generate-agents-skills.mjs — generate the CROSS-HARNESS PORTABLE SURFACE.
4
+ *
5
+ * 7 of 8 surveyed agent harnesses read `AGENTS.md` and/or a `<dir>/skills/
6
+ * <name>/SKILL.md` layout; only Claude Code reads `CLAUDE.md` + a plugin skill
7
+ * directory (Copilot CLI reads both). Measured 2026-09-06 in this repo:
8
+ * `git ls-files | grep -i AGENTS.md` returned only `agents/AGENTS.md` (the
9
+ * sub-agent authoring spec) — i.e. this repository opened in Codex CLI,
10
+ * OpenCode, Kiro, Amp or Cursor could not find its own `## Session Config`.
11
+ *
12
+ * This generator writes two artefacts, both DERIVED and never hand-edited:
13
+ *
14
+ * 1. `AGENTS.md` — a BYTE-IDENTICAL copy of `CLAUDE.md`.
15
+ * 2. `.agents/skills/<name>/SKILL.md` — a portable mirror of each
16
+ * `skills/<name>/SKILL.md`, carrying ONLY agentskills.io-spec-legal
17
+ * frontmatter plus a pointer body (progressive disclosure — the mirror
18
+ * never duplicates the canonical instructions).
19
+ *
20
+ * ## Why a generated copy and NOT a symlink (measured, not preferred)
21
+ *
22
+ * A symlink `AGENTS.md -> CLAUDE.md` is the smaller diff, and it was rejected
23
+ * on three measurements taken 2026-09-06:
24
+ *
25
+ * - `package.json` `files[]` does NOT list `CLAUDE.md`, so the published npm
26
+ * tarball would carry a DANGLING symlink. The published artefact is the
27
+ * one a foreign harness consumes.
28
+ * - Git's `core.symlinks` defaults to FALSE on Windows without Developer
29
+ * Mode / admin (`git config core.symlinks` is unset here — exit 1 — i.e.
30
+ * the platform default applies). On such a checkout git materialises the
31
+ * link as a regular file whose entire content is the literal target path
32
+ * `CLAUDE.md` — a 10-byte file with zero Session Config, which is exactly
33
+ * the "pointer file that defeats the purpose" failure mode. The same
34
+ * applies to GitHub's Download-ZIP archives.
35
+ * - `git ls-files -s | awk '$1=="120000"'` returned ZERO rows: this repo has
36
+ * no symlink precedent, and it is itself a template other repos copy.
37
+ *
38
+ * A generated copy costs a drift gate, which `--check` provides and
39
+ * `scripts/validate-plugin.mjs` runs. It carries NO "do not edit" banner on
40
+ * purpose: byte-identity makes the drift-check's whole error class ("the two
41
+ * instruction files disagree") impossible to express, and the `--check`
42
+ * failure message carries the do-not-edit instruction at the moment it
43
+ * matters. See `skills/_shared/instruction-file-resolution.md`.
44
+ *
45
+ * ## Usage
46
+ * node scripts/generate-agents-skills.mjs [--plugin-root <dir>] [--check] [--json]
47
+ *
48
+ * Exit codes: 0 — written / in sync · 1 — drift (with `--check`) or write error.
49
+ */
50
+
51
+ import { readFileSync, writeFileSync, existsSync, statSync, lstatSync, mkdirSync, readdirSync, rmSync, realpathSync } from 'node:fs';
52
+ import path from 'node:path';
53
+ import { fileURLToPath } from 'node:url';
54
+ import { execFileSync } from 'node:child_process';
55
+ import yaml from 'js-yaml';
56
+
57
+ /**
58
+ * The complete set of SKILL.md frontmatter keys agentskills.io permits outside
59
+ * Claude Code. Anything else is folded into `metadata` (as a string) or dropped.
60
+ * @type {readonly string[]}
61
+ */
62
+ export const PORTABLE_KEYS = Object.freeze([
63
+ 'name', 'description', 'license', 'compatibility', 'metadata', 'allowed-tools',
64
+ ]);
65
+
66
+ /**
67
+ * Source keys that map onto a spec key rather than into `metadata`.
68
+ * `tools:` is our own (non-spec) spelling of `allowed-tools:` — 7 SKILL.md
69
+ * files use it, measured 2026-09-06.
70
+ * @type {Record<string, string>}
71
+ */
72
+ const KEY_ALIASES = { tools: 'allowed-tools' };
73
+
74
+ /**
75
+ * agentskills.io caps `description` at 1024 characters. Measured max across the
76
+ * 43 source skills on 2026-09-06: 1012 (`sunset-review`) — i.e. nothing is
77
+ * truncated today. Revisit trigger: if this generator starts reporting
78
+ * `description-truncated` warnings, shorten the SOURCE description instead of
79
+ * raising this constant; the cap is the spec's, not ours.
80
+ */
81
+ export const DESCRIPTION_MAX = 1024;
82
+
83
+ /** Stringify a YAML scalar/array/object for the string-valued `metadata` map. */
84
+ function toMetadataString(value) {
85
+ if (value === null || value === undefined) return '';
86
+ if (Array.isArray(value)) {
87
+ return value.every((v) => typeof v !== 'object' || v === null)
88
+ ? value.map((v) => String(v)).join(', ')
89
+ : JSON.stringify(value);
90
+ }
91
+ if (typeof value === 'object') return JSON.stringify(value);
92
+ return String(value);
93
+ }
94
+
95
+ /**
96
+ * Project one source SKILL.md frontmatter object onto the spec-legal subset.
97
+ *
98
+ * @param {Record<string, unknown>} source - parsed source frontmatter
99
+ * @param {string} skillName - directory name, used when `name:` is absent
100
+ * @returns {{ fm: Record<string, unknown>, warnings: string[] }}
101
+ */
102
+ export function toPortableFrontmatter(source, skillName) {
103
+ const warnings = [];
104
+ /** @type {Record<string, unknown>} */
105
+ const out = {};
106
+ /** @type {Record<string, string>} */
107
+ const metadata = {};
108
+
109
+ out.name = typeof source.name === 'string' && source.name.trim() ? source.name.trim() : skillName;
110
+
111
+ // Descriptions are authored as YAML block scalars (`description: >`), so they
112
+ // arrive with embedded newlines. Collapse to one line: the spec's field is a
113
+ // single-line summary and a folded scalar round-trips unpredictably.
114
+ const desc = String(source.description ?? '').replace(/\s+/g, ' ').trim();
115
+ if (!desc) warnings.push(`${skillName}: source has no description`);
116
+ out.description = desc.length > DESCRIPTION_MAX
117
+ ? (warnings.push(`${skillName}: description-truncated (${desc.length} > ${DESCRIPTION_MAX})`),
118
+ `${desc.slice(0, DESCRIPTION_MAX - 1)}…`)
119
+ : desc;
120
+
121
+ for (const [rawKey, value] of Object.entries(source)) {
122
+ if (rawKey === 'name' || rawKey === 'description') continue;
123
+ const key = KEY_ALIASES[rawKey] ?? rawKey;
124
+ if (key === 'allowed-tools') {
125
+ const list = Array.isArray(value)
126
+ ? value.map((v) => String(v).trim())
127
+ : String(value).split(',').map((v) => v.trim());
128
+ const tools = list.filter(Boolean);
129
+ if (tools.length > 0) out['allowed-tools'] = tools;
130
+ continue;
131
+ }
132
+ if (key === 'license' || key === 'compatibility') {
133
+ out[key] = value;
134
+ continue;
135
+ }
136
+ if (key === 'metadata' && value && typeof value === 'object' && !Array.isArray(value)) {
137
+ for (const [mk, mv] of Object.entries(value)) metadata[mk] = toMetadataString(mv);
138
+ continue;
139
+ }
140
+ const asString = toMetadataString(value);
141
+ if (asString !== '') metadata[rawKey] = asString;
142
+ }
143
+
144
+ if (Object.keys(metadata).length > 0) out.metadata = metadata;
145
+
146
+ // Emit in the spec's own field order so the mirror reads the same everywhere.
147
+ /** @type {Record<string, unknown>} */
148
+ const ordered = {};
149
+ for (const k of PORTABLE_KEYS) if (k in out) ordered[k] = out[k];
150
+ return { fm: ordered, warnings };
151
+ }
152
+
153
+ /**
154
+ * Render one mirror file: spec-legal frontmatter + a pointer body.
155
+ *
156
+ * The body deliberately does NOT restate the canonical instructions
157
+ * (progressive disclosure): a foreign harness discovers the skill from the
158
+ * frontmatter and reads the real body only when it invokes the skill.
159
+ *
160
+ * @param {string} skillName
161
+ * @param {Record<string, unknown>} portableFm
162
+ * @returns {string}
163
+ */
164
+ export function renderMirror(skillName, portableFm) {
165
+ const front = yaml.dump(portableFm, { lineWidth: -1, noRefs: true, quotingType: '"' });
166
+ const canonical = `skills/${skillName}/SKILL.md`;
167
+ return [
168
+ '---',
169
+ front.trimEnd(),
170
+ '---',
171
+ '',
172
+ `# ${skillName}`,
173
+ '',
174
+ `> **Portable mirror — generated, do not edit.** The canonical skill body lives at`,
175
+ `> [\`${canonical}\`](../../../${canonical}); read that file for the full instructions.`,
176
+ `> This mirror carries only agentskills.io-spec-legal frontmatter so harnesses that`,
177
+ `> discover skills under \`.agents/skills/\` can find and route to the skill.`,
178
+ '>',
179
+ '> Regenerate with `node scripts/generate-agents-skills.mjs`.',
180
+ '',
181
+ ].join('\n');
182
+ }
183
+
184
+ /** Parse a SKILL.md's YAML frontmatter block; returns `{}` when absent/unparseable. */
185
+ function readFrontmatter(absPath) {
186
+ const content = readFileSync(absPath, 'utf8');
187
+ const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
188
+ if (!m) return null;
189
+ try {
190
+ const parsed = yaml.load(m[1]);
191
+ return parsed && typeof parsed === 'object' ? /** @type {Record<string, unknown>} */ (parsed) : {};
192
+ } catch {
193
+ return null;
194
+ }
195
+ }
196
+
197
+ /** List `skills/<name>` directories that carry a SKILL.md, sorted. */
198
+ export function listSourceSkills(pluginRoot) {
199
+ const dir = path.join(pluginRoot, 'skills');
200
+ if (!existsSync(dir) || !statSync(dir).isDirectory()) return [];
201
+ return readdirSync(dir, { withFileTypes: true })
202
+ .filter((e) => e.isDirectory() && !e.name.startsWith('.') && !e.name.startsWith('_'))
203
+ .map((e) => e.name)
204
+ .filter((n) => existsSync(path.join(dir, n, 'SKILL.md')))
205
+ .sort();
206
+ }
207
+
208
+ /**
209
+ * Is `AGENTS.md` already an alias of `CLAUDE.md` by construction?
210
+ *
211
+ * Two shapes count as satisfied: a symlink (or hardlink) resolving to the same
212
+ * inode, and a byte-identical regular file. The symlink branch exists because a
213
+ * CONSUMER repo may legitimately choose it even though this repo does not (see
214
+ * the header) — the invariant is "the two files cannot disagree", not "the two
215
+ * files were produced by this generator".
216
+ *
217
+ * @param {string} claudePath
218
+ * @param {string} agentsPath
219
+ * @returns {{ satisfied: boolean, kind: 'absent'|'symlink'|'identical'|'divergent' }}
220
+ */
221
+ export function agentsMdAliasState(claudePath, agentsPath) {
222
+ if (!existsSync(claudePath)) return { satisfied: false, kind: 'absent' };
223
+ if (!existsSync(agentsPath)) return { satisfied: false, kind: 'absent' };
224
+ try {
225
+ if (lstatSync(agentsPath).isSymbolicLink()) {
226
+ if (realpathSync(agentsPath) === realpathSync(claudePath)) return { satisfied: true, kind: 'symlink' };
227
+ return { satisfied: false, kind: 'divergent' };
228
+ }
229
+ const a = statSync(agentsPath);
230
+ const c = statSync(claudePath);
231
+ if (a.ino !== 0 && a.ino === c.ino && a.dev === c.dev) return { satisfied: true, kind: 'symlink' };
232
+ } catch {
233
+ // fall through to a byte comparison
234
+ }
235
+ const same = readFileSync(agentsPath).equals(readFileSync(claudePath));
236
+ return { satisfied: same, kind: same ? 'identical' : 'divergent' };
237
+ }
238
+
239
+ /**
240
+ * Generate (or, with `check`, verify) the whole portable surface.
241
+ *
242
+ * @param {{ pluginRoot: string, check?: boolean }} opts
243
+ * @returns {{ ok: boolean, drift: string[], warnings: string[], written: string[], files: number, bytes: number, skills: string[] }}
244
+ */
245
+ export function generateAgentsSurface({ pluginRoot, check = false }) {
246
+ const drift = [];
247
+ const warnings = [];
248
+ const written = [];
249
+
250
+ // --- Artefact 1: root AGENTS.md ------------------------------------------
251
+ const claudePath = path.join(pluginRoot, 'CLAUDE.md');
252
+ const agentsPath = path.join(pluginRoot, 'AGENTS.md');
253
+ if (existsSync(claudePath)) {
254
+ const state = agentsMdAliasState(claudePath, agentsPath);
255
+ if (!state.satisfied) {
256
+ if (check) {
257
+ drift.push(state.kind === 'absent'
258
+ ? 'AGENTS.md is missing (foreign harnesses — Codex CLI, OpenCode, Kiro, Amp — read AGENTS.md and never CLAUDE.md)'
259
+ : 'AGENTS.md differs from CLAUDE.md — AGENTS.md is GENERATED; put the change in CLAUDE.md, never in AGENTS.md');
260
+ } else {
261
+ writeFileSync(agentsPath, readFileSync(claudePath));
262
+ written.push('AGENTS.md');
263
+ }
264
+ }
265
+ } else {
266
+ warnings.push('CLAUDE.md not found — AGENTS.md not generated');
267
+ }
268
+
269
+ // --- Artefact 2: .agents/skills/<name>/SKILL.md ---------------------------
270
+ const mirrorRoot = path.join(pluginRoot, '.agents', 'skills');
271
+ const skills = listSourceSkills(pluginRoot);
272
+ let files = 0;
273
+ let bytes = 0;
274
+
275
+ for (const name of skills) {
276
+ const src = path.join(pluginRoot, 'skills', name, 'SKILL.md');
277
+ const fm = readFrontmatter(src);
278
+ if (fm === null) {
279
+ warnings.push(`${name}: SKILL.md has no parseable frontmatter — skipped`);
280
+ continue;
281
+ }
282
+ const { fm: portable, warnings: w } = toPortableFrontmatter(fm, name);
283
+ warnings.push(...w);
284
+ const rendered = renderMirror(name, portable);
285
+ const dest = path.join(mirrorRoot, name, 'SKILL.md');
286
+ files += 1;
287
+ bytes += Buffer.byteLength(rendered, 'utf8');
288
+
289
+ const current = existsSync(dest) ? readFileSync(dest, 'utf8') : null;
290
+ if (current === rendered) continue;
291
+ if (check) {
292
+ drift.push(current === null
293
+ ? `.agents/skills/${name}/SKILL.md is missing`
294
+ : `.agents/skills/${name}/SKILL.md is stale`);
295
+ } else {
296
+ mkdirSync(path.dirname(dest), { recursive: true });
297
+ writeFileSync(dest, rendered, 'utf8');
298
+ written.push(`.agents/skills/${name}/SKILL.md`);
299
+ }
300
+ }
301
+
302
+ // Orphans: a mirror whose source skill was renamed or removed. Left behind,
303
+ // it advertises a skill no harness can route to.
304
+ if (existsSync(mirrorRoot)) {
305
+ const known = new Set(skills);
306
+ for (const entry of readdirSync(mirrorRoot, { withFileTypes: true })) {
307
+ if (!entry.isDirectory() || known.has(entry.name)) continue;
308
+ if (check) {
309
+ drift.push(`.agents/skills/${entry.name}/ is an orphan (no skills/${entry.name}/SKILL.md)`);
310
+ } else {
311
+ rmSync(path.join(mirrorRoot, entry.name), { recursive: true, force: true });
312
+ written.push(`removed .agents/skills/${entry.name}/`);
313
+ }
314
+ }
315
+ }
316
+
317
+ return { ok: drift.length === 0, drift, warnings, written, files, bytes, skills };
318
+ }
319
+
320
+ // ---------------------------------------------------------------------------
321
+ // CLI
322
+ // ---------------------------------------------------------------------------
323
+
324
+ /** Resolve the repo root the same way validate-plugin.mjs does. */
325
+ function defaultRoot() {
326
+ try {
327
+ return execFileSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' }).trim();
328
+ } catch {
329
+ return process.cwd();
330
+ }
331
+ }
332
+
333
+ function main(argv) {
334
+ const args = { check: false, json: false, pluginRoot: null };
335
+ for (let i = 0; i < argv.length; i++) {
336
+ if (argv[i] === '--check') args.check = true;
337
+ else if (argv[i] === '--json') args.json = true;
338
+ else if (argv[i] === '--plugin-root') args.pluginRoot = argv[++i];
339
+ else if (argv[i] === '--help' || argv[i] === '-h') {
340
+ process.stdout.write('Usage: generate-agents-skills.mjs [--plugin-root <dir>] [--check] [--json]\n');
341
+ return 0;
342
+ }
343
+ }
344
+ const pluginRoot = path.resolve(args.pluginRoot ?? defaultRoot());
345
+ const result = generateAgentsSurface({ pluginRoot, check: args.check });
346
+
347
+ if (args.json) {
348
+ process.stdout.write(JSON.stringify({ ...result, plugin_root: pluginRoot }) + '\n');
349
+ } else if (args.check) {
350
+ for (const w of result.warnings) process.stderr.write(` WARN: agents-surface: ${w}\n`);
351
+ if (result.ok) {
352
+ process.stdout.write(`agents-surface: ${result.files + 1} artefact(s), in sync\n`);
353
+ } else {
354
+ for (const d of result.drift) process.stderr.write(`✗ agents-surface: ${d}\n`);
355
+ process.stderr.write(' Remedy: node scripts/generate-agents-skills.mjs\n');
356
+ }
357
+ } else {
358
+ for (const w of result.warnings) process.stderr.write(` WARN: agents-surface: ${w}\n`);
359
+ process.stdout.write(
360
+ `agents-surface: ${result.written.length} file(s) written, ${result.files} skill mirror(s), ${result.bytes} bytes\n`,
361
+ );
362
+ }
363
+ return result.ok ? 0 : 1;
364
+ }
365
+
366
+ // realpathSync on both sides: a spawn whose path traverses a symlink (macOS
367
+ // /tmp -> /private/tmp) otherwise makes this whole block a silent no-op that
368
+ // still exits 0 — the failure mode documented in validate-plugin.mjs.
369
+ const invokedDirectly = (() => {
370
+ if (!process.argv[1]) return false;
371
+ try {
372
+ return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
373
+ } catch {
374
+ return false;
375
+ }
376
+ })();
377
+
378
+ if (invokedDirectly) process.exit(main(process.argv.slice(2)));
@@ -0,0 +1,246 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Generate the Codex discovery surface from commands/ and skills/.
4
+ * Commands own overlapping public names; canonical workflow bodies stay in place.
5
+ * Usage: node scripts/generate-codex-skills.mjs [--plugin-root <dir>] [--check] [--json]
6
+ */
7
+ import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmdirSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
8
+ import { dirname, join, resolve } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+ import yaml from 'js-yaml';
11
+ import { toPortableFrontmatter } from './generate-agents-skills.mjs';
12
+
13
+ const OUTPUT = '.codex-plugin/skills';
14
+ const MARKER = 'Generated by scripts/generate-codex-skills.mjs; do not edit.';
15
+
16
+ /** @typedef {{name: string, path: string, command: boolean, fm: Record<string, unknown>, policy?: boolean}} Source */
17
+
18
+ const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
19
+
20
+ /** @param {unknown} fm @param {string} name @param {string} path */
21
+ function validateFrontmatter(fm, name, path) {
22
+ const fail = (message) => { throw new Error(`${path}: ${message}`); };
23
+ if (!isRecord(fm)) fail('YAML frontmatter must be a mapping');
24
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name) || name.length > 64) fail('name must be kebab-case, at most 64 characters');
25
+ if (Object.hasOwn(fm, 'name') && fm.name !== name) fail('name must match its file or directory name');
26
+ if (typeof fm.description !== 'string' || !fm.description.trim() || fm.description.trim().length > 1024) {
27
+ fail('description must be a non-empty string of at most 1024 characters');
28
+ }
29
+ for (const key of ['disable-model-invocation', 'user-invocable']) {
30
+ if (Object.hasOwn(fm, key) && typeof fm[key] !== 'boolean') fail(`${key} must be a boolean`);
31
+ }
32
+ for (const key of ['argument-hint', 'license', 'compatibility']) {
33
+ if (Object.hasOwn(fm, key) && typeof fm[key] !== 'string') fail(`${key} must be a string`);
34
+ }
35
+ if (Object.hasOwn(fm, 'metadata') && !isRecord(fm.metadata)) fail('metadata must be a mapping');
36
+ for (const key of ['allowed-tools', 'tools']) {
37
+ if (!Object.hasOwn(fm, key)) continue;
38
+ const value = fm[key];
39
+ if (!(typeof value === 'string' || (Array.isArray(value) && value.every((item) => typeof item === 'string' && item.trim())))) {
40
+ fail(`${key} must be a string or an array of non-empty strings`);
41
+ }
42
+ }
43
+ }
44
+
45
+ /** Existing native policy is meaningful; Claude's user-invocable flag is not a substitute. */
46
+ function readNativePolicy(root, name) {
47
+ const path = `skills/${name}/agents/openai.yaml`;
48
+ if (!existsSync(join(root, path))) return undefined;
49
+ let native;
50
+ try { native = yaml.load(readFileSync(join(root, path), 'utf8'), { schema: yaml.CORE_SCHEMA }); }
51
+ catch (error) { throw new Error(`${path}: invalid YAML: ${error.message}`, { cause: error }); }
52
+ if (!isRecord(native) || (Object.hasOwn(native, 'policy') && !isRecord(native.policy))) {
53
+ throw new Error(`${path}: native skill metadata and policy must be mappings`);
54
+ }
55
+ if (!native.policy || !Object.hasOwn(native.policy, 'allow_implicit_invocation')) return undefined;
56
+ if (typeof native.policy.allow_implicit_invocation !== 'boolean') {
57
+ throw new Error(`${path}: policy.allow_implicit_invocation must be a boolean`);
58
+ }
59
+ return native.policy.allow_implicit_invocation;
60
+ }
61
+
62
+ /** @param {string} root @returns {Source[]} */
63
+ function readSources(root) {
64
+ /** @type {Source[]} */
65
+ const sources = [];
66
+ for (const directory of ['skills', 'commands']) {
67
+ const directoryPath = join(root, directory);
68
+ if (!existsSync(directoryPath)) continue;
69
+ for (const entry of readdirSync(directoryPath, { withFileTypes: true })) {
70
+ if (entry.name.startsWith('.') || entry.name.startsWith('_')) continue;
71
+ const command = directory === 'commands';
72
+ if (command ? !entry.isFile() || !entry.name.endsWith('.md') : !entry.isDirectory()) continue;
73
+ const name = command ? entry.name.slice(0, -3) : entry.name;
74
+ const path = command ? `commands/${name}.md` : `skills/${name}/SKILL.md`;
75
+ if (!existsSync(join(root, path))) continue;
76
+ const raw = readFileSync(join(root, path), 'utf8');
77
+ const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(raw);
78
+ if (!match) throw new Error(`${path}: missing YAML frontmatter`);
79
+ let fm;
80
+ try { fm = yaml.load(match[1], { schema: yaml.CORE_SCHEMA }); }
81
+ catch (error) { throw new Error(`${path}: invalid YAML: ${error.message}`, { cause: error }); }
82
+ validateFrontmatter(fm, name, path);
83
+ sources.push({ name, path, command, fm, policy: command ? fm['disable-model-invocation'] !== true : readNativePolicy(root, name) });
84
+ }
85
+ }
86
+ return sources;
87
+ }
88
+
89
+ /** Only these two artifact shapes and their exact ownership marker belong to this generator. */
90
+ function isOwned(path, raw) {
91
+ if (/^\.codex-plugin\/skills\/[^/]+\/SKILL\.md$/.test(path)) return raw.includes(`\n<!-- ${MARKER} -->\n`);
92
+ return /^\.codex-plugin\/skills\/[^/]+\/agents\/openai\.yaml$/.test(path) && raw.startsWith(`# ${MARKER}\n`);
93
+ }
94
+
95
+ /** Read the tree without following symlinks, before any updates are permitted. */
96
+ function readOutput(root, directory = OUTPUT) {
97
+ const absolute = join(root, directory);
98
+ if (!existsSync(absolute)) return [];
99
+ if (!lstatSync(absolute).isDirectory() || lstatSync(absolute).isSymbolicLink()) {
100
+ throw new Error(`${directory}: generated directory must be a real directory`);
101
+ }
102
+ const files = [];
103
+ for (const entry of readdirSync(absolute, { withFileTypes: true })) {
104
+ const path = `${directory}/${entry.name}`;
105
+ if (entry.isSymbolicLink()) throw new Error(`${path}: refusing to follow a symlink in the generated surface`);
106
+ if (entry.isDirectory()) files.push(...readOutput(root, path));
107
+ else if (entry.isFile()) files.push({ path, raw: readFileSync(join(root, path), 'utf8') });
108
+ else throw new Error(`${path}: unsupported file type in generated surface`);
109
+ }
110
+ return files;
111
+ }
112
+
113
+ /** @param {Source} source @param {string[]} warnings */
114
+ function renderSkill(source, warnings) {
115
+ const projected = toPortableFrontmatter({ ...source.fm, name: source.name }, source.name);
116
+ warnings.push(...projected.warnings);
117
+ const lines = [
118
+ '---',
119
+ yaml.dump(projected.fm, { lineWidth: -1, noRefs: true }).trimEnd(),
120
+ '---',
121
+ '',
122
+ `<!-- ${MARKER} -->`,
123
+ '',
124
+ `# ${source.name}`,
125
+ '',
126
+ `Read [\`${source.path}\`](../../../${source.path}) in full before taking any action, then follow its complete workflow.`,
127
+ 'The linked document is authoritative, including its prechecks, argument rules, and stop conditions.',
128
+ '',
129
+ 'Resolve this link relative to this SKILL.md, not the project working directory. The plugin root is three directories above this file. Resolve package paths such as `skills/` and `scripts/` from that root; resolve relative links inside the canonical document from its own directory. Keep the user’s project as the target of project operations.',
130
+ ];
131
+ if (source.command) lines.push(
132
+ '',
133
+ '`$ARGUMENTS` means the trailing user input after the selected command skill, or an empty string when absent. Preserve flags, quoted text, and Unicode as data. Do not perform global substitution in the command document, shell expansion on the argument string, or execution of that string as shell code. When the workflow needs a command, pass its arguments through structured tool parameters or safely quoted individual arguments.',
134
+ '',
135
+ 'Read the full command before invoking an internal skill. An instruction to invoke a skill (including `session-orchestrator:<name>` or the `Skill` tool) means read and follow the canonical `skills/<name>/SKILL.md` beneath the plugin root. Do not redispatch the public command adapter: a same-named command and internal skill are distinct documents, and redispatch would recurse.',
136
+ );
137
+ lines.push('', 'Regenerate with `node scripts/generate-codex-skills.mjs`.', '');
138
+ return lines.join('\n');
139
+ }
140
+
141
+ /**
142
+ * @param {{pluginRoot: string, check?: boolean}} options
143
+ * @returns {{ok: boolean, skills: string[], files: string[], written: string[], drift: string[], warnings: string[], errors: string[]}}
144
+ */
145
+ export function generateCodexSurface({ pluginRoot, check = false }) {
146
+ const result = { ok: true, skills: [], files: [], written: [], drift: [], warnings: [], errors: [] };
147
+ try {
148
+ if (!existsSync(pluginRoot) || !statSync(pluginRoot).isDirectory()) throw new Error(`${pluginRoot}: plugin root must be an existing directory`);
149
+ const sources = new Map(readSources(pluginRoot).map((source) => [source.name, source]));
150
+ result.skills = [...sources.keys()].sort();
151
+ const artifacts = new Map();
152
+ for (const name of result.skills) {
153
+ const source = sources.get(name);
154
+ artifacts.set(`${OUTPUT}/${name}/SKILL.md`, renderSkill(source, result.warnings));
155
+ if (source.policy !== undefined) artifacts.set(`${OUTPUT}/${name}/agents/openai.yaml`, [
156
+ `# ${MARKER}`,
157
+ yaml.dump({
158
+ interface: { display_name: name },
159
+ policy: { allow_implicit_invocation: source.policy },
160
+ }, { lineWidth: -1, noRefs: true }).trimEnd(),
161
+ '',
162
+ ].join('\n'));
163
+ }
164
+ result.files = [...artifacts.keys()];
165
+
166
+ // Preflight every destination and existing artifact before the first mutation.
167
+ // A conflict or invalid source must never leave a partly refreshed surface.
168
+ for (const path of [`${OUTPUT}/.preflight`, ...result.files]) {
169
+ const destination = join(pluginRoot, path);
170
+ if (artifacts.has(path) && existsSync(destination) && !lstatSync(destination).isFile()) {
171
+ throw new Error(`${path}: generated artifact must be a regular file`);
172
+ }
173
+ let parent = dirname(path);
174
+ while (parent !== '.') {
175
+ const absolute = join(pluginRoot, parent);
176
+ if (existsSync(absolute) && (!lstatSync(absolute).isDirectory() || lstatSync(absolute).isSymbolicLink())) {
177
+ throw new Error(`${parent}: generated directory must be a real directory`);
178
+ }
179
+ parent = dirname(parent);
180
+ }
181
+ }
182
+ const obsolete = [];
183
+ for (const { path, raw } of readOutput(pluginRoot)) {
184
+ if (artifacts.has(path)) {
185
+ if (!isOwned(path, raw)) throw new Error(`${path}: refusing to overwrite an unrelated artifact without the generated marker`);
186
+ } else if (isOwned(path, raw)) obsolete.push(path);
187
+ else result.warnings.push(`${path}: retained unrelated file`);
188
+ }
189
+
190
+ for (const [path, content] of artifacts) {
191
+ const absolute = join(pluginRoot, path);
192
+ if (existsSync(absolute) && readFileSync(absolute, 'utf8') === content) continue;
193
+ if (check) result.drift.push(`${path} is missing or stale`);
194
+ else {
195
+ mkdirSync(dirname(absolute), { recursive: true });
196
+ writeFileSync(absolute, content);
197
+ result.written.push(path);
198
+ }
199
+ }
200
+ for (const path of obsolete) {
201
+ if (check) result.drift.push(`${path} is obsolete`);
202
+ else {
203
+ unlinkSync(join(pluginRoot, path));
204
+ result.written.push(`removed ${path}`);
205
+ let parent = dirname(path);
206
+ while (parent !== OUTPUT && readdirSync(join(pluginRoot, parent)).length === 0) {
207
+ rmdirSync(join(pluginRoot, parent));
208
+ parent = dirname(parent);
209
+ }
210
+ }
211
+ }
212
+ } catch (error) {
213
+ result.errors.push(error.message);
214
+ }
215
+ result.ok = result.errors.length === 0 && result.drift.length === 0;
216
+ return result;
217
+ }
218
+
219
+ function runCli() {
220
+ let pluginRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
221
+ let check = false;
222
+ let json = false;
223
+ const args = process.argv.slice(2);
224
+ for (let i = 0; i < args.length; i += 1) {
225
+ if (args[i] === '--check') check = true;
226
+ else if (args[i] === '--json') json = true;
227
+ else if (args[i] === '--plugin-root' && args[i + 1]) pluginRoot = resolve(args[++i]);
228
+ else if (args[i] === '--help') {
229
+ process.stdout.write('Usage: generate-codex-skills.mjs [--plugin-root <dir>] [--check] [--json]\n');
230
+ return;
231
+ } else {
232
+ process.stderr.write(`Unknown or incomplete argument: ${args[i]}\n`);
233
+ process.exitCode = 1;
234
+ return;
235
+ }
236
+ }
237
+ const result = generateCodexSurface({ pluginRoot, check });
238
+ if (json) process.stdout.write(`${JSON.stringify(result)}\n`);
239
+ else {
240
+ process.stdout.write(`codex-surface: ${result.files.length} artefact(s), ${result.ok ? (check ? 'in sync' : 'generated') : 'failed'}\n`);
241
+ for (const message of [...result.errors, ...result.drift, ...result.warnings]) process.stderr.write(`${message}\n`);
242
+ }
243
+ process.exitCode = result.ok ? 0 : 1;
244
+ }
245
+
246
+ if (process.argv[1] && existsSync(process.argv[1]) && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) runCli();