session-orchestrator 5.12.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 (385) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  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 +162 -4
  11. package/README.md +126 -127
  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 +1 -1
  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 +90 -17
  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 +49 -15
  47. package/docs/session-config-template.md +30 -22
  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 +139 -49
  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 -2
  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 +39 -27
  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/section-extractor.mjs +65 -11
  143. package/scripts/lib/config/template-keys.mjs +153 -0
  144. package/scripts/lib/config/vault-integration.mjs +1 -0
  145. package/scripts/lib/config.mjs +2 -2
  146. package/scripts/lib/coordinator-snapshot.mjs +1 -1
  147. package/scripts/lib/discovery/triage-state.mjs +2 -1
  148. package/scripts/lib/dispatcher/rank.mjs +5 -7
  149. package/scripts/lib/ecosystem-health.mjs +2 -2
  150. package/scripts/lib/eval/engine.mjs +3 -2
  151. package/scripts/lib/eval/session-resolve.mjs +6 -2
  152. package/scripts/lib/eval/sink.mjs +4 -3
  153. package/scripts/lib/events-retention-banner.mjs +3 -2
  154. package/scripts/lib/events-schema.mjs +10 -0
  155. package/scripts/lib/events.mjs +23 -12
  156. package/scripts/lib/evolve/autonomy-verdict.mjs +4 -9
  157. package/scripts/lib/evolve/autopilot-effectiveness.mjs +8 -18
  158. package/scripts/lib/fleet-protocol.mjs +4 -0
  159. package/scripts/lib/gates/gate-full.mjs +172 -11
  160. package/scripts/lib/gates/gate-helpers.mjs +236 -23
  161. package/scripts/lib/git-config-drift.mjs +141 -7
  162. package/scripts/lib/github-export.mjs +447 -0
  163. package/scripts/lib/gitlab-portfolio/cli.mjs +16 -5
  164. package/scripts/lib/handover-queue-marks.mjs +159 -0
  165. package/scripts/lib/hardware-pattern-detector.mjs +103 -13
  166. package/scripts/lib/harness-audit/categories/category1.mjs +16 -4
  167. package/scripts/lib/harness-audit/categories/category4.mjs +11 -8
  168. package/scripts/lib/harness-audit/categories/category6.mjs +25 -5
  169. package/scripts/lib/harness-audit/categories/category9.mjs +10 -4
  170. package/scripts/lib/host-identity.mjs +9 -5
  171. package/scripts/lib/http-request.mjs +64 -0
  172. package/scripts/lib/instruction-budget-guard.mjs +30 -22
  173. package/scripts/lib/io.mjs +6 -3
  174. package/scripts/lib/issue-budget-reconcile.mjs +3 -2
  175. package/scripts/lib/issue-budget.mjs +36 -7
  176. package/scripts/lib/learnings/evidence-candidates.mjs +154 -0
  177. package/scripts/lib/learnings/expiry-sweep.mjs +99 -7
  178. package/scripts/lib/learnings/io.mjs +289 -15
  179. package/scripts/lib/learnings/select.mjs +53 -4
  180. package/scripts/lib/learnings/store-role.mjs +209 -0
  181. package/scripts/lib/learnings/surface.mjs +46 -7
  182. package/scripts/lib/ledger-path.mjs +113 -0
  183. package/scripts/lib/maintenance-due-banner.mjs +63 -12
  184. package/scripts/lib/memory-banner.mjs +5 -6
  185. package/scripts/lib/memory-proposals/collector.mjs +5 -2
  186. package/scripts/lib/memory-proposals/sink.mjs +7 -4
  187. package/scripts/lib/memory-proposals/store.mjs +22 -5
  188. package/scripts/lib/mode-selector-accuracy.mjs +2 -1
  189. package/scripts/lib/no-follow-open.mjs +57 -0
  190. package/scripts/lib/orphan-reaper/reaper-audit.mjs +4 -2
  191. package/scripts/lib/own-lock-root.mjs +95 -0
  192. package/scripts/lib/owner-interview.mjs +2 -1
  193. package/scripts/lib/owner-yaml.mjs +70 -7
  194. package/scripts/lib/platform.mjs +161 -9
  195. package/scripts/lib/plugin-update-banner.mjs +15 -11
  196. package/scripts/lib/pre-push-local-partial.mjs +117 -0
  197. package/scripts/lib/pre-push-remote-gate.mjs +151 -25
  198. package/scripts/lib/process-group.mjs +270 -34
  199. package/scripts/lib/project-hygiene.mjs +28 -0
  200. package/scripts/lib/quality-gate.mjs +33 -9
  201. package/scripts/lib/reconcile/backlog.mjs +29 -15
  202. package/scripts/lib/reconcile/engine.mjs +26 -2
  203. package/scripts/lib/reconcile/idempotency.mjs +65 -3
  204. package/scripts/lib/reconcile/rule-expiry-sweep.mjs +53 -12
  205. package/scripts/lib/reconcile/verdict-ledger.mjs +224 -0
  206. package/scripts/lib/reconcile-nudge-banner.mjs +40 -6
  207. package/scripts/lib/resource-probe/evaluate.mjs +32 -9
  208. package/scripts/lib/resource-probe/probe-platform.mjs +3 -1
  209. package/scripts/lib/rule-loader.mjs +20 -10
  210. package/scripts/lib/scope-baseline.mjs +123 -8
  211. package/scripts/lib/scope-echo.mjs +5 -2
  212. package/scripts/lib/scope-gate.mjs +55 -20
  213. package/scripts/lib/session-close-backfill.mjs +75 -7
  214. package/scripts/lib/session-end/close-precheck.mjs +151 -0
  215. package/scripts/lib/session-end/phase-skip.mjs +8 -7
  216. package/scripts/lib/session-end/tail-runner.mjs +7 -2
  217. package/scripts/lib/session-id.mjs +9 -1
  218. package/scripts/lib/session-identity/own-session.mjs +63 -0
  219. package/scripts/lib/session-lock.mjs +114 -0
  220. package/scripts/lib/session-record-repair.mjs +245 -35
  221. package/scripts/lib/session-registry.mjs +72 -9
  222. package/scripts/lib/session-schema/constants.mjs +9 -0
  223. package/scripts/lib/session-schema/filters.mjs +56 -1
  224. package/scripts/lib/session-schema/validator.mjs +15 -5
  225. package/scripts/lib/session-start-probes.mjs +149 -22
  226. package/scripts/lib/session-token-rollup.mjs +2 -1
  227. package/scripts/lib/session-work-trees.mjs +166 -0
  228. package/scripts/lib/sessions-canonical.mjs +61 -3
  229. package/scripts/lib/sessions-integrity-banner.mjs +17 -39
  230. package/scripts/lib/sessions-staleness-banner.mjs +8 -5
  231. package/scripts/lib/sh-quote.mjs +1 -0
  232. package/scripts/lib/skill-evidence-window.mjs +5 -1
  233. package/scripts/lib/skill-health/join.mjs +10 -9
  234. package/scripts/lib/skill-invocations-schema.mjs +13 -0
  235. package/scripts/lib/skill-judgments-schema.mjs +6 -2
  236. package/scripts/lib/snapshot-probe.mjs +414 -0
  237. package/scripts/lib/so-hud/collect.mjs +49 -5
  238. package/scripts/lib/so-hud/runtime.mjs +15 -4
  239. package/scripts/lib/spiral-carryover.mjs +8 -1
  240. package/scripts/lib/state-md/frontmatter-mutators.mjs +266 -8
  241. package/scripts/lib/state-md/idle-reset.mjs +14 -5
  242. package/scripts/lib/state-md/mission-status.mjs +5 -1
  243. package/scripts/lib/state-md-peer-guard.mjs +2 -0
  244. package/scripts/lib/state-md.mjs +31 -8
  245. package/scripts/lib/sunset/walker.mjs +4 -3
  246. package/scripts/lib/telemetry/consent.mjs +4 -2
  247. package/scripts/lib/telemetry/queue.mjs +3 -3
  248. package/scripts/lib/telemetry/sync.mjs +45 -9
  249. package/scripts/lib/test-runner/artifact-paths.mjs +10 -2
  250. package/scripts/lib/tmux-layout/layouts.mjs +20 -3
  251. package/scripts/lib/ux-grill/paths.mjs +5 -1
  252. package/scripts/lib/validate/check-agents.mjs +37 -6
  253. package/scripts/lib/validate/check-component-paths.mjs +25 -1
  254. package/scripts/lib/validate/check-guard-requires-parity.mjs +3 -3
  255. package/scripts/lib/validate/check-hooks-symmetry.mjs +53 -32
  256. package/scripts/lib/validate/check-json-files.mjs +3 -1
  257. package/scripts/lib/validate/check-learning-provenance.mjs +3 -2
  258. package/scripts/lib/validate/check-learnings-shell-writes.mjs +10 -4
  259. package/scripts/lib/validate/check-metrics-ledger-root.mjs +234 -0
  260. package/scripts/lib/validate/check-owner-leakage.mjs +159 -11
  261. package/scripts/lib/validate/check-test-posix-shell.mjs +246 -0
  262. package/scripts/lib/validate/check-unwired-features.mjs +4 -2
  263. package/scripts/lib/validate/check-vcs-repo-flag-positional.mjs +58 -0
  264. package/scripts/lib/validate/check-vcs-repo-flag.mjs +259 -73
  265. package/scripts/lib/validate/check-workflow-continue-on-error.mjs +63 -0
  266. package/scripts/lib/validate/confidential-names.mjs +3 -1
  267. package/scripts/lib/vault-mirror/migrate-ids.mjs +321 -0
  268. package/scripts/lib/vault-mirror/namespace.mjs +19 -7
  269. package/scripts/lib/vault-mirror/process.mjs +56 -27
  270. package/scripts/lib/vault-mirror/prune.mjs +5 -2
  271. package/scripts/lib/vault-mirror/render-learnings.mjs +3 -3
  272. package/scripts/lib/vault-mirror/render-sessions.mjs +36 -53
  273. package/scripts/lib/vault-mirror/retention.mjs +2 -2
  274. package/scripts/lib/vault-mirror/utils.mjs +79 -0
  275. package/scripts/lib/vault-staleness-banner.mjs +4 -7
  276. package/scripts/lib/vault-status/board-writer.mjs +2 -1
  277. package/scripts/lib/vault-status/narrative-mirror.mjs +66 -7
  278. package/scripts/lib/wave-executor/foreign-dispatch.mjs +20 -0
  279. package/scripts/lib/win-process-tree.mjs +137 -0
  280. package/scripts/lib/workspace.mjs +1 -1
  281. package/scripts/lib/worktree/constants.mjs +1 -1
  282. package/scripts/lib/worktree/lifecycle.mjs +1 -1
  283. package/scripts/lib/worktree/listing.mjs +1 -1
  284. package/scripts/lib/zx.mjs +50 -0
  285. package/scripts/mcp-server.mjs +16 -7
  286. package/scripts/mcp-server.sh +13 -83
  287. package/scripts/measure-policy-cache-effectiveness.mjs +7 -5
  288. package/scripts/memory-propose.mjs +10 -3
  289. package/scripts/migrate-cold-start-seed.mjs +7 -0
  290. package/scripts/migrate-legacy-learnings.sh +22 -0
  291. package/scripts/migrate-sessions-jsonl.mjs +16 -2
  292. package/scripts/migrate-vault-paths.mjs +29 -13
  293. package/scripts/parse-config.mjs +71 -14
  294. package/scripts/print-learnings-index.mjs +25 -2
  295. package/scripts/release-session-lock.mjs +94 -7
  296. package/scripts/release.mjs +451 -24
  297. package/scripts/relocate-vault-corpus.mjs +11 -7
  298. package/scripts/repair-invalid-sessions.mjs +68 -3
  299. package/scripts/resolve-session-invocation.mjs +10 -17
  300. package/scripts/run-migrate-v2-cross-repo.mjs +56 -1
  301. package/scripts/run-quality-gate.mjs +33 -7
  302. package/scripts/self-update.mjs +56 -7
  303. package/scripts/session-commit-files.mjs +133 -0
  304. package/scripts/site-numbers.mjs +52 -9
  305. package/scripts/snapshot-probe.mjs +156 -0
  306. package/scripts/so-hud.mjs +29 -6
  307. package/scripts/state-md-idle-reset.mjs +2 -1
  308. package/scripts/sweep-expired-learnings.mjs +54 -6
  309. package/scripts/sweep-expired-rules.mjs +4 -1
  310. package/scripts/validate-plugin.mjs +22 -0
  311. package/scripts/vault-integration-watcher.mjs +54 -6
  312. package/scripts/vault-mirror.mjs +9 -0
  313. package/skills/_shared/fleet-protocol.md +142 -1
  314. package/skills/_shared/instruction-file-resolution.md +1 -1
  315. package/skills/_shared/monitor-patterns.md +44 -25
  316. package/skills/_shared/platform-tools.md +7 -0
  317. package/skills/_shared/state-ownership.md +3 -2
  318. package/skills/architecture/references/domain-model.md +1 -1
  319. package/skills/bootstrap/SKILL.md +3 -1
  320. package/skills/bootstrap/_shared-template.md +4 -4
  321. package/skills/bootstrap/fast-template.md +1 -1
  322. package/skills/bootstrap/private-contract.md +1 -1
  323. package/skills/bootstrap/references/bootstrap-retroactive-flow.md +17 -9
  324. package/skills/bootstrap/templates/agents/project-code-review.md +5 -1
  325. package/skills/bootstrap/templates/agents/project-discovery.md +5 -1
  326. package/skills/bootstrap/templates/agents/project-quality-gate.md +5 -1
  327. package/skills/claude-md-drift-check/SKILL.md +3 -3
  328. package/skills/claude-md-drift-check/checker.mjs +61 -101
  329. package/skills/close/SKILL.md +20 -7
  330. package/skills/discovery/probes/docs-staleness.mjs +7 -5
  331. package/skills/discovery/probes/frontend-slop.mjs +7 -3
  332. package/skills/discovery/probes/ssot-code-diff.mjs +5 -3
  333. package/skills/discovery/probes/supply-chain-slopcheck.mjs +10 -8
  334. package/skills/discovery/probes/vault-narrative-staleness.mjs +30 -10
  335. package/skills/discovery/probes/vault-staleness.mjs +5 -3
  336. package/skills/discovery/probes-session.md +3 -0
  337. package/skills/discovery/probes-vault.md +2 -0
  338. package/skills/dispatcher/SKILL.md +1 -1
  339. package/skills/eval/SKILL.md +3 -1
  340. package/skills/evolve/SKILL.md +2 -0
  341. package/skills/evolve/references/evolve-analyze-mode.md +3 -3
  342. package/skills/evolve/references/evolve-dialectic-mode.md +1 -1
  343. package/skills/gitlab-ops/SKILL.md +7 -6
  344. package/skills/gitlab-portfolio/SKILL.md +2 -2
  345. package/skills/go/SKILL.md +13 -1
  346. package/skills/npm-publish/SKILL.md +5 -2
  347. package/skills/plan/SKILL.md +23 -10
  348. package/skills/plan/mode-feature.md +13 -8
  349. package/skills/plan/mode-new.md +6 -14
  350. package/skills/plan/prd-feature-template.md +19 -0
  351. package/skills/plan/prd-full-template.md +12 -5
  352. package/skills/plan/prd-premortem-panel.md +48 -0
  353. package/skills/plan/prd-reviewer-prompt.md +11 -3
  354. package/skills/plan/soul.md +1 -1
  355. package/skills/portfolio/SKILL.md +5 -6
  356. package/skills/quality-gates/SKILL.md +2 -4
  357. package/skills/reconcile/SKILL.md +27 -4
  358. package/skills/release/SKILL.md +16 -8
  359. package/skills/session-end/SKILL.md +94 -11
  360. package/skills/session-end/phase-3-2-docs-verification.md +1 -0
  361. package/skills/session-end/phase-3-6-tail.md +11 -7
  362. package/skills/session-end/phase-3-7a-recommendations.md +9 -2
  363. package/skills/session-end/plan-verification.md +16 -1
  364. package/skills/session-end/references/phase-3-documentation-updates.md +14 -12
  365. package/skills/session-end/references/phase-5-issue-cleanup.md +1 -1
  366. package/skills/session-end/references/session-summary-template.md +4 -0
  367. package/skills/session-end/session-metrics-write.md +7 -4
  368. package/skills/session-plan/SKILL.md +5 -3
  369. package/skills/session-start/SKILL.md +9 -3
  370. package/skills/session-start/phase-4-5-resource-health.md +11 -5
  371. package/skills/session-start/phase-8-5-express-path.md +4 -3
  372. package/skills/session-start/references/phase-1-2-session-lock.md +7 -5
  373. package/skills/session-start/references/phase-1-5-session-continuity.md +2 -2
  374. package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
  375. package/skills/session-start/references/phase-4-ssot-environment-check.md +3 -2
  376. package/skills/tmux-layout/SKILL.md +2 -2
  377. package/skills/vault-sync/validator.mjs +3 -2
  378. package/skills/wave-executor/SKILL.md +5 -4
  379. package/skills/wave-executor/references/wave-executor-state-init.md +3 -3
  380. package/skills/wave-executor/references/wave-loop-dispatch.md +11 -5
  381. package/skills/wave-executor/references/wave-loop-review.md +13 -5
  382. package/skills/wave-executor/references/wave-loop-scope-manifest.md +1 -1
  383. package/assets/og-card.png +0 -0
  384. package/assets/og-card.svg +0 -47
  385. package/assets/wave-lifecycle.svg +0 -98
