session-orchestrator 5.11.0 → 5.13.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 (394) hide show
  1. package/.claude-plugin/marketplace.json +12 -1
  2. package/.claude-plugin/plugin.json +4 -1
  3. package/.codex-plugin/agents/explorer.toml +1 -1
  4. package/.codex-plugin/agents/session-reviewer.toml +2 -2
  5. package/.codex-plugin/agents/wave-worker.toml +1 -1
  6. package/.codex-plugin/plugin.json +1 -1
  7. package/.cursor-plugin/plugin.json +1 -1
  8. package/.orchestrator/policy/blocked-commands.json +8 -0
  9. package/.orchestrator/policy/public-export.json +30 -0
  10. package/CHANGELOG.md +180 -3
  11. package/README.md +126 -117
  12. package/agents/analyst.md +7 -2
  13. package/agents/architect-reviewer.md +7 -2
  14. package/agents/code-implementer.md +4 -2
  15. package/agents/db-specialist.md +9 -2
  16. package/agents/dialectic-deriver.md +9 -3
  17. package/agents/docs-writer.md +7 -2
  18. package/agents/eval-judge.md +7 -1
  19. package/agents/qa-strategist.md +7 -2
  20. package/agents/security-reviewer.md +9 -2
  21. package/agents/session-reviewer.md +18 -2
  22. package/agents/skill-applied-judge.md +7 -1
  23. package/agents/test-writer.md +9 -2
  24. package/agents/ui-developer.md +9 -2
  25. package/agents/ux-evaluator.md +6 -1
  26. package/commands/session.md +4 -2
  27. package/commands/templates-ack.md +89 -18
  28. package/docs/agent-authoring.md +30 -11
  29. package/docs/ci-setup.md +38 -2
  30. package/docs/codex-setup.md +33 -9
  31. package/docs/components.md +13 -2
  32. package/docs/cursor-setup.md +3 -3
  33. package/docs/events-schema.md +5 -4
  34. package/docs/evidence-candidates.md +63 -0
  35. package/docs/fleet-checkin-audit.md +7 -4
  36. package/docs/github-mirror-protection.md +150 -3
  37. package/docs/install.md +102 -25
  38. package/docs/instruction-delivery.md +1 -1
  39. package/docs/migration-v4.md +2 -2
  40. package/docs/mods.md +118 -0
  41. package/docs/owner-config-schema.md +1 -0
  42. package/docs/plugin-architecture-v3.md +1 -1
  43. package/docs/pm-skills-marketplace.md +4 -3
  44. package/docs/rule-authoring.md +24 -13
  45. package/docs/scope-collision-guard.md +2 -1
  46. package/docs/session-config-reference.md +55 -21
  47. package/docs/session-config-template.md +32 -24
  48. package/docs/sessions-ledger-repair.md +137 -0
  49. package/docs/vault-docs-architecture.md +1 -1
  50. package/hooks/_lib/hook-import-set.json +406 -73
  51. package/hooks/_lib/hook-root.mjs +301 -0
  52. package/hooks/_lib/lock-bootstrap.mjs +434 -4
  53. package/hooks/_lib/orchestrator-ignore.mjs +3 -0
  54. package/hooks/_lib/subagent-transcript.mjs +2 -2
  55. package/hooks/_lib/vcs-create-matcher.mjs +13 -17
  56. package/hooks/cwd-change-restore.mjs +5 -1
  57. package/hooks/enforce-commands.mjs +4 -3
  58. package/hooks/enforce-scope.mjs +11 -1
  59. package/hooks/hooks-codex.json +48 -2
  60. package/hooks/hooks.json +31 -31
  61. package/hooks/on-session-end.mjs +38 -15
  62. package/hooks/on-session-start.mjs +158 -59
  63. package/hooks/on-stop.mjs +11 -6
  64. package/hooks/post-bash-issue-budget-refund.mjs +6 -1
  65. package/hooks/post-bash-write-verify.mjs +8 -4
  66. package/hooks/post-edit-validate.mjs +45 -52
  67. package/hooks/post-subagent-discovery-validator.mjs +2 -2
  68. package/hooks/post-tool-batch-wave-signal.mjs +8 -4
  69. package/hooks/post-tool-failure-corrective-context.mjs +5 -1
  70. package/hooks/pre-bash-destructive-guard.mjs +220 -8
  71. package/hooks/pre-bash-issue-budget.mjs +6 -1
  72. package/hooks/pre-bash-sessions-ledger-guard.mjs +691 -123
  73. package/hooks/pre-bash-staging-fence.mjs +258 -14
  74. package/hooks/pre-bash-templates-first.mjs +28 -27
  75. package/hooks/run-node.sh +1 -1
  76. package/hooks/skill-invocation-telemetry.mjs +15 -8
  77. package/hooks/subagent-telemetry.mjs +77 -14
  78. package/hooks/wave-scope-commit-guard.mjs +143 -14
  79. package/mods/so-hud/.claude-plugin/plugin.json +11 -0
  80. package/mods/so-hud/README.md +57 -0
  81. package/mods/so-hud/hooks/beacon-io.mjs +101 -0
  82. package/mods/so-hud/hooks/heredoc-scan.ts +394 -0
  83. package/mods/so-hud/hooks/hooks.json +1 -0
  84. package/mods/so-hud/hooks/logic.ts +182 -0
  85. package/mods/so-hud/hooks/register.tsx +670 -0
  86. package/mods/so-hud/hooks/rules.ts +2998 -0
  87. package/mods/so-hud/tsconfig.json +8 -0
  88. package/mods/so-hud/types/index.d.ts +36 -0
  89. package/package.json +4 -3
  90. package/rules/_index.md +1 -1
  91. package/rules/always-on/ask-via-tool.md +1 -1
  92. package/rules/always-on/loop-and-monitor.md +17 -194
  93. package/rules/always-on/parallel-sessions.md +3 -1
  94. package/rules/opt-in-stack/backend-data.md +1 -1
  95. package/rules/opt-in-stack/backend.md +1 -1
  96. package/rules/opt-in-stack/security-web.md +5 -1
  97. package/scripts/agent-usage-census.mjs +79 -0
  98. package/scripts/apply-session-learnings.mjs +34 -6
  99. package/scripts/archive-closed-prds.mjs +21 -5
  100. package/scripts/autopilot.mjs +11 -5
  101. package/scripts/backfill-abandoned-sessions.mjs +13 -3
  102. package/scripts/backfill-evidence-digest.mjs +2 -1
  103. package/scripts/backfill-learnings-from-vault.mjs +61 -6
  104. package/scripts/backfill-sessions.mjs +16 -2
  105. package/scripts/check-package-manager.mjs +26 -8
  106. package/scripts/check-sessions-integrity.mjs +4 -1
  107. package/scripts/ci/assert-vitest-green.mjs +3 -0
  108. package/scripts/ci/windows-smoke.mjs +201 -0
  109. package/scripts/close-precheck.mjs +55 -0
  110. package/scripts/dialectic-deriver.mjs +20 -12
  111. package/scripts/emit-event.mjs +2 -2
  112. package/scripts/emit-session.mjs +239 -20
  113. package/scripts/eval-session.mjs +17 -3
  114. package/scripts/export-hw-learnings.mjs +29 -2
  115. package/scripts/fleet-checkin-audit.mjs +8 -4
  116. package/scripts/fleet-checkin.mjs +73 -3
  117. package/scripts/gc-stale-worktrees.mjs +6 -1
  118. package/scripts/github-export.mjs +128 -0
  119. package/scripts/github-protection-audit.mjs +3 -1
  120. package/scripts/handover-queue-check.mjs +209 -0
  121. package/scripts/harness-audit.mjs +6 -4
  122. package/scripts/lib/agent-frontmatter.mjs +57 -10
  123. package/scripts/lib/agent-status.mjs +125 -7
  124. package/scripts/lib/agent-usage-census.mjs +394 -0
  125. package/scripts/lib/auto-dialectic.mjs +17 -10
  126. package/scripts/lib/auto-dream.mjs +22 -14
  127. package/scripts/lib/autopilot/loop.mjs +3 -1
  128. package/scripts/lib/autopilot/recent-runs.mjs +4 -2
  129. package/scripts/lib/autopilot/stall-sampler.mjs +2 -1
  130. package/scripts/lib/autopilot/telemetry.mjs +5 -1
  131. package/scripts/lib/autopilot/worktree-pipeline.mjs +5 -5
  132. package/scripts/lib/baseline-archetypes.mjs +10 -2
  133. package/scripts/lib/blocked-commands-policy.mjs +4 -3
  134. package/scripts/lib/build-live-signals.mjs +13 -9
  135. package/scripts/lib/ci-status-banner.mjs +329 -24
  136. package/scripts/lib/codex/plugin-contract.mjs +8 -0
  137. package/scripts/lib/cold-start-detector.mjs +5 -2
  138. package/scripts/lib/command-blocker.mjs +603 -43
  139. package/scripts/lib/common.mjs +3 -2
  140. package/scripts/lib/config/cross-repo.mjs +74 -7
  141. package/scripts/lib/config/host-paths.mjs +6 -2
  142. package/scripts/lib/config/remote-hosts.mjs +2 -2
  143. package/scripts/lib/config/section-extractor.mjs +65 -11
  144. package/scripts/lib/config/template-keys.mjs +153 -0
  145. package/scripts/lib/config/vault-integration.mjs +1 -0
  146. package/scripts/lib/config.mjs +3 -3
  147. package/scripts/lib/coordinator-snapshot.mjs +1 -1
  148. package/scripts/lib/discovery/triage-state.mjs +2 -1
  149. package/scripts/lib/dispatcher/rank.mjs +5 -7
  150. package/scripts/lib/ecosystem-health.mjs +2 -2
  151. package/scripts/lib/eval/engine.mjs +3 -2
  152. package/scripts/lib/eval/session-resolve.mjs +6 -2
  153. package/scripts/lib/eval/sink.mjs +4 -3
  154. package/scripts/lib/events-retention-banner.mjs +3 -2
  155. package/scripts/lib/events-schema.mjs +10 -0
  156. package/scripts/lib/events.mjs +23 -12
  157. package/scripts/lib/evolve/autonomy-verdict.mjs +4 -9
  158. package/scripts/lib/evolve/autopilot-effectiveness.mjs +8 -18
  159. package/scripts/lib/fleet-protocol.mjs +21 -0
  160. package/scripts/lib/gates/gate-full.mjs +172 -11
  161. package/scripts/lib/gates/gate-helpers.mjs +236 -23
  162. package/scripts/lib/git-config-drift.mjs +141 -7
  163. package/scripts/lib/github-export.mjs +447 -0
  164. package/scripts/lib/gitlab-portfolio/cli.mjs +16 -5
  165. package/scripts/lib/handover-queue-marks.mjs +159 -0
  166. package/scripts/lib/hardware-pattern-detector.mjs +103 -13
  167. package/scripts/lib/harness-audit/categories/category1.mjs +16 -4
  168. package/scripts/lib/harness-audit/categories/category4.mjs +11 -8
  169. package/scripts/lib/harness-audit/categories/category6.mjs +25 -5
  170. package/scripts/lib/harness-audit/categories/category9.mjs +10 -4
  171. package/scripts/lib/host-identity.mjs +9 -5
  172. package/scripts/lib/http-request.mjs +64 -0
  173. package/scripts/lib/instruction-budget-guard.mjs +30 -22
  174. package/scripts/lib/io.mjs +6 -3
  175. package/scripts/lib/issue-budget-reconcile.mjs +3 -2
  176. package/scripts/lib/issue-budget.mjs +36 -7
  177. package/scripts/lib/learnings/evidence-candidates.mjs +154 -0
  178. package/scripts/lib/learnings/expiry-sweep.mjs +99 -7
  179. package/scripts/lib/learnings/io.mjs +289 -15
  180. package/scripts/lib/learnings/select.mjs +53 -4
  181. package/scripts/lib/learnings/store-role.mjs +209 -0
  182. package/scripts/lib/learnings/surface.mjs +46 -7
  183. package/scripts/lib/ledger-path.mjs +113 -0
  184. package/scripts/lib/maintenance-due-banner.mjs +63 -12
  185. package/scripts/lib/memory-banner.mjs +5 -6
  186. package/scripts/lib/memory-proposals/collector.mjs +5 -2
  187. package/scripts/lib/memory-proposals/sink.mjs +7 -4
  188. package/scripts/lib/memory-proposals/store.mjs +22 -5
  189. package/scripts/lib/mode-selector-accuracy.mjs +2 -1
  190. package/scripts/lib/no-follow-open.mjs +57 -0
  191. package/scripts/lib/orphan-reaper/reaper-audit.mjs +4 -2
  192. package/scripts/lib/own-lock-root.mjs +95 -0
  193. package/scripts/lib/owner-interview.mjs +2 -1
  194. package/scripts/lib/owner-yaml.mjs +70 -7
  195. package/scripts/lib/platform.mjs +161 -9
  196. package/scripts/lib/plugin-update-banner.mjs +15 -11
  197. package/scripts/lib/pre-push-local-partial.mjs +117 -0
  198. package/scripts/lib/pre-push-remote-gate.mjs +151 -25
  199. package/scripts/lib/process-group.mjs +270 -34
  200. package/scripts/lib/project-hygiene.mjs +28 -0
  201. package/scripts/lib/quality-gate.mjs +33 -9
  202. package/scripts/lib/reconcile/backlog.mjs +29 -15
  203. package/scripts/lib/reconcile/engine.mjs +26 -2
  204. package/scripts/lib/reconcile/idempotency.mjs +65 -3
  205. package/scripts/lib/reconcile/rule-expiry-sweep.mjs +53 -12
  206. package/scripts/lib/reconcile/verdict-ledger.mjs +224 -0
  207. package/scripts/lib/reconcile-nudge-banner.mjs +40 -6
  208. package/scripts/lib/resource-probe/evaluate.mjs +32 -9
  209. package/scripts/lib/resource-probe/probe-platform.mjs +3 -1
  210. package/scripts/lib/rule-loader.mjs +20 -10
  211. package/scripts/lib/scope-baseline.mjs +123 -8
  212. package/scripts/lib/scope-echo.mjs +5 -2
  213. package/scripts/lib/scope-gate.mjs +55 -20
  214. package/scripts/lib/session-close-backfill.mjs +75 -7
  215. package/scripts/lib/session-end/close-precheck.mjs +151 -0
  216. package/scripts/lib/session-end/phase-skip.mjs +8 -7
  217. package/scripts/lib/session-end/tail-runner.mjs +7 -2
  218. package/scripts/lib/session-end/vault-orphans.mjs +205 -0
  219. package/scripts/lib/session-id.mjs +9 -1
  220. package/scripts/lib/session-identity/own-session.mjs +63 -0
  221. package/scripts/lib/session-invocation.mjs +14 -3
  222. package/scripts/lib/session-lock.mjs +114 -0
  223. package/scripts/lib/session-record-repair.mjs +245 -35
  224. package/scripts/lib/session-registry.mjs +85 -12
  225. package/scripts/lib/session-schema/constants.mjs +9 -0
  226. package/scripts/lib/session-schema/filters.mjs +56 -1
  227. package/scripts/lib/session-schema/validator.mjs +15 -5
  228. package/scripts/lib/session-start-probes.mjs +149 -22
  229. package/scripts/lib/session-token-rollup.mjs +2 -1
  230. package/scripts/lib/session-work-trees.mjs +166 -0
  231. package/scripts/lib/sessions-canonical.mjs +61 -3
  232. package/scripts/lib/sessions-integrity-banner.mjs +17 -39
  233. package/scripts/lib/sessions-staleness-banner.mjs +8 -5
  234. package/scripts/lib/sh-quote.mjs +1 -0
  235. package/scripts/lib/skill-evidence-window.mjs +5 -1
  236. package/scripts/lib/skill-health/join.mjs +10 -9
  237. package/scripts/lib/skill-invocations-schema.mjs +13 -0
  238. package/scripts/lib/skill-judgments-schema.mjs +6 -2
  239. package/scripts/lib/snapshot-probe.mjs +414 -0
  240. package/scripts/lib/so-hud/collect.mjs +174 -0
  241. package/scripts/lib/so-hud/runtime.mjs +87 -0
  242. package/scripts/lib/spiral-carryover.mjs +8 -1
  243. package/scripts/lib/state-md/frontmatter-mutators.mjs +266 -8
  244. package/scripts/lib/state-md/idle-reset.mjs +248 -0
  245. package/scripts/lib/state-md/mission-status.mjs +5 -1
  246. package/scripts/lib/state-md/recommendations.mjs +26 -4
  247. package/scripts/lib/state-md-peer-guard.mjs +2 -0
  248. package/scripts/lib/state-md.mjs +31 -8
  249. package/scripts/lib/sunset/walker.mjs +4 -3
  250. package/scripts/lib/telemetry/consent.mjs +4 -2
  251. package/scripts/lib/telemetry/queue.mjs +3 -3
  252. package/scripts/lib/telemetry/sync.mjs +45 -9
  253. package/scripts/lib/test-runner/artifact-paths.mjs +10 -2
  254. package/scripts/lib/tmux-layout/layouts.mjs +20 -3
  255. package/scripts/lib/ux-grill/paths.mjs +5 -1
  256. package/scripts/lib/validate/check-agents.mjs +37 -6
  257. package/scripts/lib/validate/check-component-paths.mjs +25 -1
  258. package/scripts/lib/validate/check-guard-requires-parity.mjs +3 -3
  259. package/scripts/lib/validate/check-hooks-symmetry.mjs +53 -32
  260. package/scripts/lib/validate/check-json-files.mjs +3 -1
  261. package/scripts/lib/validate/check-learning-provenance.mjs +3 -2
  262. package/scripts/lib/validate/check-learnings-shell-writes.mjs +10 -4
  263. package/scripts/lib/validate/check-metrics-ledger-root.mjs +234 -0
  264. package/scripts/lib/validate/check-owner-leakage.mjs +159 -11
  265. package/scripts/lib/validate/check-test-posix-shell.mjs +246 -0
  266. package/scripts/lib/validate/check-unwired-features.mjs +4 -2
  267. package/scripts/lib/validate/check-vcs-repo-flag-positional.mjs +58 -0
  268. package/scripts/lib/validate/check-vcs-repo-flag.mjs +259 -73
  269. package/scripts/lib/validate/check-workflow-continue-on-error.mjs +63 -0
  270. package/scripts/lib/validate/confidential-names.mjs +3 -1
  271. package/scripts/lib/vault-mirror/migrate-ids.mjs +321 -0
  272. package/scripts/lib/vault-mirror/namespace.mjs +22 -9
  273. package/scripts/lib/vault-mirror/process.mjs +197 -65
  274. package/scripts/lib/vault-mirror/prune.mjs +5 -2
  275. package/scripts/lib/vault-mirror/render-learnings.mjs +8 -7
  276. package/scripts/lib/vault-mirror/render-sessions.mjs +36 -53
  277. package/scripts/lib/vault-mirror/retention.mjs +2 -2
  278. package/scripts/lib/vault-mirror/utils.mjs +106 -13
  279. package/scripts/lib/vault-staleness-banner.mjs +4 -7
  280. package/scripts/lib/vault-status/board-writer.mjs +34 -5
  281. package/scripts/lib/vault-status/narrative-mirror.mjs +66 -7
  282. package/scripts/lib/wave-executor/foreign-dispatch.mjs +20 -0
  283. package/scripts/lib/win-process-tree.mjs +137 -0
  284. package/scripts/lib/workspace.mjs +1 -1
  285. package/scripts/lib/worktree/constants.mjs +1 -1
  286. package/scripts/lib/worktree/lifecycle.mjs +1 -1
  287. package/scripts/lib/worktree/listing.mjs +1 -1
  288. package/scripts/lib/zx.mjs +50 -0
  289. package/scripts/mcp-server.mjs +16 -7
  290. package/scripts/mcp-server.sh +13 -83
  291. package/scripts/measure-policy-cache-effectiveness.mjs +7 -5
  292. package/scripts/memory-propose.mjs +10 -3
  293. package/scripts/migrate-cold-start-seed.mjs +7 -0
  294. package/scripts/migrate-legacy-learnings.sh +22 -0
  295. package/scripts/migrate-sessions-jsonl.mjs +16 -2
  296. package/scripts/migrate-vault-paths.mjs +29 -13
  297. package/scripts/parse-config.mjs +71 -14
  298. package/scripts/print-learnings-index.mjs +25 -2
  299. package/scripts/release-session-lock.mjs +94 -7
  300. package/scripts/release.mjs +451 -24
  301. package/scripts/relocate-vault-corpus.mjs +11 -7
  302. package/scripts/repair-invalid-sessions.mjs +68 -3
  303. package/scripts/resolve-session-invocation.mjs +57 -8
  304. package/scripts/run-migrate-v2-cross-repo.mjs +56 -1
  305. package/scripts/run-quality-gate.mjs +33 -7
  306. package/scripts/self-update.mjs +56 -7
  307. package/scripts/session-commit-files.mjs +133 -0
  308. package/scripts/site-numbers.mjs +52 -9
  309. package/scripts/snapshot-probe.mjs +156 -0
  310. package/scripts/so-hud.mjs +68 -0
  311. package/scripts/state-md-idle-reset.mjs +82 -0
  312. package/scripts/sweep-expired-learnings.mjs +54 -6
  313. package/scripts/sweep-expired-rules.mjs +4 -1
  314. package/scripts/validate-plugin.mjs +22 -0
  315. package/scripts/vault-integration-watcher.mjs +54 -6
  316. package/scripts/vault-mirror.mjs +9 -0
  317. package/scripts/vault-orphans.mjs +171 -0
  318. package/skills/_shared/fleet-protocol.md +166 -3
  319. package/skills/_shared/instruction-file-resolution.md +1 -1
  320. package/skills/_shared/monitor-patterns.md +44 -25
  321. package/skills/_shared/platform-tools.md +7 -0
  322. package/skills/_shared/state-ownership.md +5 -4
  323. package/skills/architecture/references/domain-model.md +1 -1
  324. package/skills/autopilot/SKILL.md +3 -2
  325. package/skills/bootstrap/SKILL.md +3 -1
  326. package/skills/bootstrap/_shared-template.md +4 -4
  327. package/skills/bootstrap/fast-template.md +1 -1
  328. package/skills/bootstrap/private-contract.md +1 -1
  329. package/skills/bootstrap/references/bootstrap-retroactive-flow.md +17 -9
  330. package/skills/bootstrap/templates/agents/project-code-review.md +5 -1
  331. package/skills/bootstrap/templates/agents/project-discovery.md +5 -1
  332. package/skills/bootstrap/templates/agents/project-quality-gate.md +5 -1
  333. package/skills/claude-md-drift-check/SKILL.md +3 -3
  334. package/skills/claude-md-drift-check/checker.mjs +61 -101
  335. package/skills/close/SKILL.md +20 -7
  336. package/skills/discovery/probes/docs-staleness.mjs +7 -5
  337. package/skills/discovery/probes/frontend-slop.mjs +7 -3
  338. package/skills/discovery/probes/ssot-code-diff.mjs +5 -3
  339. package/skills/discovery/probes/supply-chain-slopcheck.mjs +10 -8
  340. package/skills/discovery/probes/vault-narrative-staleness.mjs +30 -10
  341. package/skills/discovery/probes/vault-staleness.mjs +5 -3
  342. package/skills/discovery/probes-session.md +3 -0
  343. package/skills/discovery/probes-vault.md +2 -0
  344. package/skills/dispatcher/SKILL.md +1 -1
  345. package/skills/eval/SKILL.md +3 -1
  346. package/skills/evolve/SKILL.md +2 -0
  347. package/skills/evolve/references/evolve-analyze-mode.md +3 -3
  348. package/skills/evolve/references/evolve-dialectic-mode.md +1 -1
  349. package/skills/gitlab-ops/SKILL.md +7 -6
  350. package/skills/gitlab-portfolio/SKILL.md +2 -2
  351. package/skills/go/SKILL.md +13 -1
  352. package/skills/npm-publish/SKILL.md +5 -2
  353. package/skills/plan/SKILL.md +23 -10
  354. package/skills/plan/mode-feature.md +13 -8
  355. package/skills/plan/mode-new.md +6 -14
  356. package/skills/plan/prd-feature-template.md +19 -0
  357. package/skills/plan/prd-full-template.md +12 -5
  358. package/skills/plan/prd-premortem-panel.md +48 -0
  359. package/skills/plan/prd-reviewer-prompt.md +11 -3
  360. package/skills/plan/soul.md +1 -1
  361. package/skills/portfolio/SKILL.md +5 -6
  362. package/skills/quality-gates/SKILL.md +2 -4
  363. package/skills/reconcile/SKILL.md +27 -4
  364. package/skills/release/SKILL.md +16 -8
  365. package/skills/session-end/SKILL.md +97 -13
  366. package/skills/session-end/phase-3-2-docs-verification.md +1 -0
  367. package/skills/session-end/phase-3-6-tail.md +11 -7
  368. package/skills/session-end/phase-3-7a-recommendations.md +9 -2
  369. package/skills/session-end/plan-verification.md +16 -1
  370. package/skills/session-end/references/phase-3-7e-vault-orphans.md +63 -0
  371. package/skills/session-end/references/phase-3-documentation-updates.md +25 -13
  372. package/skills/session-end/references/phase-5-issue-cleanup.md +1 -1
  373. package/skills/session-end/references/session-summary-template.md +8 -0
  374. package/skills/session-end/session-metrics-write.md +7 -4
  375. package/skills/session-plan/SKILL.md +5 -3
  376. package/skills/session-start/SKILL.md +10 -4
  377. package/skills/session-start/phase-4-5-resource-health.md +11 -5
  378. package/skills/session-start/phase-7-5-mode-selector.md +3 -1
  379. package/skills/session-start/phase-8-5-express-path.md +4 -3
  380. package/skills/session-start/references/phase-1-2-session-lock.md +7 -5
  381. package/skills/session-start/references/phase-1-5-session-continuity.md +13 -5
  382. package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
  383. package/skills/session-start/references/phase-4-ssot-environment-check.md +3 -2
  384. package/skills/tmux-layout/SKILL.md +2 -2
  385. package/skills/vault-sync/validator.mjs +3 -2
  386. package/skills/wave-executor/SKILL.md +5 -4
  387. package/skills/wave-executor/references/wave-executor-state-init.md +3 -3
  388. package/skills/wave-executor/references/wave-loop-dispatch.md +11 -5
  389. package/skills/wave-executor/references/wave-loop-review.md +13 -5
  390. package/skills/wave-executor/references/wave-loop-scope-manifest.md +1 -1
  391. package/AGENTS.md +0 -207
  392. package/assets/og-card.png +0 -0
  393. package/assets/og-card.svg +0 -47
  394. package/assets/wave-lifecycle.svg +0 -98
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * session-commit-files.mjs — which files did one session commit, in which of
4
+ * its own work trees (#1659).
5
+ *
6
+ * Read-only CLI over scripts/lib/session-work-trees.mjs: the session's
7
+ * `(tree, start_ref)` pairs come from the `orchestrator.session.work_tree`
8
+ * events in the default events ledger, and every tree is diffed INSIDE ITSELF
9
+ * against its own start ref (`git -C <tree> diff --name-only <ref>..HEAD`).
10
+ * Never a diff across two trees, never a write.
11
+ *
12
+ * Usage:
13
+ * node scripts/session-commit-files.mjs [--session-id <raw-id>] [--json]
14
+ *
15
+ * Exit codes: 0 success (also with zero trees), 2 usage error (unknown flag,
16
+ * missing session id). Deviates from cli-design.md's `1` for input errors by
17
+ * coordinator contract (#1659); documented in `--help`.
18
+ */
19
+
20
+ import { parseArgs } from 'node:util';
21
+
22
+ import { isMainModule } from './lib/is-main-module.mjs';
23
+ import { readSessionWorkTrees, sessionCommitFiles } from './lib/session-work-trees.mjs';
24
+
25
+ const USAGE = `Usage: node scripts/session-commit-files.mjs [--session-id <raw-id>] [--json]
26
+
27
+ List the files one session committed, per own work tree (#1659).
28
+
29
+ Reads the session's (tree, start_ref) pairs from the
30
+ orchestrator.session.work_tree events of the default events ledger and runs
31
+ 'git -C <tree> diff --name-only <start_ref>..HEAD' inside each tree only.
32
+ Read-only: writes nothing.
33
+
34
+ Options:
35
+ --session-id <id> raw session id (default: $CLAUDE_CODE_SESSION_ID)
36
+ --json print { trees, perTree, skipped, union, malformed, invalid,
37
+ complete[, error] } as JSON (complete: null = no ledger found)
38
+ -h, --help show this help
39
+
40
+ Default output (stdout): one line per tree '<tree>\\t<start_ref>\\t<n> files',
41
+ a blank line, then the union of files, one per line. Skipped trees and notes
42
+ go to stderr.
43
+
44
+ Exit codes:
45
+ 0 success (also when the session recorded no trees)
46
+ 2 usage error (unknown option, no session id)
47
+
48
+ Example:
49
+ node scripts/session-commit-files.mjs --session-id "$CLAUDE_CODE_SESSION_ID" --json`;
50
+
51
+ /**
52
+ * Run the CLI.
53
+ *
54
+ * @param {string[]} argv — arguments after the script path.
55
+ * @param {{ env?: NodeJS.ProcessEnv,
56
+ * stdout?: (s: string) => void, stderr?: (s: string) => void }} [io]
57
+ * @returns {number} exit code
58
+ */
59
+ export function main(argv, {
60
+ env = process.env,
61
+ stdout = (s) => process.stdout.write(s),
62
+ stderr = (s) => process.stderr.write(s),
63
+ } = {}) {
64
+ let values;
65
+ try {
66
+ ({ values } = parseArgs({
67
+ args: argv,
68
+ options: {
69
+ 'session-id': { type: 'string' },
70
+ json: { type: 'boolean', default: false },
71
+ help: { type: 'boolean', short: 'h', default: false },
72
+ },
73
+ strict: true,
74
+ allowPositionals: false,
75
+ }));
76
+ } catch (err) {
77
+ stderr(`session-commit-files: ${err.message}\n${USAGE}\n`);
78
+ return 2;
79
+ }
80
+ if (values.help) {
81
+ stdout(`${USAGE}\n`);
82
+ return 0;
83
+ }
84
+
85
+ // Process-local identity only — never a repo-global file (#1659).
86
+ const sessionId = (values['session-id'] ?? env.CLAUDE_CODE_SESSION_ID ?? '').trim();
87
+ if (sessionId === '') {
88
+ stderr('session-commit-files: no session id (pass --session-id or set CLAUDE_CODE_SESSION_ID)\n');
89
+ return 2;
90
+ }
91
+
92
+ const read = readSessionWorkTrees({ sessionId });
93
+ const files = sessionCommitFiles({ trees: read.trees });
94
+
95
+ // Notes go to stderr in BOTH modes, so `--json` never hides that the
96
+ // answer is partial or that no ledger exists (#1659).
97
+ if (read.error) stderr(`session-commit-files: ledger read failed: ${read.error}\n`);
98
+ if (read.complete === null && !read.error) stderr('session-commit-files: no events ledger found\n');
99
+ if (read.malformed > 0) stderr(`session-commit-files: ${read.malformed} unreadable ledger line(s)\n`);
100
+ if (read.invalid > 0) {
101
+ stderr(`session-commit-files: ignored ${read.invalid} own work_tree event(s) without a valid tree/start_ref\n`);
102
+ }
103
+ for (const s of files.skipped) {
104
+ stderr(`session-commit-files: skipped ${s.tree} (${s.start_ref}): ${s.reason}\n`);
105
+ }
106
+
107
+ if (values.json) {
108
+ stdout(`${JSON.stringify({
109
+ trees: read.trees,
110
+ perTree: files.perTree,
111
+ skipped: files.skipped,
112
+ union: files.union,
113
+ malformed: read.malformed,
114
+ invalid: read.invalid,
115
+ complete: read.complete,
116
+ ...(read.error ? { error: read.error } : {}),
117
+ }, null, 2)}\n`);
118
+ return 0;
119
+ }
120
+
121
+ if (read.trees.length === 0) {
122
+ if (read.complete !== null) stderr(`session-commit-files: no work trees recorded for session ${sessionId}\n`);
123
+ return 0;
124
+ }
125
+ if (files.perTree.length === 0) return 0; // every tree skipped — reported on stderr above
126
+ const lines = files.perTree.map((t) => `${t.tree}\t${t.start_ref}\t${t.files.length} files`);
127
+ stdout(`${lines.join('\n')}\n\n${files.union.map((f) => `${f}\n`).join('')}`);
128
+ return 0;
129
+ }
130
+
131
+ if (isMainModule(import.meta.url)) {
132
+ process.exitCode = main(process.argv.slice(2));
133
+ }
@@ -110,9 +110,10 @@ import { join, resolve, relative, sep } from 'node:path';
110
110
  import { execFileSync } from 'node:child_process';
111
111
 
112
112
  import { writeStdoutLineSync, writeJsonAtomicSync } from './lib/io.mjs';
113
- import { readCanonicalSessions } from './lib/sessions-canonical.mjs';
113
+ import { isMeasuredSession, readCanonicalSessions } from './lib/sessions-canonical.mjs';
114
114
  import { isMainModule } from './lib/is-main-module.mjs';
115
115
  import { slashCommandNames } from './lib/user-invocable-skills.mjs';
116
+ import { resolveEventsLedgerRoot } from './lib/platform.mjs';
116
117
 
117
118
  /** Machine-readable schema tag for the --json envelope. */
118
119
  export const SCHEMA = 'site-numbers/1';
@@ -393,6 +394,30 @@ export function isDirty(root) {
393
394
  }
394
395
  }
395
396
 
397
+ /** Metrics counted from a gitignored ledger — the ones `--write` must never republish from the snapshot. */
398
+ const LEDGER_METRICS = Object.freeze(['sessions', 'learnings']);
399
+
400
+ /**
401
+ * Path of a gitignored metrics ledger. A linked worktree carries none of its own,
402
+ * so it reads its main checkout's copy (#1555) — the same mapping
403
+ * `scripts/resolve-session-invocation.mjs` applies to sessions.jsonl. Returns the
404
+ * root's own path when neither exists, so an absent ledger still reads as `null`
405
+ * downstream and the snapshot fallback stays reachable for a fresh clone.
406
+ *
407
+ * @param {string} root
408
+ * @param {string} name e.g. `sessions.jsonl`
409
+ * @returns {string}
410
+ */
411
+ function ledgerFile(root, name) {
412
+ // ledger-root: this tree's own copy first, on purpose; the shared one is the fallback below
413
+ const own = join(root, '.orchestrator', 'metrics', name);
414
+ if (existsSync(own)) return own;
415
+ const main = resolveEventsLedgerRoot(root);
416
+ // ledger-root: the shared copy in the main checkout, read on purpose beside the own one
417
+ const shared = join(main, '.orchestrator', 'metrics', name);
418
+ return main !== resolve(root) && existsSync(shared) ? shared : own;
419
+ }
420
+
396
421
  // ---------------------------------------------------------------------------
397
422
  // The metric table — the part that must never be re-derived by hand
398
423
  // ---------------------------------------------------------------------------
@@ -472,9 +497,9 @@ export const METRIC_DEFS = Object.freeze([
472
497
  // Measured on THIS repo's own ledger @ 2ccea0f2: raw line count 289 vs
473
498
  // canonical (deduped) count 278 — an over-count of 11 records, which is the
474
499
  // bug's magnitude this migration removes.
475
- source: 'grep -c . .orchestrator/metrics/sessions.jsonl (then deduped per session_id — see readCanonicalSessions, #1186)',
500
+ source: 'grep -c . .orchestrator/metrics/sessions.jsonl (then deduped per session_id — see readCanonicalSessions, #1186; synthetic backfill stubs excluded — isMeasuredSession, audit E1)',
476
501
  compute: (root) => {
477
- const file = join(root, '.orchestrator', 'metrics', 'sessions.jsonl');
502
+ const file = ledgerFile(root, 'sessions.jsonl');
478
503
  // Same absent/non-file guard as countJsonlEntries, kept explicit here
479
504
  // because readCanonicalSessions returns [] (not null) on a missing ledger
480
505
  // — collapsing that into fmtCount(0) would silently defeat the
@@ -487,7 +512,9 @@ export const METRIC_DEFS = Object.freeze([
487
512
  // identified for dedup. Measured @ 2ccea0f2 on this repo's own ledger:
488
513
  // 0 such rows (raw 289 lines == 289 identified), so the drop is inert
489
514
  // here; it only matters for a future malformed/legacy row.
490
- return fmtCount(readCanonicalSessions({ filePath: file }).length);
515
+ // Audit E1: count MEASURED sessions only — synthetic backfill stubs are
516
+ // records of a start, not sessions (`isMeasuredSession`).
517
+ return fmtCount(readCanonicalSessions({ filePath: file }).filter(isMeasuredSession).length);
491
518
  },
492
519
  },
493
520
  {
@@ -497,7 +524,7 @@ export const METRIC_DEFS = Object.freeze([
497
524
  snapshotFallback: true,
498
525
  source: 'grep -c . .orchestrator/metrics/learnings.jsonl',
499
526
  compute: (root) => {
500
- const r = countJsonlEntries(join(root, '.orchestrator', 'metrics', 'learnings.jsonl'));
527
+ const r = countJsonlEntries(ledgerFile(root, 'learnings.jsonl'));
501
528
  return r === null ? null : fmtCount(r.entries);
502
529
  },
503
530
  },
@@ -743,7 +770,7 @@ export function collect(root, ctx = {}) {
743
770
  ['sessions', 'sessions.jsonl'],
744
771
  ['learnings', 'learnings.jsonl'],
745
772
  ]) {
746
- const r = countJsonlEntries(join(root, '.orchestrator', 'metrics', file));
773
+ const r = countJsonlEntries(ledgerFile(root, file));
747
774
  if (r && r.malformed > 0) {
748
775
  // #1186: the two ledgers now disagree on what a malformed line DOES to
749
776
  // the published count. `learnings` is still the raw countJsonlEntries
@@ -759,7 +786,7 @@ export function collect(root, ctx = {}) {
759
786
  }
760
787
  }
761
788
 
762
- return { values, missing, warnings, fromSnapshot };
789
+ return { values, missing, warnings, fromSnapshot, localFallbacks };
763
790
  }
764
791
 
765
792
  // ---------------------------------------------------------------------------
@@ -1307,7 +1334,7 @@ export function main(argv = process.argv.slice(2), env = {}) {
1307
1334
  return 2;
1308
1335
  }
1309
1336
 
1310
- const { values, missing, warnings, fromSnapshot } = collect(root, { usage: env.usage });
1337
+ const { values, missing, warnings, fromSnapshot, localFallbacks } = collect(root, { usage: env.usage });
1311
1338
  if (missing.length > 0) {
1312
1339
  stderr(
1313
1340
  `Error: could not measure ${missing.join(', ')} under ${root} — ` +
@@ -1315,6 +1342,22 @@ export function main(argv = process.argv.slice(2), env = {}) {
1315
1342
  );
1316
1343
  return 2;
1317
1344
  }
1345
+ // #1555: a --write of the SERVED site publishes what it measured. A ledger
1346
+ // metric answered by the snapshot is the previous release's number, not a
1347
+ // measurement (5.11.0 and 5.12.0 both republished 475 sessions / 220
1348
+ // learnings from a release worktree), so it is refused. Only the real site/
1349
+ // is guarded; a --write --site <fixture> run publishes nothing. counted-sha
1350
+ // keeps its snapshot answer — the tested no-.git contract below.
1351
+ const writesServedSite = args.write && resolve(siteDir) === resolve(join(root, 'site'));
1352
+ const staleLedgers = localFallbacks.filter((id) => LEDGER_METRICS.includes(id));
1353
+ if (writesServedSite && staleLedgers.length > 0) {
1354
+ stderr(
1355
+ `Error: ${staleLedgers.join(', ')} not measurable under ${root} — the gitignored ledgers are absent ` +
1356
+ `here and in the main checkout; refusing to republish the old ${CENSUS_FILE.join('/')} values. ` +
1357
+ 'Run --write from a checkout that has them.',
1358
+ );
1359
+ return 2;
1360
+ }
1318
1361
  for (const w of warnings) stderr(`WARN: ${w}`);
1319
1362
 
1320
1363
  const dirty = isDirty(root);
@@ -1489,7 +1532,7 @@ export function main(argv = process.argv.slice(2), env = {}) {
1489
1532
  // release preflight's clean-tree check. A fixture run is a read of the repo,
1490
1533
  // never a write to it.
1491
1534
  let censusWritten = false;
1492
- const writesCensus = args.write && resolve(siteDir) === resolve(join(root, 'site'));
1535
+ const writesCensus = writesServedSite;
1493
1536
  if (writesCensus && ok) {
1494
1537
  const res = writeCensusSnapshot(root, values);
1495
1538
  if (res.ok) {
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * snapshot-probe.mjs — run validate-plugin and the test suite on the curated public
4
+ * snapshot tree, materialized outside the repo without a remote (#1712).
5
+ * Library: scripts/lib/snapshot-probe.mjs.
6
+ */
7
+
8
+ import { readFileSync } from 'node:fs';
9
+ import { dirname, join } from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { parseArgs } from 'node:util';
12
+
13
+ import { isMainModule } from './lib/is-main-module.mjs';
14
+ import { KNOWN_STEPS, SnapshotProbeSystemError, runSnapshotProbe } from './lib/snapshot-probe.mjs';
15
+
16
+ const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
17
+
18
+ export const HELP = `Usage: node scripts/snapshot-probe.mjs [options]
19
+
20
+ Builds the curated public GitHub snapshot of a source commit (the tree minus the paths in
21
+ .orchestrator/policy/public-export.json — the same helper the export uses), materializes it
22
+ under $TMPDIR as a one-commit git repo WITHOUT any remote, and runs the steps there. Catches
23
+ tests and validators that depend on a file the public export drops.
24
+
25
+ Options:
26
+ --source <rev> source commit (default HEAD)
27
+ --steps <list> comma-separated steps, from: ${KNOWN_STEPS.join(', ')} (default: both)
28
+ --host <alias> run the 'test' step on an offload host instead: one 'remote-gate' step
29
+ (the pre-push router's full gate against the snapshot commit, no gate
30
+ telemetry); validate-plugin still runs locally
31
+ --local run the 'test' step (full suite) on this host instead
32
+ The 'test' step needs exactly one of --host or --local.
33
+ --keep keep the materialized snapshot directory (printed as dir)
34
+ --no-receipt do not write .orchestrator/tmp/snapshot-probe/<tree>.json
35
+ --json print one JSON object instead of the human summary (remote gate
36
+ output goes to stderr)
37
+ --version print the plugin version
38
+ -h, --help show this help
39
+
40
+ Exit codes:
41
+ 0 every step passed
42
+ 1 usage error
43
+ 2 system error: git, tar or mkdtemp failure, or the remote gate did not run the suite
44
+ to completion (host unknown or not ready, fallback, gate-timeout, a timed-out check,
45
+ no test result) — no receipt is written
46
+ 3 the probe ran and the snapshot is red (a step failed or timed out)
47
+
48
+ A receipt is written only when a suite step ran ('test' or 'remote-gate'); a
49
+ --steps validate-plugin run prints its result and leaves any existing receipt untouched.
50
+ The release preflight accepts only receipts whose steps include a passing suite step:
51
+ 'test' (--local) or 'remote-gate' (--host).
52
+
53
+ Examples:
54
+ node scripts/snapshot-probe.mjs --steps validate-plugin
55
+ node scripts/snapshot-probe.mjs --host m5 --json
56
+ node scripts/snapshot-probe.mjs --local --json
57
+ `;
58
+
59
+ /**
60
+ * Parse argv into probe options.
61
+ * @param {string[]} argv
62
+ * @returns {{ ok: true, opts: object } | { ok: false, error: string } | { ok: true, help: true } | { ok: true, version: true }}
63
+ */
64
+ export function parseCli(argv) {
65
+ let values;
66
+ try {
67
+ ({ values } = parseArgs({
68
+ args: argv,
69
+ strict: true,
70
+ allowPositionals: false,
71
+ options: {
72
+ source: { type: 'string', default: 'HEAD' },
73
+ steps: { type: 'string' },
74
+ local: { type: 'boolean', default: false },
75
+ host: { type: 'string' },
76
+ keep: { type: 'boolean', default: false },
77
+ 'no-receipt': { type: 'boolean', default: false },
78
+ json: { type: 'boolean', default: false },
79
+ version: { type: 'boolean', default: false },
80
+ help: { type: 'boolean', short: 'h', default: false },
81
+ },
82
+ }));
83
+ } catch (err) {
84
+ return { ok: false, error: err.message };
85
+ }
86
+ if (values.help) return { ok: true, help: true };
87
+ if (values.version) return { ok: true, version: true };
88
+ const steps = values.steps === undefined ? [...KNOWN_STEPS] : values.steps.split(',').map((s) => s.trim()).filter(Boolean);
89
+ if (steps.length === 0) return { ok: false, error: '--steps is empty' };
90
+ const unknown = steps.filter((s) => !KNOWN_STEPS.includes(s));
91
+ if (unknown.length > 0) return { ok: false, error: `unknown step(s): ${unknown.join(', ')} (known: ${KNOWN_STEPS.join(', ')})` };
92
+ if (values.local && values.host !== undefined) return { ok: false, error: '--host and --local are mutually exclusive' };
93
+ if (values.host !== undefined && !/^[A-Za-z0-9._-]+$/.test(values.host)) return { ok: false, error: `invalid --host alias '${values.host}'` };
94
+ // Host rule: full suites belong on a remote host; the local host only with --local.
95
+ if (steps.includes('test') && !values.local && values.host === undefined) {
96
+ return { ok: false, error: "the 'test' step runs the full suite — pass --host <alias> or --local, or use --steps validate-plugin" };
97
+ }
98
+ return {
99
+ ok: true,
100
+ opts: { source: values.source, steps, keep: values.keep, writeReceipt: !values['no-receipt'], json: values.json, ...(values.host !== undefined ? { host: values.host } : {}) },
101
+ };
102
+ }
103
+
104
+ function humanSummary(r) {
105
+ const lines = [
106
+ `snapshot probe ${r.ok ? 'GREEN' : 'RED'} — source ${r.sourceSha.slice(0, 12)}, tree ${r.snapshotTree.slice(0, 12)} (route ${r.route})`,
107
+ `files: ${r.counts.source} source, ${r.counts.excluded} excluded, ${r.counts.exported} exported`,
108
+ ];
109
+ for (const s of r.steps) {
110
+ lines.push(` ${s.name}: ${s.status} (exit ${s.exitCode}, ${Math.round(s.durationMs / 1000)}s)`);
111
+ if (s.reason) lines.push(` ${s.reason}`);
112
+ if (s.failedFiles) for (const f of s.failedFiles) lines.push(` FAIL ${f}`);
113
+ if (s.tail) lines.push(...s.tail.split('\n').map((l) => ` | ${l}`));
114
+ }
115
+ if (r.dir) lines.push(`kept: ${r.dir}`);
116
+ return lines.join('\n');
117
+ }
118
+
119
+ /**
120
+ * @param {string[]} argv
121
+ * @param {{ repoRoot?: string, probe?: typeof runSnapshotProbe }} [deps]
122
+ * @returns {Promise<number>} exit code
123
+ */
124
+ export async function main(argv, { repoRoot = REPO_ROOT, probe = runSnapshotProbe } = {}) {
125
+ const parsed = parseCli(argv);
126
+ if (!parsed.ok) {
127
+ process.stderr.write(`snapshot-probe: ${parsed.error}\n\n${HELP}`);
128
+ return 1;
129
+ }
130
+ if (parsed.help) {
131
+ process.stdout.write(HELP);
132
+ return 0;
133
+ }
134
+ if (parsed.version) {
135
+ process.stdout.write(`${JSON.parse(readFileSync(join(repoRoot, 'package.json'), 'utf8')).version}\n`);
136
+ return 0;
137
+ }
138
+ const { json, ...opts } = parsed.opts;
139
+ let result;
140
+ try {
141
+ result = await probe({ repoRoot, ...opts });
142
+ } catch (err) {
143
+ const message = err instanceof SnapshotProbeSystemError ? err.message : `unexpected failure: ${err?.message ?? err}`;
144
+ if (json) process.stdout.write(`${JSON.stringify({ ok: false, error: message })}\n`);
145
+ process.stderr.write(`snapshot-probe: ${message}\n`);
146
+ return 2;
147
+ }
148
+ process.stdout.write(json ? `${JSON.stringify(result)}\n` : `${humanSummary(result)}\n`);
149
+ return result.ok ? 0 : 3;
150
+ }
151
+
152
+ if (isMainModule(import.meta.url)) {
153
+ main(process.argv.slice(2)).then((code) => {
154
+ process.exitCode = code;
155
+ });
156
+ }
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { execFile } from 'node:child_process';
7
+ import { promisify, parseArgs } from 'node:util';
8
+ import { measure } from './lib/so-hud/runtime.mjs';
9
+ import { isMainModule } from './lib/is-main-module.mjs';
10
+ import { parseSessionId } from './lib/session-id.mjs';
11
+ const exec = promisify(execFile);
12
+ const SO_ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
13
+
14
+ export async function main(argv, deps = {}) {
15
+ const output = deps.output ?? (s => process.stdout.write(s));
16
+ const error = deps.error ?? (s => process.stderr.write(s));
17
+ let values;
18
+ try {
19
+ ({ values } = parseArgs({ args: argv, options: { json: { type: 'boolean' }, 'repo-root': { type: 'string' }, 'session-id': { type: 'string' }, 'register-root': { type: 'boolean' }, help: { type: 'boolean' } }, allowPositionals: false }));
20
+ if (values.help) {
21
+ output('so-hud --json [--repo-root <path>] [--session-id <raw-uuid>] [--register-root]\nRead-only HUD JSON v1. --register-root registers this SO installation (0600).\nDefault repo: git toplevel of cwd. --session-id: the session whose agents are counted, read beside its own session.lock (#1730); without it the id comes from CLAUDE_SESSION_ID/SO_SESSION_ID and agents are read in the repo.\nExit 0: measured; 2: invocation/registration error; 6: repository unavailable.\n');
22
+ return 0;
23
+ }
24
+ if (!values.json && !values['register-root']) throw new Error('--json required');
25
+ if (values['session-id'] !== undefined && parseSessionId(values['session-id'])?.format !== 'uuid') throw new Error('bad --session-id');
26
+ } catch (e) {
27
+ error(e?.message === 'bad --session-id' ? 'so-hud: --session-id braucht eine Roh-UUID; siehe --help\n' : 'so-hud: --json erforderlich; siehe --help\n');
28
+ return 2;
29
+ }
30
+ if (values['register-root']) {
31
+ try {
32
+ const dir = path.join(deps.home ?? os.homedir(), '.config/session-orchestrator/hud');
33
+ await fs.mkdir(dir, { recursive: true, mode: 0o700 });
34
+ await fs.chmod(dir, 0o700);
35
+ const target = path.join(dir, 'so-root');
36
+ const temp = `${target}.${process.pid}.tmp`;
37
+ try { await fs.writeFile(temp, `${SO_ROOT}\n`, { mode: 0o600, flag: 'wx' }); await fs.rename(temp, target); }
38
+ finally { await fs.unlink(temp).catch(() => {}); }
39
+ } catch { error('so-hud: Registrierung fehlgeschlagen\n'); return 2; }
40
+ if (!values.json) return 0;
41
+ }
42
+ let root;
43
+ try {
44
+ // A session started in a subdirectory measures its working copy: the git toplevel. Not a repo → the given
45
+ // directory itself (never fail the measurement).
46
+ const given = values['repo-root'] ? path.resolve(values['repo-root']) : null;
47
+ const top = await (deps.exec ?? exec)('git', [...(given ? ['-C', given] : []), 'rev-parse', '--show-toplevel'], { timeout: 700 })
48
+ .then(r => r.stdout.trim(), () => '');
49
+ if (!top && !given) throw new Error('missing');
50
+ root = await fs.realpath(top || given);
51
+ // git answers in physical spelling, but the session writes file paths in the spelling it was given, and the
52
+ // collision guard compares those as strings: keep the caller's spelling of the toplevel where it is derivable.
53
+ // Peers are compared physically in collect.mjs. The caller's spelling is used only when it resolves to the
54
+ // physical toplevel: a symlinked last path part (<d>/elsewhere/scripts → <d>/repo/scripts) would otherwise
55
+ // map to <d>/elsewhere, which is not the repository.
56
+ if (given) {
57
+ const sub = path.relative(root, await fs.realpath(given));
58
+ let candidate = null;
59
+ if (sub === '') candidate = given;
60
+ else if (sub !== '..' && !sub.startsWith(`..${path.sep}`) && given.endsWith(`${path.sep}${sub}`)) candidate = given.slice(0, -sub.length - 1);
61
+ if (candidate !== null && await fs.realpath(candidate).catch(() => null) === root) root = candidate;
62
+ }
63
+ if (!(await fs.stat(root)).isDirectory()) throw new Error('missing');
64
+ } catch { error('so-hud: Repo nicht ermittelbar\n'); return 6; }
65
+ try { output(`${JSON.stringify(await (deps.measure ?? measure)(root, { sessionId: values['session-id'] ?? null }))}\n`); return 0; }
66
+ catch { error('so-hud: Messung fehlgeschlagen\n'); return 6; }
67
+ }
68
+ if (isMainModule(import.meta.url)) process.exitCode = await main(process.argv.slice(2));
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+ /** session-start Phase 1.5 Idle Reset (#1560): rewrite a completed STATE.md to idle, under the STATE.md lock. */
3
+ import { readFileSync, statSync } from 'node:fs';
4
+ import { resolve } from 'node:path';
5
+ import { parseArgs } from 'node:util';
6
+ import { resetStateMdToIdleOnDisk } from './lib/state-md/idle-reset.mjs';
7
+
8
+ const HELP = `Usage: node scripts/state-md-idle-reset.mjs [--repo-root <dir>] [--json]
9
+
10
+ Reset the repo's STATE.md to idle when its status is completed (session-start
11
+ Phase 1.5, Idle Reset rules 1-7): status idle, current-wave 0, Wave History
12
+ demoted into Previous Session, Deviations emptied, scope-baseline-* keys
13
+ deleted. The five recommendation keys stay in the frontmatter so the Phase 7.5
14
+ Mode-Selector can read them. Any other status is a no-op; reasons:
15
+ status-not-completed, unparseable, missing (no or empty STATE.md),
16
+ foreign-state-md (only another platform's foreign STATE.md exists, #1618).
17
+
18
+ --repo-root <dir> Repository root holding the state dir (default: cwd)
19
+ --json Emit {"changed", "reason", "path"} instead of one line
20
+ --help Show this help
21
+ --version Show the plugin version
22
+
23
+ Example: node scripts/state-md-idle-reset.mjs --json
24
+ Exit codes: 0 reset or no-op, 1 bad CLI arguments,
25
+ 2 I/O error, --repo-root not a directory, or a write the
26
+ STATE.md guards refused
27
+ `;
28
+
29
+ /** Reasons under which writeStateMd refused a wanted write. */
30
+ const REFUSED = new Set(['size-ceiling', 'frontmatter-unsafe']);
31
+
32
+ let options;
33
+ try {
34
+ options = parseArgs({
35
+ options: {
36
+ 'repo-root': { type: 'string' },
37
+ json: { type: 'boolean' },
38
+ help: { type: 'boolean' },
39
+ version: { type: 'boolean' },
40
+ },
41
+ allowPositionals: false,
42
+ }).values;
43
+ } catch (error) {
44
+ process.stderr.write(`state-md-idle-reset: ${error.message}\n`);
45
+ process.exit(1);
46
+ }
47
+
48
+ if (options.help) {
49
+ process.stdout.write(HELP);
50
+ } else if (options.version) {
51
+ process.stdout.write(
52
+ `${JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version}\n`,
53
+ );
54
+ } else {
55
+ const repoRoot = resolve(options['repo-root'] ?? process.cwd());
56
+ // The STATE.md lock would otherwise create <repoRoot>/.orchestrator/ for a
57
+ // root that does not exist and report a harmless-looking `missing`.
58
+ let rootStat;
59
+ try {
60
+ rootStat = statSync(repoRoot, { throwIfNoEntry: false });
61
+ } catch {
62
+ rootStat = undefined; // EACCES / ELOOP: an I/O error, exit 2 below
63
+ }
64
+ if (!rootStat?.isDirectory()) {
65
+ process.stderr.write(`state-md-idle-reset: --repo-root is not a directory: ${repoRoot}\n`);
66
+ process.exit(2);
67
+ }
68
+ let result;
69
+ try {
70
+ result = await resetStateMdToIdleOnDisk(repoRoot);
71
+ } catch (error) {
72
+ process.stderr.write(`state-md-idle-reset: ${error.message}\n`);
73
+ process.exit(2);
74
+ }
75
+ const reason = result.reason ?? null;
76
+ process.stdout.write(
77
+ options.json
78
+ ? `${JSON.stringify({ changed: result.changed, reason, path: result.path })}\n`
79
+ : `${result.changed ? 'reset to idle' : `unchanged (${reason})`}: ${result.path}\n`,
80
+ );
81
+ if (REFUSED.has(reason)) process.exit(2);
82
+ }