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
@@ -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)));
@@ -80,12 +80,40 @@ function parseFrontmatter(content) {
80
80
  return fields;
81
81
  }
82
82
 
83
+ /**
84
+ * Values that YAML would read as a non-string scalar even though every
85
+ * character in them is "safe". `argument-hint: no` is a boolean, not the
86
+ * string "no"; `description: 3` is an integer. A consumer asserting
87
+ * `typeof value === 'string'` fails on a file that looks perfectly fine.
88
+ */
89
+ const YAML_SCALAR_LOOKALIKE = /^(?:true|false|yes|no|on|off|null|nan|[-+]?\.?inf|~)$/i;
90
+
91
+ /**
92
+ * A value safe to emit as a YAML *plain* (unquoted) scalar.
93
+ *
94
+ * Deliberately an ALLOW-list. Its predecessor was a deny-list of special
95
+ * characters, which is unenumerable by construction: it caught `[` and `:`
96
+ * but not a leading `-` (block-sequence entry), not surrounding whitespace,
97
+ * and not the scalar look-alikes above. An allow-list fails closed — an
98
+ * unforeseen shape gets quoted, which is never wrong, only noisier.
99
+ */
100
+ const YAML_PLAIN_SAFE = /^[A-Za-z0-9_][A-Za-z0-9 _.,()/-]*$/;
101
+
102
+ /**
103
+ * Render a frontmatter VALUE as YAML.
104
+ *
105
+ * @param {unknown} value
106
+ * @returns {string|null} the YAML scalar, or `null` when there is nothing to emit
107
+ */
83
108
  function yamlQuote(value) {
84
- if (value === undefined || value === '') return null;
85
- if (/[:#[\]{}&*!|>'"%@`]/.test(value) || value.includes('\n')) {
86
- return JSON.stringify(value);
87
- }
88
- return value;
109
+ if (value === undefined || value === null || value === '') return null;
110
+ const text = String(value);
111
+ if (text.trim() === '') return null;
112
+ if (text !== text.trim()) return JSON.stringify(text);
113
+ if (!YAML_PLAIN_SAFE.test(text)) return JSON.stringify(text);
114
+ if (YAML_SCALAR_LOOKALIKE.test(text)) return JSON.stringify(text);
115
+ if (/^\d/.test(text)) return JSON.stringify(text);
116
+ return text;
89
117
  }
90
118
 
91
119
  function frontmatterLine(key, value) {
@@ -103,10 +131,16 @@ function renderCommand(commandFile) {
103
131
  const commandPath = path.join(COMMANDS_DIR, commandFile);
104
132
  const commandName = commandFile.replace(/\.md$/, '');
105
133
  const fields = parseFrontmatter(readFileSync(commandPath, 'utf8'));
134
+ // EVERY value goes through yamlQuote(). `argument-hint` did not, and its
135
+ // canonical form is a bare bracket list (`[mode] [--flag]`) — a YAML flow
136
+ // sequence, which is GH#54: Copilot CLI >= 1.0.65 silently DROPS a command
137
+ // file whose `argument-hint` is not a string. Fixed in `commands/` and
138
+ // `pi/prompts/` by 93b40dd (v3.16.0) and re-introduced here on every
139
+ // generation, because this mirror re-derived the frontmatter by hand.
106
140
  const frontmatter = [
107
141
  '---',
108
- frontmatterLine('description', yamlQuote(fields.description) ?? fields.description),
109
- frontmatterLine('argument-hint', fields['argument-hint']),
142
+ frontmatterLine('description', yamlQuote(fields.description)),
143
+ frontmatterLine('argument-hint', yamlQuote(fields['argument-hint'])),
110
144
  '---',
111
145
  ].filter(Boolean).join('\n');
112
146
 
@@ -132,9 +166,12 @@ function renderSkill(skillName) {
132
166
  const skillPath = path.join(SKILLS_DIR, skillName, 'SKILL.md');
133
167
  const fields = parseFrontmatter(readFileSync(skillPath, 'utf8'));
134
168
  const description = clampDescription(fields.description || `Session Orchestrator skill: ${skillName}`);
169
+ // Same rule as renderCommand: no frontmatter value is emitted raw. `name` is
170
+ // a directory basename today, so it is plain-safe in practice — routing it
171
+ // through yamlQuote() is what keeps that true after the next skill is added.
135
172
  const lines = [
136
173
  '---',
137
- `name: ${skillName}`,
174
+ `name: ${yamlQuote(skillName)}`,
138
175
  `description: ${yamlQuote(description)}`,
139
176
  ];
140
177
  if (!isUserInvocable(fields['user-invocable'])) {