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
@@ -49,13 +49,24 @@ Entered when `$ARGUMENTS` contains `--retroactive`. Writes the lock file and, pe
49
49
  plugin-version: <current plugin version from $PLUGIN_ROOT/package.json>
50
50
  ```
51
51
 
52
- 6. **Patch Session Config (#182).** Run the validator against the current `## Session Config` block; append any missing mandatory fields with defaults. The 7 mandatory fields (per `scripts/lib/config-schema.mjs`) are: `test-command`, `typecheck-command`, `lint-command`, `agents-per-wave`, `waves`, `persistence`, `enforcement`.
52
+ 6. **Patch Session Config (#182, #1567).** Find the mandatory fields the `## Session Config` block does not carry and append them with defaults. The mandatory fields are the keys of the FIRST `## Session Config` block in `$PLUGIN_ROOT/docs/session-config-template.md` (today the 7 validated by `scripts/lib/config-schema.mjs`): `test-command`, `typecheck-command`, `lint-command`, `agents-per-wave`, `waves`, `persistence`, `enforcement`.
53
+
54
+ Presence is read from the FILE, not from the validator: `parseSessionConfig()` fills every mandatory field with its default, so `parse-config.mjs` / `validate-config.mjs` never report an ABSENT field (a pnpm repo without `test-command` would silently run `npm test`). `missingStarterKeys()` (`scripts/lib/config/template-keys.mjs`) compares the template's starter keys against `collectTopLevelKeyLines()` — the top-level keys the runtime reads, in every accepted spelling (`key:`, `- key:`, `- **key:**`).
53
55
 
54
56
  ```bash
55
- CONFIG_OUT="$(node "$PLUGIN_ROOT/scripts/parse-config.mjs" 2>&1 >/dev/null)"
56
- # parse-config.mjs emits validation warnings to stderr when enforcement=warn.
57
- # Grep for 'must be' lines (issued by validate-config.mjs) to detect missing fields.
58
- MISSING_FIELDS="$(echo "$CONFIG_OUT" | grep -oE '(test-command|typecheck-command|lint-command|agents-per-wave|waves|persistence|enforcement)' | sort -u || true)"
57
+ CONFIG_FILE="CLAUDE.md"
58
+ [[ -f "AGENTS.md" ]] && CONFIG_FILE="AGENTS.md"
59
+
60
+ MISSING_FIELDS="$(node --input-type=module -e "
61
+ import { readFileSync } from 'node:fs';
62
+ import { missingStarterKeys } from '$PLUGIN_ROOT/scripts/lib/config/template-keys.mjs';
63
+ const missing = missingStarterKeys(
64
+ readFileSync(process.argv[1], 'utf8'),
65
+ readFileSync('$PLUGIN_ROOT/docs/session-config-template.md', 'utf8'),
66
+ );
67
+ if (missing === null) { console.error('template has no Session Config block — mandatory-field check did not run'); process.exit(1); }
68
+ console.log(missing.join(' '));
69
+ " "$CONFIG_FILE")"
59
70
  if [[ -n "$MISSING_FIELDS" ]]; then
60
71
  # Detect package manager to pick sensible defaults for commands.
61
72
  PM_DEFAULTS="$(node --input-type=module -e "
@@ -67,9 +78,6 @@ Entered when `$ARGUMENTS` contains `--retroactive`. Writes the lock file and, pe
67
78
  console.log('lint-command: ' + cmds.lint.command);
68
79
  " 2>/dev/null)"
69
80
 
70
- CONFIG_FILE="CLAUDE.md"
71
- [[ -f "AGENTS.md" ]] && CONFIG_FILE="AGENTS.md"
72
-
73
81
  # Append each missing field under the ## Session Config block.
74
82
  for field in $MISSING_FIELDS; do
75
83
  case "$field" in
@@ -89,7 +97,7 @@ Entered when `$ARGUMENTS` contains `--retroactive`. Writes the lock file and, pe
89
97
  fi
90
98
  ```
91
99
 
92
- This patch is best-effort: existing fields are never overwritten. If no fields are missing, this step is a no-op.
100
+ This patch is best-effort: existing fields are never overwritten, and an INVALID value of a present field is left for `parse-config.mjs` to report. If no fields are missing, this step is a no-op; if the check did not run (exit 1), report that line instead of `No config changes.`.
93
101
 
94
102
  7. **Commit.** Stage the lock file (and the patched config file, if it changed) and commit:
95
103
  ```bash
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: project-code-review
3
- description: Use this agent when you need to review code changes for convention compliance, security issues, or correctness before merging. <example>Context: A wave of implementation work has completed and the coordinator needs a review pass before committing. user: "Review the auth module changes" assistant: "I'll use the project-code-review agent to check convention compliance and surface any security concerns." <commentary>Convention enforcement and security review are the project-code-review agent's responsibilities.</commentary></example>
3
+ description: Use this agent when you need to review code changes for convention compliance, security issues, or correctness before merging.
4
4
  model: inherit
5
5
  color: green
6
6
  tools: Read, Grep, Glob
@@ -16,3 +16,7 @@ Performs read-only review of code changes. Use this agent to:
16
16
  - Verify adherence to naming conventions and file structure
17
17
 
18
18
  Edit this file to add project-specific review checklists, e.g. banned patterns, required linting rules, security invariants, or architecture constraints.
19
+
20
+ ## Examples
21
+
22
+ <example>Context: A wave of implementation work has completed and the coordinator needs a review pass before committing. user: "Review the auth module changes" assistant: "I'll use the project-code-review agent to check convention compliance and surface any security concerns." <commentary>Convention enforcement and security review are the project-code-review agent's responsibilities.</commentary></example>
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: project-discovery
3
- description: Use this agent when you need to audit project state, map affected modules, or verify assumptions before implementation. <example>Context: Before adding a new feature, the coordinator needs to understand existing code paths. user: "Audit the auth flow" assistant: "I'll use the project-discovery agent to map auth modules and identify dependencies." <commentary>Read-only audit work is the project-discovery agent's sweet spot.</commentary></example>
3
+ description: Use this agent when you need to audit project state, map affected modules, or verify assumptions before implementation.
4
4
  model: inherit
5
5
  color: cyan
6
6
  tools: Read, Grep, Glob, Bash
@@ -16,3 +16,7 @@ Performs read-only audits of the project codebase. Use this agent to:
16
16
  - Surface potential conflicts or constraints
17
17
 
18
18
  Edit this file to add project-specific discovery queries, e.g. common file paths, known patterns to grep for, or project-specific terminology to watch for.
19
+
20
+ ## Examples
21
+
22
+ <example>Context: Before adding a new feature, the coordinator needs to understand existing code paths. user: "Audit the auth flow" assistant: "I'll use the project-discovery agent to map auth modules and identify dependencies." <commentary>Read-only audit work is the project-discovery agent's sweet spot.</commentary></example>
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: project-quality-gate
3
- description: Use this agent when you need to verify a repo meets quality standards before a release, merge, or deployment decision. <example>Context: The session is about to close and the coordinator wants a final quality check. user: "Run the quality gate checks" assistant: "I'll use the project-quality-gate agent to verify tests pass, lint is clean, and coverage thresholds are met." <commentary>Gate decisions — pass/fail with evidence — are the project-quality-gate agent's core output.</commentary></example>
3
+ description: Use this agent when you need to verify a repo meets quality standards before a release, merge, or deployment decision.
4
4
  model: inherit
5
5
  color: blue
6
6
  tools: Read, Grep, Glob, Bash
@@ -16,3 +16,7 @@ Evaluates whether the repo passes project-defined quality criteria. Use this age
16
16
  - Confirm no blocked patterns or known vulnerabilities are introduced
17
17
 
18
18
  Edit this file to add project-specific gate criteria, e.g. minimum test coverage percentage, required CI checks, or deployment pre-conditions.
19
+
20
+ ## Examples
21
+
22
+ <example>Context: The session is about to close and the coordinator wants a final quality check. user: "Run the quality gate checks" assistant: "I'll use the project-quality-gate agent to verify tests pass, lint is clean, and coverage thresholds are met." <commentary>Gate decisions — pass/fail with evidence — are the project-quality-gate agent's core output.</commentary></example>
@@ -42,14 +42,14 @@ PHASE 1 IMPLEMENTED (2026-04-19). Session-end opt-in quality gate. Upstream of `
42
42
  | 6 | `session-config-parity` | Top-level keys under `## Session Config` in `CLAUDE.md` / `AGENTS.md` | diff against `docs/session-config-template.md`; a missing MANDATORY (minimal-baseline) key is an error, a missing OPT-IN-baseline-only key is a note, a local key absent from the template's Session Config blocks is a warning |
43
43
  | 7 | `vault-dir-parity` | `vault-integration.vault-dir` in BOTH `CLAUDE.md` AND `AGENTS.md` | reuse `_parseVaultIntegration`; flag when the two files disagree. Short-circuits to PASS when the two are the SAME file by construction (symlink / same inode / byte-identical generated copy) |
44
44
  | 8 | `generated-rule-staleness` *(WARN only)* | `.claude/rules/*.md` with `auto-generated: true` frontmatter | extract `learning-key`; WARN when the key is absent from `.orchestrator/metrics/learnings.jsonl` or its learning's `expires_at` is in the past; skipped silently when no auto-generated rules exist |
45
- | 9 | `rule-scoping` | `.claude/rules/*.md` frontmatter + `## See Also` footers + `.claude/rules/<name>.md` citations in `CLAUDE.md`/`AGENTS.md` | six probes: `paths:` frontmatter incl. frontmatter not at line 1 (error, #1449), cited-but-missing rule citations (error), zero-match `globs:` patterns (warn), foreign PascalCase glob tokens (warn), unreadable rule files (warn — surfaced instead of silently skipped), declared fleet-intent globs (**note**, never warn); skipped silently when `.claude/rules/` is absent |
45
+ | 9 | `rule-scoping` | `.claude/rules/*.md` frontmatter + `## See Also` footers + `.claude/rules/<name>.md` citations in `CLAUDE.md`/`AGENTS.md` | seven probes: `paths:` frontmatter incl. frontmatter not at line 1 (error, #1449), cited-but-missing rule citations (error), zero-match `globs:` patterns (warn), foreign PascalCase glob tokens (warn), unreadable rule files (warn — surfaced instead of silently skipped), declared fleet-intent globs (**note**, never warn), `globs:` without `paths:` (warn, #1638 — Claude Code loads it always-on); skipped silently when `.claude/rules/` is absent |
46
46
  | 10 | `docs-parity` | `docs/components.md` count-claims, Session Config key parity (`docs/session-config-template.md` vs `docs/session-config-reference.md`), `.claude/metrics/` stale-path references in `docs/*.md` / `docs/examples/*.md` | three sub-checks (a/b/c) — see below; skipped silently when `docs/components.md` is absent |
47
47
 
48
48
  Check 3 deliberately scopes to forward-looking sections. Mentions inside "Recently Closed", "Decisions", "Archive", etc. describe history and must not be flagged.
49
49
 
50
50
  Check 5 uses `countSlashCommands()` to count the union of non-hidden `*.md` files directly inside `commands/` and skills under `skills/` whose frontmatter explicitly declares `user-invocable: true`, deduplicated by name. The `commands/` directory is resolved relative to `VAULT_DIR` by default; `--commands-dir <path>` overrides that side of the union, while skills remain relative to `VAULT_DIR`. Either directory alone is sufficient; an existing directory with no commands contributes zero rather than causing a skip.
51
51
 
52
- Check 6 (issue #30; severity split per #785 and #1356) extracts the YAML block under `## Session Config` from both the canonical template (`docs/session-config-template.md` by default, override with `--config-template`) and the resolved local instruction file — reading the template TWICE, once per occurrence: `{ occurrence: 'first' }` reaches the "Full minimal baseline" (the 7 schema-mandatory keys), `{ occurrence: 'last' }` reaches the "Full opt-in baseline" (the full key catalog, a strict superset of the minimal block). A template key missing from the local file is a `session-config-parity` **error** when it belongs to the minimal-baseline set, and a **note** with `probe: "opt-in-gap"` when it is opt-in-baseline-only. A local key absent from those template blocks produces a **warning**: it may be a typo or a template gap, and this check makes no claim about documentation elsewhere or runtime use. When the template carries a single `## Session Config` heading (no separate opt-in block), `'first'` and `'last'` resolve to the same block, every template key is treated as mandatory, and unknown-local-key warnings are skipped. Both fenced YAML (```` ```yaml ... ``` ````) and raw YAML body (up to next `## ` heading) are accepted. The check skips gracefully when the template file is absent, when no instruction file is detected, or when explicitly disabled via `--skip-session-config-parity`.
52
+ Check 6 (issue #30; severity split per #785 and #1356) extracts the YAML block under `## Session Config` from both the canonical template (`docs/session-config-template.md` by default, override with `--config-template`) and the resolved local instruction file — reading the template TWICE, once per occurrence: `{ occurrence: 'first' }` reaches the "Starter config" at the top of the template (the 7 schema-mandatory keys), `{ occurrence: 'last' }` reaches the "Full opt-in baseline" (the full key catalog, a strict superset of the minimal block). A template key missing from the local file is a `session-config-parity` **error** when it belongs to the minimal-baseline set, and a **note** with `probe: "opt-in-gap"` when it is opt-in-baseline-only. A local key absent from those template blocks produces a **warning**: it may be a typo or a template gap, and this check makes no claim about documentation elsewhere or runtime use. When the template carries a single `## Session Config` heading (no separate opt-in block), `'first'` and `'last'` resolve to the same block, every template key is treated as mandatory, and unknown-local-key warnings are skipped. Both fenced YAML (```` ```yaml ... ``` ````) and raw YAML body (up to next `## ` heading) are accepted. The check skips gracefully when the template file is absent, when no instruction file is detected, or when explicitly disabled via `--skip-session-config-parity`.
53
53
 
54
54
  The parity set is **template-driven**: every column-0 YAML key under `## Session Config` in the template (across both occurrences) is checked. As of the gsd Pattern Adoption Quick-Wins bundle (PRD 2026-05-22, issues #517–#521), the template-side keys include four opt-in top-level blocks:
55
55
 
@@ -68,7 +68,7 @@ Check 7 (issue #600) is the **only** check that intentionally reads BOTH instruc
68
68
 
69
69
  The same predicate guards Check 9's probe 2a, which iterates `['CLAUDE.md', 'AGENTS.md']`: with an aliased `AGENTS.md` it scans `CLAUDE.md` only, because otherwise every dangling `.claude/rules/*.md` citation would be reported TWICE — one defect, two errors, and in `--mode strict` a doubled blocking surface against a file the operator is not allowed to edit.
70
70
 
71
- Check 9 (`rule-scoping`) validates `.claude/rules/*.md` frontmatter against the `scripts/lib/rule-loader.mjs` contract, catching the class of defect where a rule silently drifts out of the activation pipeline the loader actually implements. Six probes: **(1) paths-presence** — a frontmatter block that does not open on line 1 (text or a provenance comment before `---`) is an ERROR since #1449: Claude Code ignores it and loads the rule always-on; the probe reports `parseGlobsFrontmatter(...).defect === 'frontmatter-not-at-top'` with the remedy (move the block to line 1, provenance comment after the closing `---`). Otherwise, since #795 `paths:` is a full same-shape alias for `globs:` in `rule-loader.mjs` (`globs:` wins silently when both are present), so a well-formed `paths:`-only rule loads correctly SCOPED and is NOT flagged (#840). The probe fires only when the frontmatter textually carries a top-level `paths:` key AND `parseGlobsFrontmatter` — the loader's own parser, imported rather than re-derived — returns `globs === null` (e.g. a malformed frontmatter line makes it throw); that parse mismatch means the rule may silently load ALWAYS-ON, and is flagged as an error. **(2) cited-but-missing** — `(a)` `.claude/rules/<name>.md` citations inside `CLAUDE.md`/`AGENTS.md` that don't resolve to a file on disk, and `(b)` bare `<name>.md` tokens in a rule's own `## See Also` footer that don't exist as sibling rule files (tokens carrying a path separator, e.g. `../../skills/_shared/state-ownership.md`, are cross-directory references and explicitly out of scope); both flagged as errors. **(3) zero-match-globs** — a `globs:` pattern matching zero files in `git ls-files` (falls back to a manual directory walk when git is unavailable); flagged as a WARNING, not an error, because library/exemplar repos legitimately carry dead stack rules. **(4) foreign-glob** — a glob pattern containing a PascalCase product-like token (regex `[A-Z][a-z]+[A-Z]`, e.g. `WalkAITalkieTests`) — a likely copy-paste leftover from another project's rule scope; flagged as a WARNING. **(5) unreadable-file** — a rule file that could not be read (permissions, a race with a concurrent delete); flagged as a WARNING rather than silently skipped, so a completeness audit does not drop files, and so an unreadable file cannot brick the gate under `mode: hard`. **(6) fleet-intent-glob** — a `globs:` pattern DECLARED as fleet intent goes to `notes[]` (see below), never to `warnings[]`, and the declaration also settles probe 4 for that same pattern: a pattern the rule author declared is by construction not a copy-paste leftover. Glob matching reuses the same picomatch-with-inline-fallback resolution `scripts/lib/rule-loader.mjs` uses (`parseGlobsFrontmatter` is imported directly; the picomatch resolution itself is duplicated locally since `rule-loader.mjs` does not export a public matcher function). The check is skipped silently (no `checks_run` entry, no `checks_skipped` entry) when `.claude/rules/` is absent, or explicitly via `--skip-rule-scoping`.
71
+ Check 9 (`rule-scoping`) validates `.claude/rules/*.md` frontmatter against the `scripts/lib/rule-loader.mjs` contract, catching the class of defect where a rule silently drifts out of the activation pipeline the loader actually implements. Seven probes: **(1) paths-presence** — a frontmatter block that does not open on line 1 (text or a provenance comment before `---`) is an ERROR since #1449: Claude Code ignores it and loads the rule always-on; the probe reports `parseGlobsFrontmatter(...).defect === 'frontmatter-not-at-top'` with the remedy (move the block to line 1, provenance comment after the closing `---`). Otherwise, since #795 `paths:` is a full same-shape alias for `globs:` in `rule-loader.mjs` (`globs:` wins silently when both are present), so a well-formed `paths:`-only rule loads correctly SCOPED and is NOT flagged (#840). The probe fires only when the frontmatter textually carries a top-level `paths:` key AND `parseGlobsFrontmatter` — the loader's own parser, imported rather than re-derived — returns `globs === null` (e.g. a malformed frontmatter line makes it throw); that parse mismatch means the rule may silently load ALWAYS-ON, and is flagged as an error. **(2) cited-but-missing** — `(a)` `.claude/rules/<name>.md` citations inside `CLAUDE.md`/`AGENTS.md` that don't resolve to a file on disk, and `(b)` bare `<name>.md` tokens in a rule's own `## See Also` footer that don't exist as sibling rule files (tokens carrying a path separator, e.g. `../../skills/_shared/state-ownership.md`, are cross-directory references and explicitly out of scope); both flagged as errors. **(3) zero-match-globs** — a `globs:` pattern matching zero files in `git ls-files` (falls back to a manual directory walk when git is unavailable); flagged as a WARNING, not an error, because library/exemplar repos legitimately carry dead stack rules. **(4) foreign-glob** — a glob pattern containing a PascalCase product-like token (regex `[A-Z][a-z]+[A-Z]`, e.g. `WalkAITalkieTests`) — a likely copy-paste leftover from another project's rule scope; flagged as a WARNING. **(5) unreadable-file** — a rule file that could not be read (permissions, a race with a concurrent delete); flagged as a WARNING rather than silently skipped, so a completeness audit does not drop files, and so an unreadable file cannot brick the gate under `mode: hard`. **(6) fleet-intent-glob** — a `globs:` pattern DECLARED as fleet intent goes to `notes[]` (see below), never to `warnings[]`, and the declaration also settles probe 4 for that same pattern: a pattern the rule author declared is by construction not a copy-paste leftover. **(7) globs-without-paths** (#1638) — a line-1 frontmatter with a `globs:` key and no `paths:` key: Claude Code reads only `paths:` and ignores `globs:` without an error, so the rule loads ALWAYS-ON there although `rule-loader.mjs` scopes it for wave injection. Flagged as a WARNING with `probe: "globs-without-paths"`, the line of the `globs:` key and the remedy (add `paths:` with the same patterns); never an error, also not under `--mode strict`. Keyed on the parser's raw `paths` field, so body text and a displaced block (probe 1) never trigger it. Glob matching reuses the same picomatch-with-inline-fallback resolution `scripts/lib/rule-loader.mjs` uses (`parseGlobsFrontmatter` is imported directly; the picomatch resolution itself is duplicated locally since `rule-loader.mjs` does not export a public matcher function). The check is skipped silently (no `checks_run` entry, no `checks_skipped` entry) when `.claude/rules/` is absent, or explicitly via `--skip-rule-scoping`.
72
72
 
73
73
  **Fleet-intent declaration (Check 9, probe 6).** A rule may declare that a `globs:` pattern is aimed at CONSUMER repos rather than this one, via its own frontmatter key `fleet-intent-globs:`. Accepted forms are block style (`fleet-intent-globs:` plus indented ` - "<pattern>"` lines), flow style (`fleet-intent-globs: ["a", "b"]`) and a single inline value; surrounding quotes are stripped, blank and `#` comment lines are ignored. The key is inert for rule loading — `rule-loader.mjs`'s `parseGlobsFrontmatter` skips unknown top-level keys and their continuation lines. A declared pattern is reported in `notes[]` with its file, the pattern, whether it matches `≥1` or `0` tracked files here, and the declaration `source` (`frontmatter`). The grain is per pattern AND per rule file: declaring one glob amnesties neither a sibling glob in the same rule nor the same pattern in another rule. There is deliberately NO checker-side exemption list: a rule that does not declare the pattern itself is not exempt, so the declaration always sits with the rule it describes.
74
74
 
@@ -38,11 +38,13 @@ import { resolveInstructionFile } from '../../scripts/lib/common.mjs';
38
38
  import { _parseVaultIntegration } from '../../scripts/lib/config/vault-integration.mjs';
39
39
  import { _parseDriftCheck } from '../../scripts/lib/config/drift-check.mjs';
40
40
  import { isSessionConfigHeading } from '../../scripts/lib/config/section-extractor.mjs';
41
+ import { extractSessionConfigBlock, extractTopLevelKeys } from '../../scripts/lib/config/template-keys.mjs';
41
42
  import { FRONTMATTER_NOT_AT_TOP, isRuleExpired, parseGlobsFrontmatter } from '../../scripts/lib/rule-loader.mjs';
42
43
  import { resolveRepoSpec } from '../../scripts/lib/vcs-repo-spec.mjs';
43
44
  import { userInvocableSkills } from '../../scripts/lib/user-invocable-skills.mjs';
44
45
  import { loadOwnerConfig } from '../../scripts/lib/owner-yaml.mjs';
45
46
  import { learningKeyOf } from '../../scripts/lib/learnings/kebab.mjs';
47
+ import { resolveLedgerPath } from '../../scripts/lib/ledger-path.mjs';
46
48
 
47
49
  const FORWARD_HEADING_RE =
48
50
  /(?:^|\b)(what'?s?\s+next|backlog|open\s+issues?|offene\s+(?:issues?|themen)|todo|next\s+steps?|roadmap)(?:$|\b)/i;
@@ -133,88 +135,6 @@ function walkDir(dir, visit) {
133
135
  }
134
136
  }
135
137
 
136
- /**
137
- * Extract the YAML block under a `## Session Config` heading from a Markdown
138
- * document. Tries fenced YAML first (```yaml ... ```) *within the matched
139
- * heading's own body slice*, then falls back to the raw body up to the next
140
- * `^## ` heading or EOF.
141
- *
142
- * `docs/session-config-template.md` carries TWO literal `## Session Config`
143
- * lines — both live INSIDE fenced ```yaml example blocks: the "Full minimal
144
- * baseline" (7 mandatory keys) and the "Full opt-in baseline" (the complete
145
- * key catalog, a strict superset of the minimal block). By default this
146
- * function matches the FIRST occurrence (correct for a local CLAUDE.md /
147
- * AGENTS.md instruction file, which has exactly one real heading). Pass
148
- * `{ occurrence: 'last' }` to match the LAST occurrence instead — the template
149
- * call site uses this to reach the opt-in baseline's full key set rather than
150
- * silently stopping at the 7-key minimal block (issue #785).
151
- *
152
- * Returns { body: string, headingLine: number } or null when no heading found.
153
- *
154
- * @param {string} content
155
- * @param {{ occurrence?: 'first'|'last' }} [options]
156
- */
157
- function extractSessionConfigBlock(content, { occurrence = 'first' } = {}) {
158
- const lines = content.split('\n');
159
- const headingIdxs = [];
160
- for (let i = 0; i < lines.length; i++) {
161
- // SSOT predicate (#968). The previous local `/^##\s+Session Config\b/`
162
- // was the loosest JS comparator in the repo: no end anchor and `\s+`
163
- // instead of a single space, so it accepted `## Session Config` (two
164
- // spaces) and `## Session Config Convention` — both of which the runtime
165
- // parser rejects. This checker's whole job is to report on the block the
166
- // runtime reads, so matching a heading the runtime cannot see made it
167
- // audit a section that does not exist.
168
- if (isSessionConfigHeading(lines[i])) headingIdxs.push(i);
169
- }
170
- if (headingIdxs.length === 0) return null;
171
-
172
- const targetIdx = occurrence === 'last' ? headingIdxs[headingIdxs.length - 1] : headingIdxs[0];
173
- const headingLine = targetIdx + 1; // 1-based line number for error reporting
174
-
175
- // Raw body: from the line after the heading up to the next `## ` heading or EOF.
176
- let endIdx = lines.length;
177
- for (let i = targetIdx + 1; i < lines.length; i++) {
178
- if (/^##\s+/.test(lines[i])) { endIdx = i; break; }
179
- }
180
- const rawBody = lines.slice(targetIdx + 1, endIdx).join('\n');
181
-
182
- // Prefer a fenced YAML block found anywhere within the raw body slice
183
- // (tolerates a blank line between the heading and the fence); fall back to
184
- // the raw body itself when no fence is present.
185
- const fenced = rawBody.match(/```ya?ml\r?\n([\s\S]*?)\r?\n```/);
186
- const body = fenced ? fenced[1] : rawBody;
187
-
188
- return { body, headingLine };
189
- }
190
-
191
- /**
192
- * Extract top-level YAML keys from a YAML body. Only column-0 keys are
193
- * collected (indented keys are children and ignored).
194
- *
195
- * Accepts four equivalent forms consumer CLAUDE.md files write a Session
196
- * Config key in: bare `key:`, Markdown bullet `- key:`, bold `**key:**`
197
- * (closing bold before the colon), and the bold-bullet consumer shape
198
- * `- **key:** value` (bold wraps the key AND colon together, closing bold
199
- * AFTER the colon). Baseline issue #60: the original bare-only regex
200
- * extracted zero keys from bullet-form local files, so session-config-parity
201
- * (Check 6, below) either false-positived on every mandatory key (local side
202
- * reads as empty) or, when the TEMPLATE side also used bullet form, passed
203
- * vacuously (both sides empty, so the diff was always empty). Sibling
204
- * precedent: `scripts/lib/config/block-header.mjs` recognizes only header
205
- * lines, not individual key-value pairs — this function is the per-key
206
- * counterpart.
207
- */
208
- function extractTopLevelKeys(body) {
209
- const keys = [];
210
- const re = /^(?:-\s+)?(?:\*\*)?([A-Za-z][\w-]*)(?:\*\*)?:/gm;
211
- let m;
212
- while ((m = re.exec(body)) !== null) {
213
- keys.push(m[1]);
214
- }
215
- return keys;
216
- }
217
-
218
138
  /**
219
139
  * Extract Session-Config-key-shaped mentions from a "reference" style doc
220
140
  * (docs/session-config-reference.md for Check 10b). Unlike the template,
@@ -632,7 +552,8 @@ function lookupIssueState(iid, repo, cache, vaultDir) {
632
552
  // legitimately carry dead stack rules.
633
553
  // 4. foreign-glob → warnings[]: a glob pattern containing a PascalCase
634
554
  // product-like token (e.g. `WalkAITalkieTests`) — a likely copy-paste
635
- // leftover from another project's rule.
555
+ // leftover from another project's rule. Skipped when the glob matches a
556
+ // `git ls-files` entry of the checked repo — its own directory (#1657).
636
557
  // 5. unreadable-file → warnings[]: a rule file that could not be read
637
558
  // (permissions, race with a concurrent delete, etc.) — surfaced instead
638
559
  // of being silently skipped, since a completeness audit that silently
@@ -642,16 +563,22 @@ function lookupIssueState(iid, repo, cache, vaultDir) {
642
563
  // rule's own `fleet-intent-globs:` frontmatter key — reported with its
643
564
  // local match count, never warned, and it settles probes 3+4 for that
644
565
  // pattern. See FLEET_INTENT_FM_KEY below.
566
+ // 7. globs-without-paths → warnings[]: a line-1 frontmatter with a `globs:`
567
+ // key and no `paths:` key (#1638). Claude Code reads `paths:` only and
568
+ // ignores `globs:` without an error, so the rule loads ALWAYS-ON there
569
+ // even though rule-loader.mjs scopes it for wave injection. WARN, not
570
+ // error, also under strict — the fix is a judgement (which patterns),
571
+ // and the repo's own hard gate is check-rules.mjs, not this check.
645
572
  //
646
573
  // This list is numeric, not a call sequence — the code below does NOT run
647
- // 1→2→3→4→5→6 in order. Per rule file: probe 5's read-failure guard runs
574
+ // 1→2→3→4→5→6→7 in order. Per rule file: probe 5's read-failure guard runs
648
575
  // FIRST (it gates every other probe via `continue` on an unreadable file,
649
- // before probe 1 ever sees the content), then probe 1, then probes 3/4 with
576
+ // before probe 1 ever sees the content), then probe 1, then probe 7, then probes 3/4 with
650
577
  // probe 6 interleaved INSIDE the same per-pattern loop (it must intercept a
651
578
  // declared fleet-intent pattern before 3/4 evaluate it), then probe 2b: once
652
579
  // every rule file has been visited, probe 2a runs last over CLAUDE.md/
653
580
  // AGENTS.md. `grep -n "Probe [0-9]" checker.mjs` therefore surfaces markers
654
- // in code order (5, 1, 6, 2b, 2a), not numeric order — that is intentional,
581
+ // in code order (5, 1, 7, 6, 2b, 2a), not numeric order — that is intentional,
655
582
  // not drift; do not "fix" it by moving probe 6's marker next to probe 5's.
656
583
  // ───────────────────────────────────────────────────────────────────────────
657
584
 
@@ -718,6 +645,10 @@ function globMatchesAny(pattern, trackedFiles) {
718
645
  * 64 MiB buffer exists because the 1 MiB default threw ENOBUFS on a vault
719
646
  * listing of 1,199,500 bytes (measured 2026-10-04), which the old bare catch
720
647
  * turned into exactly those false warnings, silently.
648
+ *
649
+ * Returns `{ files, fromGit }`: `fromGit` is false when the listing came from
650
+ * the walk fallback. The foreign-glob probe only trusts a git listing to
651
+ * exempt a glob (#1657) — without it, it keeps warning.
721
652
  */
722
653
  function listTrackedFiles(vaultDir) {
723
654
  try {
@@ -727,7 +658,7 @@ function listTrackedFiles(vaultDir) {
727
658
  stdio: ['ignore', 'pipe', 'pipe'],
728
659
  maxBuffer: 64 * 1024 * 1024,
729
660
  });
730
- return out.split('\n').filter(Boolean);
661
+ return { files: out.split('\n').filter(Boolean), fromGit: true };
731
662
  } catch (err) {
732
663
  const notARepo = err?.status === 128 && /not a git repository/i.test(String(err?.stderr ?? ''));
733
664
  if (err?.code !== 'ENOENT' && !notARepo) {
@@ -737,7 +668,7 @@ function listTrackedFiles(vaultDir) {
737
668
  }
738
669
  const files = [];
739
670
  walkDir(vaultDir, (f) => files.push(relative(vaultDir, f).replace(/\\/g, '/')));
740
- return files;
671
+ return { files, fromGit: false };
741
672
  }
742
673
  }
743
674
 
@@ -1067,13 +998,13 @@ function main() {
1067
998
  // and was wrong about two of them:
1068
999
  // awk '/^## /{sec=$0} /^auto-skill-dispatch:|^issue-budget:/{print NR,sec}' \
1069
1000
  // docs/session-config-template.md
1070
- // → auto-skill-dispatch line 87 (## Auto-Skill Dispatch)
1071
- // → issue-budget line 123 (## Issue Budget)
1001
+ // → auto-skill-dispatch under ## Auto-Skill Dispatch
1002
+ // → issue-budget under ## Issue Budget
1072
1003
  // rg -c "'auto-skill-dispatch'" scripts/ → 4 files, non-zero
1073
1004
  // rg -c "'issue-budget'" scripts/ → non-zero
1074
1005
  // Both are documented in the template FILE and both are read at runtime;
1075
- // only their absence from the two `## Session Config` blocks (lines 658/676)
1076
- // was ever true. The message below now asserts only that.
1006
+ // only their absence from the two `## Session Config` blocks (starter and
1007
+ // opt-in baseline) was ever true. The message below now asserts only that.
1077
1008
  //
1078
1009
  // `mode: hard`/`strict` still keys off `errors[]` only, so neither notes
1079
1010
  // nor the new warnings can block `runConfigValidationGate()`.
@@ -1091,10 +1022,11 @@ function main() {
1091
1022
  const localContent = readFileSync(instr.path, 'utf8');
1092
1023
  // 'last' occurrence reaches the "Full opt-in baseline" block (the full
1093
1024
  // key catalog, a strict superset of the minimal block) — issue #785.
1094
- // 'first' occurrence reaches the "Full minimal baseline" (7 mandatory
1095
- // keys) — used here to classify which missing keys are mandatory vs
1096
- // opt-in. When the template has only ONE `## Session Config` heading
1097
- // (e.g. a test fixture, or a repo that never split the two baselines),
1025
+ // 'first' occurrence reaches the "Starter config" (formerly "Full
1026
+ // minimal baseline"; 7 mandatory keys) — used here to classify which
1027
+ // missing keys are mandatory vs opt-in. When the template has only ONE
1028
+ // `## Session Config` heading (e.g. a test fixture, or a repo that never
1029
+ // split the two baselines),
1098
1030
  // 'first' and 'last' resolve to the SAME block, so every key is
1099
1031
  // treated as mandatory — preserving pre-existing behaviour for
1100
1032
  // single-block templates.
@@ -1297,7 +1229,7 @@ function main() {
1297
1229
  // Build the set of known learning keys from learnings.jsonl.
1298
1230
  // key → expires_at (ISO string or null)
1299
1231
  const knownKeys = new Map();
1300
- const learningsPath = join(vaultDir, '.orchestrator', 'metrics', 'learnings.jsonl');
1232
+ const learningsPath = resolveLedgerPath('.orchestrator/metrics/learnings.jsonl', vaultDir);
1301
1233
  const learningsPresent = existsSync(learningsPath) && statSync(learningsPath).isFile();
1302
1234
 
1303
1235
  if (learningsPresent) {
@@ -1341,6 +1273,7 @@ function main() {
1341
1273
  check: 'generated-rule-staleness',
1342
1274
  file: relPath,
1343
1275
  line: 1,
1276
+ // ledger-root: message text naming the ledger, not a path read
1344
1277
  message: `Auto-generated rule references learning-key '${learningKey}' which is absent from .orchestrator/metrics/learnings.jsonl, and the rule carries no valid evidence-digest to verify it offline`,
1345
1278
  extracted: learningKey,
1346
1279
  });
@@ -1382,7 +1315,7 @@ function main() {
1382
1315
 
1383
1316
  // Check 9: rule-scoping — validates .claude/rules/*.md frontmatter against
1384
1317
  // the rule-loader.mjs contract (see the doc-comment above the helper
1385
- // functions for the six probes, incl. unreadable-file and fleet-intent-glob). Silently skipped (no id pushed, no
1318
+ // functions for the seven probes, incl. unreadable-file and fleet-intent-glob). Silently skipped (no id pushed, no
1386
1319
  // checksSkipped entry) when .claude/rules/ is absent — mirrors Check 8's
1387
1320
  // silent-skip semantics. `--skip-rule-scoping` disables the whole check.
1388
1321
  if (!args.skipRuleScoping) {
@@ -1399,6 +1332,7 @@ function main() {
1399
1332
 
1400
1333
  // Lazily computed — only needed once any rule declares ≥1 glob pattern.
1401
1334
  let trackedFiles = null;
1335
+ let trackedFromGit = false;
1402
1336
 
1403
1337
  for (const fname of ruleFileNames) {
1404
1338
  const absPath = join(rulesDir, fname);
@@ -1457,10 +1391,30 @@ function main() {
1457
1391
  });
1458
1392
  }
1459
1393
 
1394
+ // --- Probe 7: globs-without-paths → warnings[] (#1638) ---
1395
+ // Keyed on the parser's raw `paths` field, never on the merged `globs`
1396
+ // value or on body text: `parsed.globs !== null` with `parsed.paths ===
1397
+ // null` means the frontmatter carries `globs:` and no `paths:`. A
1398
+ // displaced or unparseable block yields `globs: null` and stays silent
1399
+ // here (probe 1 owns the displaced case).
1400
+ if (parsed.globs !== null && parsed.paths === null) {
1401
+ // Search the frontmatter only (line 2 up to the closing ---), with the
1402
+ // parser's own key tolerance (`globs :`), so a body line never wins.
1403
+ const fmLines = content.split(/\r?\n/);
1404
+ const fmEnd = fmLines.indexOf('---', 1);
1405
+ const globsLine = fmLines.slice(0, fmEnd === -1 ? fmLines.length : fmEnd)
1406
+ .findIndex((l, i) => i > 0 && /^globs\s*:/.test(l)) + 1;
1407
+ warnings.push({
1408
+ check: 'rule-scoping', probe: 'globs-without-paths', file: relPath, line: globsLine || 1,
1409
+ message: `${relPath}: frontmatter has 'globs:' but no 'paths:' — Claude Code reads only 'paths:', so this rule loads ALWAYS-ON for Claude Code. Add 'paths:' with the matching patterns (the same list as 'globs:').`,
1410
+ extracted: 'globs:',
1411
+ });
1412
+ }
1413
+
1460
1414
  // --- Probes 3 & 4: zero-match-globs / foreign-glob → warnings[] ---
1461
1415
  const globs = parsed.globs;
1462
1416
  if (Array.isArray(globs) && globs.length > 0) {
1463
- if (trackedFiles === null) trackedFiles = listTrackedFiles(vaultDir);
1417
+ if (trackedFiles === null) ({ files: trackedFiles, fromGit: trackedFromGit } = listTrackedFiles(vaultDir));
1464
1418
  const declaredFleetIntent = new Set(parseFleetIntentGlobs(fmBody));
1465
1419
  for (const pattern of globs) {
1466
1420
  // --- Probe 6: fleet-intent-glob → notes[] ---
@@ -1480,14 +1434,19 @@ function main() {
1480
1434
  });
1481
1435
  continue;
1482
1436
  }
1483
- if (!globMatchesAny(pattern, trackedFiles)) {
1437
+ const matchesTracked = globMatchesAny(pattern, trackedFiles);
1438
+ if (!matchesTracked) {
1484
1439
  warnings.push({
1485
1440
  check: 'rule-scoping', file: relPath, line: 1,
1486
1441
  message: `glob '${pattern}' matches 0 tracked files (library/exemplar repos may legitimately carry dead stack rules)`,
1487
1442
  extracted: pattern,
1488
1443
  });
1489
1444
  }
1490
- if (FOREIGN_GLOB_TOKEN_RE.test(pattern)) {
1445
+ // #1657: a glob that matches a git-tracked file of THIS repo names
1446
+ // the repo's own directory (`WalkAITalkie/**` in WalkAITalkie), not
1447
+ // a copy-paste leftover. Only a git listing exempts — the walk
1448
+ // fallback keeps warning (fail toward reporting).
1449
+ if (FOREIGN_GLOB_TOKEN_RE.test(pattern) && !(trackedFromGit && matchesTracked)) {
1491
1450
  warnings.push({
1492
1451
  check: 'rule-scoping', file: relPath, line: 1,
1493
1452
  message: `glob '${pattern}' contains a PascalCase product-like token — verify this rule was not copy-pasted from another project`,
@@ -1687,6 +1646,7 @@ function main() {
1687
1646
  if (metricsLines[i].includes('.claude/metrics/')) {
1688
1647
  errors.push({
1689
1648
  check: 'docs-parity', file: rel, line: i + 1,
1649
+ // ledger-root: message text naming the canonical dir, not a path read
1690
1650
  message: `Stale metrics path '.claude/metrics/' referenced — canonical path is '.orchestrator/metrics/'`,
1691
1651
  extracted: '.claude/metrics/',
1692
1652
  });
@@ -13,15 +13,28 @@ The user invokes `/close` (arguments: **$ARGUMENTS** — none are interpreted; a
13
13
 
14
14
  The user wants to end the current session.
15
15
 
16
- **Pre-check before invoking session-end:** Determine `<state-dir>` from the current platform (`.claude/`, `.codex/`, `.cursor/`, or `.pi/`). Check if `<state-dir>/STATE.md` exists and read its `status` field. Three exit conditions:
16
+ **Pre-check before invoking session-end (#1661).** Run, from the project root:
17
17
 
18
- 1. **STATE.md does not exist:** Read Session Config to check `persistence`. If `persistence: false`, inform the user: "Session completed (persistence is off — STATE.md was never created). Use `/session` to start a new session." If `persistence: true` (or Session Config unavailable), inform the user: "No active session found. Use `/session` to start a session first." Either way, stop.
19
- 2. **STATE.md exists and `status: completed`:** `status: completed` alone is NOT proof that session-end's Phase 3.7 writer ever ran (#429) — the field can be set by hand, or by any path that stops short of that write. Parse STATE.md with `parseStateMd` and parse `.orchestrator/metrics/sessions.jsonl` as JSONL. Import `findRecordedSession` from the plugin's `scripts/lib/session-close-backfill.mjs` and call it with the parsed records and `{ sessionId: frontmatter['session-id'] ?? readLock({ repoRoot })?.session_id ?? null, semanticSessionId: frontmatter.session, startedAt: frontmatter.started_at }` (`readLock` from `scripts/lib/session-lock.mjs`). The lock fallback is sound here and ONLY here: `/close` runs inside the very session whose STATE.md it is reading, so the live lock names that same session — it is never a peer's id. STATE.md files written before #1368 carry no `session-id` key at all, and without the fallback every one of them misses the native fast path. This is the shared backfill identity reader: native UUIDs take precedence, conflicting native IDs never match through a label, and legacy semantic records remain readable. Do not substitute a text grep or compare the semantic label only against `session_id`. A missing ledger means no record; an unreadable or malformed ledger is inconclusive — report the read/parse failure and stop before repeating close side effects.
20
- - **A matching completed record exists (including a legacy authoritative record without a `status` field):** Inform the user: "Previous session was already finalized by session-end. Start a new session with `/session`, or inspect `<state-dir>/STATE.md` to review the prior close. (Note: if this is unexpected after an Express Path session, `skills/go/SKILL.md` should have auto-invoked /close — check that STATE.md `## Deviations` contains an `Express path:` entry.)" and stop.
21
- - **No matching completed record exists:** Warn the user: "STATE.md completed ohne passenden Abschluss im Ledger — session-end wird zur Vervollständigung ausgeführt (#429)." An abandoned backfill stub does not prove finalization. Do NOT stop — proceed to invoke the session-end skill exactly as in exit condition 3, so the missing completion gets written this time. (The SessionEnd hook's `backfillCompletedFromStateMd` independently self-heals this same gap on a later session's teardown; this Pre-Check branch covers the case where /close is re-run before that hook has had a chance to fire.)
22
- 3. **STATE.md exists and `status: active` or `status: paused`:** Proceed to invoke the session-end skill.
18
+ ```bash
19
+ node "${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-$PLUGIN_ROOT}}/scripts/close-precheck.mjs" --repo-root "$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
20
+ ```
23
21
 
24
- For any other `status` value (e.g., `idle`), warn the user: "Unexpected session status `<value>`. Inspect `<state-dir>/STATE.md` and use `/session` to reset if needed." and stop.
22
+ It prints one JSON object (exit 0 for every verdict). It reads `<state-dir>/STATE.md` for the current platform (`.claude/`, `.codex/`, `.cursor/`, `.pi/`) and decides **whose** it is before it looks at `status`: STATE.md `session-id` (#1368) against this process's native id (`readProcessLocalSessionIds()`). Never decide ownership from the session lock: it records the pid of the short-lived hook process, and in a shared checkout it can name a peer. After `/clear` the tab runs under a NEW native id, so the STATE.md of the work before `/clear` reads as foreign — measured 2026-10-17 in three sessions. A STATE.md without `session-id` (before #1368) or a process without a native id (`ownership: legacy` / `unprovable`) never reaches session-end on its status alone (#1718): where the status would hand off, the verdict is `ownership-unproven`. An optional `--confirm-own <confirmToken>` (16 lowercase hex, from that verdict) re-runs the pre-check for the unchanged file. Act on `verdict`:
23
+
24
+ 1. **`run-session-end`** — own STATE.md, or one confirmed with `--confirm-own` (`ownership: confirmed`), `status: active` or `paused`: invoke the session-end skill.
25
+ 2. **`complete-missing-record`** — own or confirmed STATE.md `status: completed`, but `.orchestrator/metrics/sessions.jsonl` holds no completed record for it (#429: `completed` alone does not prove the Phase 3.7 writer ran; an abandoned backfill stub is no completion). Warn: "STATE.md completed ohne passenden Abschluss im Ledger — session-end wird zur Vervollständigung ausgeführt (#429)." and invoke the session-end skill. (The SessionEnd hook's `backfillCompletedFromStateMd` heals the same gap on a later teardown; this branch covers a `/close` re-run before that.)
26
+ 3. **`already-finalized`** — a matching completed record exists (a legacy record without `status` counts): "Previous session was already finalized by session-end. Start a new session with `/session`, or inspect `<state-dir>/STATE.md` to review the prior close. (Note: if this is unexpected after an Express Path session, `skills/go/SKILL.md` should have auto-invoked /close — check that STATE.md `## Deviations` contains an `Express path:` entry.)" Stop.
27
+ 4. **`foreign`** — STATE.md belongs to session `stateSessionId`, not to this one, whatever its `status`. Never invoke session-end on it and never edit it: an `active`/`paused` one is a peer's live session, a `completed` one an earlier tab or a peer. Inform: "STATE.md gehört Session `<stateSessionId>` (status `<status>`), nicht dieser — kein eigener Plan zum Abschließen." With `status: active` or `paused` add: "Ist das der Plan dieses Tabs von vor einem `/clear`, kann ihn dieser Prozess nicht als eigenen belegen; er bleibt offen, der SessionEnd-Hook füllt den Ledger nach." Stop.
28
+ 5. **`ownership-unproven`** — nobody can prove this STATE.md is this session's (`ownership` `legacy`: no `session-id`; or `unprovable`: this process has no native id, as on Cursor, Pi, Codex without a thread id); session-end would run `wouldRun`. Never invoke session-end on this result and never edit the file. Inform with the facts from the JSON: "STATE.md `<statePath>` ist keiner Session nachweisbar zugeordnet (ownership `<ownership>`, session `<session>`, session-id `<stateSessionId>`, started_at `<startedAt>`, status `<status>`); session-end würde `<wouldRun>` ausführen." — the `session-id` part only when `stateSessionId` is present: without a native id (`unprovable`) a STATE.md that names another session's id comes back here, and the operator must see that. Confirm ONLY when (a) the operator explicitly answers that this is this session's plan — ask via `AskUserQuestion` on Claude Code (a numbered list on Codex/Cursor/Pi, per `.claude/rules/ask-via-tool.md`), the question naming path, session, `session-id` (when present), started_at and status; or (b) first-person evidence: this session's OWN tool call that wrote exactly this STATE.md is visible in its current context (for example its own wave-executor initialised it in this conversation). A compaction summary, a handover file, memory notes or the STATE.md content itself are NOT first-person evidence, and neither is anything inferred from the `session` label, `started_at` or the session lock, which are repo-wide. With a native id (Claude Code, Codex with a thread id) a `legacy` file is usually an older or foreign file — writers stamp `session-id` whenever this process's own lock exists at write time, so an own legacy file arises only when the STATE.md was written without an own lock; expect (b) to apply rarely there. A Lead-led or headless session without (b) never confirms: it stops and reports. On confirmation, re-run the pre-check with `--confirm-own <confirmToken>` and act on the NEW verdict per items 1/2. If it again returns `ownership-unproven` with `reason` (the file changed), show the new facts and decide again; never reuse an old token. Otherwise the plan stays untouched: it belongs to another or an earlier session; start an own `/session`, or inspect the file. Stop.
29
+ 6. **`no-state`** — read Session Config `persistence`. `false`: "Session completed (persistence is off — STATE.md was never created). Use `/session` to start a new session." Otherwise: "No active session found. Use `/session` to start a session first." Stop.
30
+ 7. **`unexpected-status`** (e.g. `idle`): "Unexpected session status `<status>`. Inspect `<state-dir>/STATE.md` and use `/session` to reset if needed." Stop.
31
+ 8. **`inconclusive`** — STATE.md frontmatter or the ledger is unreadable (`reason`): report it and stop before repeating any close side effect.
32
+
33
+ **`/close` ohne Plan.** Every stop above (3–8) leaves this session without an own plan to close — the wait case `close-ohne-plan`. With fleet integration, before the report run the queue-mark re-check on the own handover (`skills/session-end/SKILL.md` § 4.5, "Queue marks on the own handover", #1639); its JSON `file` says whether this session has an own handover or train manifest. Then apply `skills/_shared/fleet-protocol.md` § Rücklauf an den Navigator (Pflicht) with the wait case `close-ohne-plan` (exactly one report in this turn, only with fleet integration, fallback chain up to `Meldung gescheitert: <Grund>`, never wait for a reply), and end the answer — after the message quoted above — with exactly one closing sentence (none of those messages is recognised as a waiting answer on its own):
34
+ - `Bereit zum Schließen.` only on `already-finalized`, `no-state`, `unexpected-status`, or a `foreign` or `ownership-unproven` STATE.md with `status: completed`, and only when the re-check found an own handover (`file` not `null`) and reported no unmarked points (exit 0): the work of this session is handed over, and the coordinator may clear the tab (fleet-protocol § Übergabe und Zug-Manifest, Punkt 3). A `foreign` stop is the normal end of a session that worked after `/clear` without its own `/session`.
35
+ - `Ich warte auf den nächsten Auftrag.` otherwise — no own handover, unmarked points (exit 3), no fleet integration, `inconclusive` (the status is unknown), or a `foreign` or `ownership-unproven` STATE.md that is still `active`/`paused` (a plan that is still open must not let the coordinator clear the tab).
36
+
37
+ session-end itself never runs on this path. A session that only waits on a pipeline or release train closes normally: its train manifest belongs to the waiting answer BEFORE `/close` (same section, Übergabe und Zug-Manifest), and session-end Phase 4.5 reports the case `zug-<name>` once in the `/close` turn.
25
38
 
26
39
  If the pre-check passes, invoke the session-end skill.
27
40
 
@@ -32,7 +32,8 @@
32
32
  */
33
33
 
34
34
  import { existsSync, readdirSync, statSync, mkdirSync, appendFileSync } from 'node:fs';
35
- import { join } from 'node:path';
35
+ import { dirname, join } from 'node:path';
36
+ import { resolveLedgerPath } from '../../../scripts/lib/ledger-path.mjs';
36
37
 
37
38
  // ---------------------------------------------------------------------------
38
39
  // Constants
@@ -70,8 +71,8 @@ function listMarkdownFiles(dirPath) {
70
71
  return entries.filter((e) => e.isFile() && e.name.endsWith('.md')).map((e) => e.name);
71
72
  }
72
73
 
73
- function makeMetricsPath(projectRoot) {
74
- return join(projectRoot, '.orchestrator', 'metrics');
74
+ function makeLedgerPath(projectRoot) {
75
+ return resolveLedgerPath('.orchestrator/metrics/docs-staleness.jsonl', projectRoot);
75
76
  }
76
77
 
77
78
  // ---------------------------------------------------------------------------
@@ -158,7 +159,8 @@ export async function runProbe(projectRoot, config) {
158
159
 
159
160
  // ── JSONL append ──────────────────────────────────────────────────────
160
161
  try {
161
- const metricsDir = makeMetricsPath(projectRoot);
162
+ const ledgerPath = makeLedgerPath(projectRoot);
163
+ const metricsDir = dirname(ledgerPath);
162
164
  if (!existsSync(metricsDir)) mkdirSync(metricsDir, { recursive: true });
163
165
  const record = {
164
166
  timestamp: new Date().toISOString(),
@@ -175,7 +177,7 @@ export async function runProbe(projectRoot, config) {
175
177
  threshold_days: f.evidence.threshold_days ?? threshold,
176
178
  })),
177
179
  };
178
- appendFileSync(join(metricsDir, 'docs-staleness.jsonl'), JSON.stringify(record) + '\n', 'utf8');
180
+ appendFileSync(ledgerPath, JSON.stringify(record) + '\n', 'utf8');
179
181
  } catch {
180
182
  // JSONL write failure is non-fatal — probe result is still returned
181
183
  }
@@ -28,6 +28,7 @@
28
28
 
29
29
  import { existsSync, readdirSync, statSync } from 'node:fs';
30
30
  import { join } from 'node:path';
31
+ import { fileURLToPath, pathToFileURL } from 'node:url';
31
32
 
32
33
  /** Max files to scan before sampling — protects against huge trees. */
33
34
  const MAX_FILES = 2000;
@@ -127,7 +128,10 @@ export default async function frontendSlop({ repoRoot }) {
127
128
 
128
129
  // Defensive import — mirrors supply-chain-slopcheck's pattern so the probe
129
130
  // never hard-crashes the discovery run if the detector is absent.
130
- const detectPath = join(repoRoot, 'scripts', 'lib', 'frontend-detect', 'detect.mjs');
131
+ // The detector ships with the PLUGIN, not with the scanned repo: resolve it
132
+ // from this module, never from repoRoot (#1653 — in every consumer repo the
133
+ // repoRoot path is absent and the probe skipped silently).
134
+ const detectPath = fileURLToPath(new URL('../../../scripts/lib/frontend-detect/detect.mjs', import.meta.url));
131
135
  if (!existsSync(detectPath)) {
132
136
  return {
133
137
  probe,
@@ -137,7 +141,7 @@ export default async function frontendSlop({ repoRoot }) {
137
141
  title: 'frontend-slop: detector module unavailable',
138
142
  evidence: { file: detectPath, classification: 'SKIPPED' },
139
143
  recommendation:
140
- 'scripts/lib/frontend-detect/detect.mjs is missing. Re-run /discovery after it is committed.',
144
+ 'scripts/lib/frontend-detect/detect.mjs is missing from the plugin install. Reinstall or update the plugin.',
141
145
  },
142
146
  ],
143
147
  summary: { total: 0, filesScanned: 0, sampled: false, errors: 0, skipped_reason: 'detector-unavailable' },
@@ -146,7 +150,7 @@ export default async function frontendSlop({ repoRoot }) {
146
150
 
147
151
  let detectFiles, SCANNABLE_EXTS;
148
152
  try {
149
- ({ detectFiles, SCANNABLE_EXTS } = await import(detectPath));
153
+ ({ detectFiles, SCANNABLE_EXTS } = await import(pathToFileURL(detectPath).href));
150
154
  } catch (err) {
151
155
  return {
152
156
  probe,
@@ -45,8 +45,9 @@
45
45
  */
46
46
 
47
47
  import { existsSync, readdirSync, readFileSync, mkdirSync, appendFileSync } from 'node:fs';
48
- import { join } from 'node:path';
48
+ import { dirname, join } from 'node:path';
49
49
  import { slashCommandNames } from '../../../scripts/lib/user-invocable-skills.mjs';
50
+ import { resolveLedgerPath } from '../../../scripts/lib/ledger-path.mjs';
50
51
 
51
52
  // ---------------------------------------------------------------------------
52
53
  // Doc source sets (deliberately small and hand-curated)
@@ -277,7 +278,8 @@ export async function runProbe(projectRoot, config) {
277
278
 
278
279
  // ── JSONL append ──────────────────────────────────────────────────────
279
280
  try {
280
- const metricsDir = join(projectRoot, '.orchestrator', 'metrics');
281
+ const ledgerPath = resolveLedgerPath('.orchestrator/metrics/ssot-code-diff.jsonl', projectRoot);
282
+ const metricsDir = dirname(ledgerPath);
281
283
  if (!existsSync(metricsDir)) mkdirSync(metricsDir, { recursive: true });
282
284
  const record = {
283
285
  timestamp: new Date().toISOString(),
@@ -296,7 +298,7 @@ export async function runProbe(projectRoot, config) {
296
298
  registry_id: f.evidence.registry_id,
297
299
  })),
298
300
  };
299
- appendFileSync(join(metricsDir, 'ssot-code-diff.jsonl'), JSON.stringify(record) + '\n', 'utf8');
301
+ appendFileSync(ledgerPath, JSON.stringify(record) + '\n', 'utf8');
300
302
  } catch {
301
303
  // JSONL write failure is non-fatal — probe result is still returned
302
304
  }