@@ -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
+ }
@@ -7,6 +7,7 @@ import { execFile } from 'node:child_process';
7
7
  import { promisify, parseArgs } from 'node:util';
8
8
  import { measure } from './lib/so-hud/runtime.mjs';
9
9
  import { isMainModule } from './lib/is-main-module.mjs';
10
+ import { parseSessionId } from './lib/session-id.mjs';
10
11
  const exec = promisify(execFile);
11
12
  const SO_ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
12
13
 
@@ -15,13 +16,17 @@ export async function main(argv, deps = {}) {
15
16
  const error = deps.error ?? (s => process.stderr.write(s));
16
17
  let values;
17
18
  try {
18
- ({ values } = parseArgs({ args: argv, options: { json: { type: 'boolean' }, 'repo-root': { type: 'string' }, 'register-root': { type: 'boolean' }, help: { type: 'boolean' } }, allowPositionals: false }));
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 }));
19
20
  if (values.help) {
20
- output('so-hud --json [--repo-root <path>] [--register-root]\nRead-only HUD JSON v1. --register-root registers this SO installation (0600).\nDefault repo: git toplevel of cwd. Exit 0: measured; 2: invocation/registration error; 6: repository unavailable.\n');
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');
21
22
  return 0;
22
23
  }
23
24
  if (!values.json && !values['register-root']) throw new Error('--json required');
24
- } catch { error('so-hud: --json erforderlich; siehe --help\n'); return 2; }
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
+ }
25
30
  if (values['register-root']) {
26
31
  try {
27
32
  const dir = path.join(deps.home ?? os.homedir(), '.config/session-orchestrator/hud');
@@ -36,10 +41,28 @@ export async function main(argv, deps = {}) {
36
41
  }
37
42
  let root;
38
43
  try {
39
- root = values['repo-root'] ? path.resolve(values['repo-root']) : (await (deps.exec ?? exec)('git', ['rev-parse', '--show-toplevel'], { timeout: 700 })).stdout.trim();
40
- if (!root || !(await fs.stat(root)).isDirectory()) throw new Error('missing');
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');
41
64
  } catch { error('so-hud: Repo nicht ermittelbar\n'); return 6; }
42
- try { output(`${JSON.stringify(await (deps.measure ?? measure)(root))}\n`); return 0; }
65
+ try { output(`${JSON.stringify(await (deps.measure ?? measure)(root, { sessionId: values['session-id'] ?? null }))}\n`); return 0; }
43
66
  catch { error('so-hud: Messung fehlgeschlagen\n'); return 6; }
44
67
  }
45
68
  if (isMainModule(import.meta.url)) process.exitCode = await main(process.argv.slice(2));
@@ -12,7 +12,8 @@ Phase 1.5, Idle Reset rules 1-7): status idle, current-wave 0, Wave History
12
12
  demoted into Previous Session, Deviations emptied, scope-baseline-* keys
13
13
  deleted. The five recommendation keys stay in the frontmatter so the Phase 7.5
14
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).
15
+ status-not-completed, unparseable, missing (no or empty STATE.md),
16
+ foreign-state-md (only another platform's foreign STATE.md exists, #1618).
16
17
 
17
18
  --repo-root <dir> Repository root holding the state dir (default: cwd)
18
19
  --json Emit {"changed", "reason", "path"} instead of one line
@@ -139,6 +139,7 @@ import { atomicWriteText, envelopeToError } from './lib/io.mjs';
139
139
  import { validateLearning } from './lib/learnings/schema.mjs';
140
140
  import { validatePathInsideProject } from './lib/path-utils.mjs';
141
141
  import { emitEvolveCompleted } from './lib/learnings/evolve-telemetry.mjs';
142
+ import { resolveLedgerPath, resolveLedgerRoot } from './lib/ledger-path.mjs';
142
143
 
143
144
  const DEFAULT_FILE = '.orchestrator/metrics/learnings.jsonl';
144
145
  const DEFAULT_ARCHIVE = '.orchestrator/metrics/learnings-archive.jsonl';
@@ -257,6 +258,7 @@ function parseArgs(argv) {
257
258
  generation: null,
258
259
  acceptEmbedded: false,
259
260
  };
261
+ let fileExplicit = false;
260
262
  for (let i = 0; i < argv.length; i++) {
261
263
  const a = argv[i];
262
264
  if (a === '--prune') {
@@ -296,6 +298,7 @@ function parseArgs(argv) {
296
298
  args.snapshot = pathValue(a, argv[++i]);
297
299
  } else if (a === '--file') {
298
300
  args.file = pathValue(a, argv[++i]);
301
+ fileExplicit = true;
299
302
  } else if (a === '--archive') {
300
303
  args.archive = pathValue(a, argv[++i]);
301
304
  args.archiveExplicit = true;
@@ -343,6 +346,12 @@ function parseArgs(argv) {
343
346
  }
344
347
  }
345
348
 
349
+ // #1722: the DEFAULT store/archive route through the ledger resolver (main
350
+ // checkout when cwd is a linked worktree that gitignores them); outside one
351
+ // the relative defaults are kept verbatim. Explicit --file/--archive stay.
352
+ if (!fileExplicit) args.file = routedDefault(DEFAULT_FILE);
353
+ if (!args.archiveExplicit) args.archive = routedDefault(DEFAULT_ARCHIVE);
354
+
346
355
  // Mode/flag mismatches are usage errors, never silent no-ops: a `--grace-days`
347
356
  // that the prune path ignores would read as "the grace window applied" in a
348
357
  // transcript, and an `--entries` the sweep ignores would read as "my next
@@ -381,7 +390,7 @@ function parseArgs(argv) {
381
390
  // (macOS /var → /private/var) refused a store inside it. The file goes
382
391
  // through resolveLedger (realpath when it exists), because
383
392
  // canonicalizeRoot realpaths only the root.
384
- if (!validatePathInsideProject(resolveLedger(args.file), args.repoRoot, { canonicalizeRoot: true }).ok) {
393
+ if (ledgerRootOf(args.file, args.repoRoot) === null) {
385
394
  usageError(`--file ${args.file} lies outside --repo-root ${args.repoRoot} — nothing written`);
386
395
  }
387
396
  }
@@ -441,7 +450,9 @@ async function runSweep(args) {
441
450
  } else {
442
451
  process.stdout.write(
443
452
  `sweep-expired-learnings: scanned=${summary.scanned} kept=${summary.kept} ` +
444
- `archived=${summary.archived} dry_run=${summary.dryRun} archive=${summary.archivePath}\n`
453
+ `archived=${summary.archived} dry_run=${summary.dryRun} archive=${summary.archivePath}` +
454
+ // An inbox-role apply (#1516) wrote nothing; say so, as --json does.
455
+ `${summary.skipped ? ` skipped=${summary.skipped}` : ''}\n`
445
456
  );
446
457
  }
447
458
  }
@@ -609,6 +620,39 @@ function resolveLedger(p) {
609
620
  }
610
621
  }
611
622
 
623
+ /**
624
+ * The default ledger for this cwd (#1722): `rel` verbatim unless the resolver
625
+ * maps it elsewhere (linked worktree that gitignores it), then that absolute path.
626
+ *
627
+ * @param {string} rel
628
+ * @returns {string}
629
+ */
630
+ function routedDefault(rel) {
631
+ const routed = resolveLedgerPath(rel, process.cwd());
632
+ return routed === path.resolve(rel) ? rel : routed;
633
+ }
634
+
635
+ /**
636
+ * Which root a store lies inside, canonically (#1500 F4): `repoRoot` itself, or
637
+ * its ledger root (the main checkout of a linked worktree, #1722) — but only for
638
+ * a file inside the DIRECTORY of the mapped default store, never anywhere in
639
+ * that other checkout (a worktree session must not rewrite a foreign tree).
640
+ * `null` when it lies outside both.
641
+ *
642
+ * @param {string} file
643
+ * @param {string} repoRoot
644
+ * @returns {string|null}
645
+ */
646
+ function ledgerRootOf(file, repoRoot) {
647
+ const abs = resolveLedger(file);
648
+ if (validatePathInsideProject(abs, repoRoot, { canonicalizeRoot: true }).ok) return repoRoot;
649
+ const storeDir = path.dirname(resolveLedgerPath(DEFAULT_FILE, repoRoot));
650
+ if (validatePathInsideProject(abs, storeDir, { canonicalizeRoot: true }).ok) {
651
+ return resolveLedgerRoot(DEFAULT_FILE, repoRoot);
652
+ }
653
+ return null;
654
+ }
655
+
612
656
  /**
613
657
  * Exit 1 when `--snapshot` would replace the store or the archive. The rename
614
658
  * lands the header as the store's line 1 with no `.bak` (and drops malformed
@@ -820,7 +864,9 @@ async function runPrune(args) {
820
864
  // hand-assembled `--entries` sidecar) never wrote anything, so it must not
821
865
  // report a completed run either. Best-effort — never changes this
822
866
  // command's exit code or stdout contract.
823
- if (!args.dryRun) {
867
+ // An inbox-role apply (#1516) returns `skipped`: it archived nothing and did
868
+ // not rewrite the store, so there is no completed store write to report.
869
+ if (!args.dryRun && !result.skipped) {
824
870
  await emitEvolveCompleted({
825
871
  repoRoot: args.repoRoot,
826
872
  appended: args.appended,
@@ -841,7 +887,7 @@ async function runPrune(args) {
841
887
  process.stdout.write(
842
888
  `sweep-expired-learnings: prune scanned=${summary.scanned} kept=${summary.kept} ` +
843
889
  `archived=${summary.archived} by_reason=${byReason} dry_run=${summary.dryRun} ` +
844
- `archive=${summary.archivePath}\n`
890
+ `archive=${summary.archivePath}${summary.skipped ? ` skipped=${summary.skipped}` : ''}\n`
845
891
  );
846
892
  }
847
893
  }
@@ -858,7 +904,7 @@ async function runPrune(args) {
858
904
  async function emitMalformedDropped(args, result) {
859
905
  // Both sides canonical, as the parseArgs check judged them: a lexical
860
906
  // relative() across /var vs /private/var reads `../../private/...`.
861
- const root = resolveLedger(args.repoRoot);
907
+ const root = resolveLedger(ledgerRootOf(args.file, args.repoRoot) ?? args.repoRoot);
862
908
  const rel = (p) => path.relative(root, resolveLedger(p));
863
909
  try {
864
910
  const { emitEvent, sessionAttribution } = await import('./lib/events.mjs');
@@ -916,7 +962,9 @@ async function runDropMalformed(args) {
916
962
  process.exit(2);
917
963
  }
918
964
 
919
- if (!args.dryRun) await emitMalformedDropped(args, result);
965
+ // An inbox-role host (#1516) returns the dry-run probe with `skipped` set:
966
+ // nothing was dropped, so no event may claim it was.
967
+ if (!args.dryRun && !result.skipped) await emitMalformedDropped(args, result);
920
968
 
921
969
  const summary = {
922
970
  file: args.file,
@@ -105,6 +105,7 @@ export function parseArgs(argv) {
105
105
  } else if (a === '--learnings') {
106
106
  const raw = argv[++i];
107
107
  if (typeof raw !== 'string' || raw.length === 0) usageError('--learnings requires a path');
108
+ if (path.isAbsolute(raw)) usageError('--learnings requires a repo-relative path');
108
109
  args.learnings = raw;
109
110
  } else if (a === '--help' || a === '-h') {
110
111
  printHelp();
@@ -140,6 +141,7 @@ export function summarize(plan, args, applied) {
140
141
  expired_entries: plan.plans.reduce((n, p) => n + p.expiredPairIds.length, 0),
141
142
  unresolved_pairs: plan.plans.reduce((n, p) => n + p.unresolvedPairIds.length, 0),
142
143
  malformed_lines: plan.malformedLines,
144
+ archive_malformed_lines: plan.archiveMalformedLines,
143
145
  rules_dir_readable: plan.ok,
144
146
  skipped: plan.skipped,
145
147
  plans: plan.plans.map(({ nextContent: _nextContent, ...rest }) => rest),
@@ -178,7 +180,8 @@ async function main(argv) {
178
180
  `(${summary.header_raises} header-raise(s)), ` +
179
181
  `${summary.deletes} delete(s), ${summary.expired_entries} expired entr(ies), ` +
180
182
  `${summary.unresolved_pairs} unresolved pair(s), ${summary.skipped.length} skipped, ` +
181
- `${summary.malformed_lines} malformed learnings line(s)\n`,
183
+ `${summary.malformed_lines} malformed learnings line(s), ` +
184
+ `${summary.archive_malformed_lines} malformed archive line(s)\n`,
182
185
  );
183
186
  for (const s of plan.skipped) process.stdout.write(` skipped ${s.file}: ${s.reason}\n`);
184
187
  for (const p of plan.plans) {
@@ -481,6 +481,11 @@ runCheck('check-learning-provenance.mjs');
481
481
  process.stdout.write('\n');
482
482
  runCheck('check-vcs-repo-flag.mjs');
483
483
 
484
+ // Separate advice contract (#1024): name the PR or drop a rejected repo flag.
485
+ // WARN-only, with tool errors surfaced through FAIL lines as in the bare census.
486
+ process.stdout.write('\n');
487
+ runCheck('check-vcs-repo-flag-positional.mjs');
488
+
484
489
  // WARN-only (#1023): a `gh`/`glab` command cited in docs that no released CLI
485
490
  // ever had — `glab repo edit --visibility`, `glab group list` — costs an
486
491
  // operator a failed command and a re-derivation. The oracle is the CLI's own
@@ -539,6 +544,13 @@ if (runCheck('check-learnings-shell-writes.mjs') !== 0) checkFailed = 1;
539
544
  process.stdout.write('\n');
540
545
  runCheck('check-test-git-config-target.mjs');
541
546
 
547
+ // BLOCKING (#1613): a test that spawns `sh`/`bash` by bare name is red on native
548
+ // Windows (no sh on PATH, bash is WSL) — 301 red tests at fd8d3673. It may block
549
+ // because the backlog was drained in the same change: the live census leaves only
550
+ // three files in open MRs, baselined inside the checker as WARN.
551
+ process.stdout.write('\n');
552
+ if (runCheck('check-test-posix-shell.mjs') !== 0) checkFailed = 1;
553
+
542
554
  // BLOCKING (#1183): every `emitEvent()` call site under hooks/**/*.mjs must be
543
555
  // try/catch-guarded (emitEvent() throws EventValidationError on a malformed
544
556
  // record — an unguarded call aborts the hook process). Five pre-existing
@@ -548,6 +560,16 @@ runCheck('check-test-git-config-target.mjs');
548
560
  process.stdout.write('\n');
549
561
  if (runCheck('check-hooks-emit-event-guard.mjs') !== 0) checkFailed = 1;
550
562
 
563
+ process.stdout.write('\n'); if (runCheck('check-workflow-continue-on-error.mjs') !== 0) checkFailed = 1;
564
+
565
+ // BLOCKING (#1722): a module under scripts/ or hooks/ that builds a
566
+ // `.orchestrator/metrics` path must route it through resolveLedgerPath()
567
+ // (events/sessions: resolveEventsLedgerRoot()). The ledgers are gitignored, so
568
+ // from a linked worktree an unrouted writer leaves a copy that is lost with the
569
+ // worktree. Deliberately local sites sit on the checker's own ALLOWLIST.
570
+ process.stdout.write('\n');
571
+ if (runCheck('check-metrics-ledger-root.mjs') !== 0) checkFailed = 1;
572
+
551
573
  // BLOCKING (#1184): every scripts/lib/validate/check-*.mjs must be referenced
552
574
  // by basename from this file, .husky/pre-commit, or .gitlab-ci.yml — or carry
553
575
  // a `// registration: standalone <reason>` header marker declaring itself