session-orchestrator 5.11.0 → 5.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. package/.claude-plugin/marketplace.json +12 -1
  2. package/.claude-plugin/plugin.json +4 -1
  3. package/.codex-plugin/agents/explorer.toml +1 -1
  4. package/.codex-plugin/agents/session-reviewer.toml +2 -2
  5. package/.codex-plugin/agents/wave-worker.toml +1 -1
  6. package/.codex-plugin/plugin.json +1 -1
  7. package/.cursor-plugin/plugin.json +1 -1
  8. package/.orchestrator/policy/blocked-commands.json +8 -0
  9. package/.orchestrator/policy/public-export.json +30 -0
  10. package/CHANGELOG.md +180 -3
  11. package/README.md +126 -117
  12. package/agents/analyst.md +7 -2
  13. package/agents/architect-reviewer.md +7 -2
  14. package/agents/code-implementer.md +4 -2
  15. package/agents/db-specialist.md +9 -2
  16. package/agents/dialectic-deriver.md +9 -3
  17. package/agents/docs-writer.md +7 -2
  18. package/agents/eval-judge.md +7 -1
  19. package/agents/qa-strategist.md +7 -2
  20. package/agents/security-reviewer.md +9 -2
  21. package/agents/session-reviewer.md +18 -2
  22. package/agents/skill-applied-judge.md +7 -1
  23. package/agents/test-writer.md +9 -2
  24. package/agents/ui-developer.md +9 -2
  25. package/agents/ux-evaluator.md +6 -1
  26. package/commands/session.md +4 -2
  27. package/commands/templates-ack.md +89 -18
  28. package/docs/agent-authoring.md +30 -11
  29. package/docs/ci-setup.md +38 -2
  30. package/docs/codex-setup.md +33 -9
  31. package/docs/components.md +13 -2
  32. package/docs/cursor-setup.md +3 -3
  33. package/docs/events-schema.md +5 -4
  34. package/docs/evidence-candidates.md +63 -0
  35. package/docs/fleet-checkin-audit.md +7 -4
  36. package/docs/github-mirror-protection.md +150 -3
  37. package/docs/install.md +102 -25
  38. package/docs/instruction-delivery.md +1 -1
  39. package/docs/migration-v4.md +2 -2
  40. package/docs/mods.md +118 -0
  41. package/docs/owner-config-schema.md +1 -0
  42. package/docs/plugin-architecture-v3.md +1 -1
  43. package/docs/pm-skills-marketplace.md +4 -3
  44. package/docs/rule-authoring.md +24 -13
  45. package/docs/scope-collision-guard.md +2 -1
  46. package/docs/session-config-reference.md +55 -21
  47. package/docs/session-config-template.md +32 -24
  48. package/docs/sessions-ledger-repair.md +137 -0
  49. package/docs/vault-docs-architecture.md +1 -1
  50. package/hooks/_lib/hook-import-set.json +406 -73
  51. package/hooks/_lib/hook-root.mjs +301 -0
  52. package/hooks/_lib/lock-bootstrap.mjs +434 -4
  53. package/hooks/_lib/orchestrator-ignore.mjs +3 -0
  54. package/hooks/_lib/subagent-transcript.mjs +2 -2
  55. package/hooks/_lib/vcs-create-matcher.mjs +13 -17
  56. package/hooks/cwd-change-restore.mjs +5 -1
  57. package/hooks/enforce-commands.mjs +4 -3
  58. package/hooks/enforce-scope.mjs +11 -1
  59. package/hooks/hooks-codex.json +48 -2
  60. package/hooks/hooks.json +31 -31
  61. package/hooks/on-session-end.mjs +38 -15
  62. package/hooks/on-session-start.mjs +158 -59
  63. package/hooks/on-stop.mjs +11 -6
  64. package/hooks/post-bash-issue-budget-refund.mjs +6 -1
  65. package/hooks/post-bash-write-verify.mjs +8 -4
  66. package/hooks/post-edit-validate.mjs +45 -52
  67. package/hooks/post-subagent-discovery-validator.mjs +2 -2
  68. package/hooks/post-tool-batch-wave-signal.mjs +8 -4
  69. package/hooks/post-tool-failure-corrective-context.mjs +5 -1
  70. package/hooks/pre-bash-destructive-guard.mjs +220 -8
  71. package/hooks/pre-bash-issue-budget.mjs +6 -1
  72. package/hooks/pre-bash-sessions-ledger-guard.mjs +691 -123
  73. package/hooks/pre-bash-staging-fence.mjs +258 -14
  74. package/hooks/pre-bash-templates-first.mjs +28 -27
  75. package/hooks/run-node.sh +1 -1
  76. package/hooks/skill-invocation-telemetry.mjs +15 -8
  77. package/hooks/subagent-telemetry.mjs +77 -14
  78. package/hooks/wave-scope-commit-guard.mjs +143 -14
  79. package/mods/so-hud/.claude-plugin/plugin.json +11 -0
  80. package/mods/so-hud/README.md +57 -0
  81. package/mods/so-hud/hooks/beacon-io.mjs +101 -0
  82. package/mods/so-hud/hooks/heredoc-scan.ts +394 -0
  83. package/mods/so-hud/hooks/hooks.json +1 -0
  84. package/mods/so-hud/hooks/logic.ts +182 -0
  85. package/mods/so-hud/hooks/register.tsx +670 -0
  86. package/mods/so-hud/hooks/rules.ts +2998 -0
  87. package/mods/so-hud/tsconfig.json +8 -0
  88. package/mods/so-hud/types/index.d.ts +36 -0
  89. package/package.json +4 -3
  90. package/rules/_index.md +1 -1
  91. package/rules/always-on/ask-via-tool.md +1 -1
  92. package/rules/always-on/loop-and-monitor.md +17 -194
  93. package/rules/always-on/parallel-sessions.md +3 -1
  94. package/rules/opt-in-stack/backend-data.md +1 -1
  95. package/rules/opt-in-stack/backend.md +1 -1
  96. package/rules/opt-in-stack/security-web.md +5 -1
  97. package/scripts/agent-usage-census.mjs +79 -0
  98. package/scripts/apply-session-learnings.mjs +34 -6
  99. package/scripts/archive-closed-prds.mjs +21 -5
  100. package/scripts/autopilot.mjs +11 -5
  101. package/scripts/backfill-abandoned-sessions.mjs +13 -3
  102. package/scripts/backfill-evidence-digest.mjs +2 -1
  103. package/scripts/backfill-learnings-from-vault.mjs +61 -6
  104. package/scripts/backfill-sessions.mjs +16 -2
  105. package/scripts/check-package-manager.mjs +26 -8
  106. package/scripts/check-sessions-integrity.mjs +4 -1
  107. package/scripts/ci/assert-vitest-green.mjs +3 -0
  108. package/scripts/ci/windows-smoke.mjs +201 -0
  109. package/scripts/close-precheck.mjs +55 -0
  110. package/scripts/dialectic-deriver.mjs +20 -12
  111. package/scripts/emit-event.mjs +2 -2
  112. package/scripts/emit-session.mjs +239 -20
  113. package/scripts/eval-session.mjs +17 -3
  114. package/scripts/export-hw-learnings.mjs +29 -2
  115. package/scripts/fleet-checkin-audit.mjs +8 -4
  116. package/scripts/fleet-checkin.mjs +73 -3
  117. package/scripts/gc-stale-worktrees.mjs +6 -1
  118. package/scripts/github-export.mjs +128 -0
  119. package/scripts/github-protection-audit.mjs +3 -1
  120. package/scripts/handover-queue-check.mjs +209 -0
  121. package/scripts/harness-audit.mjs +6 -4
  122. package/scripts/lib/agent-frontmatter.mjs +57 -10
  123. package/scripts/lib/agent-status.mjs +125 -7
  124. package/scripts/lib/agent-usage-census.mjs +394 -0
  125. package/scripts/lib/auto-dialectic.mjs +17 -10
  126. package/scripts/lib/auto-dream.mjs +22 -14
  127. package/scripts/lib/autopilot/loop.mjs +3 -1
  128. package/scripts/lib/autopilot/recent-runs.mjs +4 -2
  129. package/scripts/lib/autopilot/stall-sampler.mjs +2 -1
  130. package/scripts/lib/autopilot/telemetry.mjs +5 -1
  131. package/scripts/lib/autopilot/worktree-pipeline.mjs +5 -5
  132. package/scripts/lib/baseline-archetypes.mjs +10 -2
  133. package/scripts/lib/blocked-commands-policy.mjs +4 -3
  134. package/scripts/lib/build-live-signals.mjs +13 -9
  135. package/scripts/lib/ci-status-banner.mjs +329 -24
  136. package/scripts/lib/codex/plugin-contract.mjs +8 -0
  137. package/scripts/lib/cold-start-detector.mjs +5 -2
  138. package/scripts/lib/command-blocker.mjs +603 -43
  139. package/scripts/lib/common.mjs +3 -2
  140. package/scripts/lib/config/cross-repo.mjs +74 -7
  141. package/scripts/lib/config/host-paths.mjs +6 -2
  142. package/scripts/lib/config/remote-hosts.mjs +2 -2
  143. package/scripts/lib/config/section-extractor.mjs +65 -11
  144. package/scripts/lib/config/template-keys.mjs +153 -0
  145. package/scripts/lib/config/vault-integration.mjs +1 -0
  146. package/scripts/lib/config.mjs +3 -3
  147. package/scripts/lib/coordinator-snapshot.mjs +1 -1
  148. package/scripts/lib/discovery/triage-state.mjs +2 -1
  149. package/scripts/lib/dispatcher/rank.mjs +5 -7
  150. package/scripts/lib/ecosystem-health.mjs +2 -2
  151. package/scripts/lib/eval/engine.mjs +3 -2
  152. package/scripts/lib/eval/session-resolve.mjs +6 -2
  153. package/scripts/lib/eval/sink.mjs +4 -3
  154. package/scripts/lib/events-retention-banner.mjs +3 -2
  155. package/scripts/lib/events-schema.mjs +10 -0
  156. package/scripts/lib/events.mjs +23 -12
  157. package/scripts/lib/evolve/autonomy-verdict.mjs +4 -9
  158. package/scripts/lib/evolve/autopilot-effectiveness.mjs +8 -18
  159. package/scripts/lib/fleet-protocol.mjs +21 -0
  160. package/scripts/lib/gates/gate-full.mjs +172 -11
  161. package/scripts/lib/gates/gate-helpers.mjs +236 -23
  162. package/scripts/lib/git-config-drift.mjs +141 -7
  163. package/scripts/lib/github-export.mjs +447 -0
  164. package/scripts/lib/gitlab-portfolio/cli.mjs +16 -5
  165. package/scripts/lib/handover-queue-marks.mjs +159 -0
  166. package/scripts/lib/hardware-pattern-detector.mjs +103 -13
  167. package/scripts/lib/harness-audit/categories/category1.mjs +16 -4
  168. package/scripts/lib/harness-audit/categories/category4.mjs +11 -8
  169. package/scripts/lib/harness-audit/categories/category6.mjs +25 -5
  170. package/scripts/lib/harness-audit/categories/category9.mjs +10 -4
  171. package/scripts/lib/host-identity.mjs +9 -5
  172. package/scripts/lib/http-request.mjs +64 -0
  173. package/scripts/lib/instruction-budget-guard.mjs +30 -22
  174. package/scripts/lib/io.mjs +6 -3
  175. package/scripts/lib/issue-budget-reconcile.mjs +3 -2
  176. package/scripts/lib/issue-budget.mjs +36 -7
  177. package/scripts/lib/learnings/evidence-candidates.mjs +154 -0
  178. package/scripts/lib/learnings/expiry-sweep.mjs +99 -7
  179. package/scripts/lib/learnings/io.mjs +289 -15
  180. package/scripts/lib/learnings/select.mjs +53 -4
  181. package/scripts/lib/learnings/store-role.mjs +209 -0
  182. package/scripts/lib/learnings/surface.mjs +46 -7
  183. package/scripts/lib/ledger-path.mjs +113 -0
  184. package/scripts/lib/maintenance-due-banner.mjs +63 -12
  185. package/scripts/lib/memory-banner.mjs +5 -6
  186. package/scripts/lib/memory-proposals/collector.mjs +5 -2
  187. package/scripts/lib/memory-proposals/sink.mjs +7 -4
  188. package/scripts/lib/memory-proposals/store.mjs +22 -5
  189. package/scripts/lib/mode-selector-accuracy.mjs +2 -1
  190. package/scripts/lib/no-follow-open.mjs +57 -0
  191. package/scripts/lib/orphan-reaper/reaper-audit.mjs +4 -2
  192. package/scripts/lib/own-lock-root.mjs +95 -0
  193. package/scripts/lib/owner-interview.mjs +2 -1
  194. package/scripts/lib/owner-yaml.mjs +70 -7
  195. package/scripts/lib/platform.mjs +161 -9
  196. package/scripts/lib/plugin-update-banner.mjs +15 -11
  197. package/scripts/lib/pre-push-local-partial.mjs +117 -0
  198. package/scripts/lib/pre-push-remote-gate.mjs +151 -25
  199. package/scripts/lib/process-group.mjs +270 -34
  200. package/scripts/lib/project-hygiene.mjs +28 -0
  201. package/scripts/lib/quality-gate.mjs +33 -9
  202. package/scripts/lib/reconcile/backlog.mjs +29 -15
  203. package/scripts/lib/reconcile/engine.mjs +26 -2
  204. package/scripts/lib/reconcile/idempotency.mjs +65 -3
  205. package/scripts/lib/reconcile/rule-expiry-sweep.mjs +53 -12
  206. package/scripts/lib/reconcile/verdict-ledger.mjs +224 -0
  207. package/scripts/lib/reconcile-nudge-banner.mjs +40 -6
  208. package/scripts/lib/resource-probe/evaluate.mjs +32 -9
  209. package/scripts/lib/resource-probe/probe-platform.mjs +3 -1
  210. package/scripts/lib/rule-loader.mjs +20 -10
  211. package/scripts/lib/scope-baseline.mjs +123 -8
  212. package/scripts/lib/scope-echo.mjs +5 -2
  213. package/scripts/lib/scope-gate.mjs +55 -20
  214. package/scripts/lib/session-close-backfill.mjs +75 -7
  215. package/scripts/lib/session-end/close-precheck.mjs +151 -0
  216. package/scripts/lib/session-end/phase-skip.mjs +8 -7
  217. package/scripts/lib/session-end/tail-runner.mjs +7 -2
  218. package/scripts/lib/session-end/vault-orphans.mjs +205 -0
  219. package/scripts/lib/session-id.mjs +9 -1
  220. package/scripts/lib/session-identity/own-session.mjs +63 -0
  221. package/scripts/lib/session-invocation.mjs +14 -3
  222. package/scripts/lib/session-lock.mjs +114 -0
  223. package/scripts/lib/session-record-repair.mjs +245 -35
  224. package/scripts/lib/session-registry.mjs +85 -12
  225. package/scripts/lib/session-schema/constants.mjs +9 -0
  226. package/scripts/lib/session-schema/filters.mjs +56 -1
  227. package/scripts/lib/session-schema/validator.mjs +15 -5
  228. package/scripts/lib/session-start-probes.mjs +149 -22
  229. package/scripts/lib/session-token-rollup.mjs +2 -1
  230. package/scripts/lib/session-work-trees.mjs +166 -0
  231. package/scripts/lib/sessions-canonical.mjs +61 -3
  232. package/scripts/lib/sessions-integrity-banner.mjs +17 -39
  233. package/scripts/lib/sessions-staleness-banner.mjs +8 -5
  234. package/scripts/lib/sh-quote.mjs +1 -0
  235. package/scripts/lib/skill-evidence-window.mjs +5 -1
  236. package/scripts/lib/skill-health/join.mjs +10 -9
  237. package/scripts/lib/skill-invocations-schema.mjs +13 -0
  238. package/scripts/lib/skill-judgments-schema.mjs +6 -2
  239. package/scripts/lib/snapshot-probe.mjs +414 -0
  240. package/scripts/lib/so-hud/collect.mjs +174 -0
  241. package/scripts/lib/so-hud/runtime.mjs +87 -0
  242. package/scripts/lib/spiral-carryover.mjs +8 -1
  243. package/scripts/lib/state-md/frontmatter-mutators.mjs +266 -8
  244. package/scripts/lib/state-md/idle-reset.mjs +248 -0
  245. package/scripts/lib/state-md/mission-status.mjs +5 -1
  246. package/scripts/lib/state-md/recommendations.mjs +26 -4
  247. package/scripts/lib/state-md-peer-guard.mjs +2 -0
  248. package/scripts/lib/state-md.mjs +31 -8
  249. package/scripts/lib/sunset/walker.mjs +4 -3
  250. package/scripts/lib/telemetry/consent.mjs +4 -2
  251. package/scripts/lib/telemetry/queue.mjs +3 -3
  252. package/scripts/lib/telemetry/sync.mjs +45 -9
  253. package/scripts/lib/test-runner/artifact-paths.mjs +10 -2
  254. package/scripts/lib/tmux-layout/layouts.mjs +20 -3
  255. package/scripts/lib/ux-grill/paths.mjs +5 -1
  256. package/scripts/lib/validate/check-agents.mjs +37 -6
  257. package/scripts/lib/validate/check-component-paths.mjs +25 -1
  258. package/scripts/lib/validate/check-guard-requires-parity.mjs +3 -3
  259. package/scripts/lib/validate/check-hooks-symmetry.mjs +53 -32
  260. package/scripts/lib/validate/check-json-files.mjs +3 -1
  261. package/scripts/lib/validate/check-learning-provenance.mjs +3 -2
  262. package/scripts/lib/validate/check-learnings-shell-writes.mjs +10 -4
  263. package/scripts/lib/validate/check-metrics-ledger-root.mjs +234 -0
  264. package/scripts/lib/validate/check-owner-leakage.mjs +159 -11
  265. package/scripts/lib/validate/check-test-posix-shell.mjs +246 -0
  266. package/scripts/lib/validate/check-unwired-features.mjs +4 -2
  267. package/scripts/lib/validate/check-vcs-repo-flag-positional.mjs +58 -0
  268. package/scripts/lib/validate/check-vcs-repo-flag.mjs +259 -73
  269. package/scripts/lib/validate/check-workflow-continue-on-error.mjs +63 -0
  270. package/scripts/lib/validate/confidential-names.mjs +3 -1
  271. package/scripts/lib/vault-mirror/migrate-ids.mjs +321 -0
  272. package/scripts/lib/vault-mirror/namespace.mjs +22 -9
  273. package/scripts/lib/vault-mirror/process.mjs +197 -65
  274. package/scripts/lib/vault-mirror/prune.mjs +5 -2
  275. package/scripts/lib/vault-mirror/render-learnings.mjs +8 -7
  276. package/scripts/lib/vault-mirror/render-sessions.mjs +36 -53
  277. package/scripts/lib/vault-mirror/retention.mjs +2 -2
  278. package/scripts/lib/vault-mirror/utils.mjs +106 -13
  279. package/scripts/lib/vault-staleness-banner.mjs +4 -7
  280. package/scripts/lib/vault-status/board-writer.mjs +34 -5
  281. package/scripts/lib/vault-status/narrative-mirror.mjs +66 -7
  282. package/scripts/lib/wave-executor/foreign-dispatch.mjs +20 -0
  283. package/scripts/lib/win-process-tree.mjs +137 -0
  284. package/scripts/lib/workspace.mjs +1 -1
  285. package/scripts/lib/worktree/constants.mjs +1 -1
  286. package/scripts/lib/worktree/lifecycle.mjs +1 -1
  287. package/scripts/lib/worktree/listing.mjs +1 -1
  288. package/scripts/lib/zx.mjs +50 -0
  289. package/scripts/mcp-server.mjs +16 -7
  290. package/scripts/mcp-server.sh +13 -83
  291. package/scripts/measure-policy-cache-effectiveness.mjs +7 -5
  292. package/scripts/memory-propose.mjs +10 -3
  293. package/scripts/migrate-cold-start-seed.mjs +7 -0
  294. package/scripts/migrate-legacy-learnings.sh +22 -0
  295. package/scripts/migrate-sessions-jsonl.mjs +16 -2
  296. package/scripts/migrate-vault-paths.mjs +29 -13
  297. package/scripts/parse-config.mjs +71 -14
  298. package/scripts/print-learnings-index.mjs +25 -2
  299. package/scripts/release-session-lock.mjs +94 -7
  300. package/scripts/release.mjs +451 -24
  301. package/scripts/relocate-vault-corpus.mjs +11 -7
  302. package/scripts/repair-invalid-sessions.mjs +68 -3
  303. package/scripts/resolve-session-invocation.mjs +57 -8
  304. package/scripts/run-migrate-v2-cross-repo.mjs +56 -1
  305. package/scripts/run-quality-gate.mjs +33 -7
  306. package/scripts/self-update.mjs +56 -7
  307. package/scripts/session-commit-files.mjs +133 -0
  308. package/scripts/site-numbers.mjs +52 -9
  309. package/scripts/snapshot-probe.mjs +156 -0
  310. package/scripts/so-hud.mjs +68 -0
  311. package/scripts/state-md-idle-reset.mjs +82 -0
  312. package/scripts/sweep-expired-learnings.mjs +54 -6
  313. package/scripts/sweep-expired-rules.mjs +4 -1
  314. package/scripts/validate-plugin.mjs +22 -0
  315. package/scripts/vault-integration-watcher.mjs +54 -6
  316. package/scripts/vault-mirror.mjs +9 -0
  317. package/scripts/vault-orphans.mjs +171 -0
  318. package/skills/_shared/fleet-protocol.md +166 -3
  319. package/skills/_shared/instruction-file-resolution.md +1 -1
  320. package/skills/_shared/monitor-patterns.md +44 -25
  321. package/skills/_shared/platform-tools.md +7 -0
  322. package/skills/_shared/state-ownership.md +5 -4
  323. package/skills/architecture/references/domain-model.md +1 -1
  324. package/skills/autopilot/SKILL.md +3 -2
  325. package/skills/bootstrap/SKILL.md +3 -1
  326. package/skills/bootstrap/_shared-template.md +4 -4
  327. package/skills/bootstrap/fast-template.md +1 -1
  328. package/skills/bootstrap/private-contract.md +1 -1
  329. package/skills/bootstrap/references/bootstrap-retroactive-flow.md +17 -9
  330. package/skills/bootstrap/templates/agents/project-code-review.md +5 -1
  331. package/skills/bootstrap/templates/agents/project-discovery.md +5 -1
  332. package/skills/bootstrap/templates/agents/project-quality-gate.md +5 -1
  333. package/skills/claude-md-drift-check/SKILL.md +3 -3
  334. package/skills/claude-md-drift-check/checker.mjs +61 -101
  335. package/skills/close/SKILL.md +20 -7
  336. package/skills/discovery/probes/docs-staleness.mjs +7 -5
  337. package/skills/discovery/probes/frontend-slop.mjs +7 -3
  338. package/skills/discovery/probes/ssot-code-diff.mjs +5 -3
  339. package/skills/discovery/probes/supply-chain-slopcheck.mjs +10 -8
  340. package/skills/discovery/probes/vault-narrative-staleness.mjs +30 -10
  341. package/skills/discovery/probes/vault-staleness.mjs +5 -3
  342. package/skills/discovery/probes-session.md +3 -0
  343. package/skills/discovery/probes-vault.md +2 -0
  344. package/skills/dispatcher/SKILL.md +1 -1
  345. package/skills/eval/SKILL.md +3 -1
  346. package/skills/evolve/SKILL.md +2 -0
  347. package/skills/evolve/references/evolve-analyze-mode.md +3 -3
  348. package/skills/evolve/references/evolve-dialectic-mode.md +1 -1
  349. package/skills/gitlab-ops/SKILL.md +7 -6
  350. package/skills/gitlab-portfolio/SKILL.md +2 -2
  351. package/skills/go/SKILL.md +13 -1
  352. package/skills/npm-publish/SKILL.md +5 -2
  353. package/skills/plan/SKILL.md +23 -10
  354. package/skills/plan/mode-feature.md +13 -8
  355. package/skills/plan/mode-new.md +6 -14
  356. package/skills/plan/prd-feature-template.md +19 -0
  357. package/skills/plan/prd-full-template.md +12 -5
  358. package/skills/plan/prd-premortem-panel.md +48 -0
  359. package/skills/plan/prd-reviewer-prompt.md +11 -3
  360. package/skills/plan/soul.md +1 -1
  361. package/skills/portfolio/SKILL.md +5 -6
  362. package/skills/quality-gates/SKILL.md +2 -4
  363. package/skills/reconcile/SKILL.md +27 -4
  364. package/skills/release/SKILL.md +16 -8
  365. package/skills/session-end/SKILL.md +97 -13
  366. package/skills/session-end/phase-3-2-docs-verification.md +1 -0
  367. package/skills/session-end/phase-3-6-tail.md +11 -7
  368. package/skills/session-end/phase-3-7a-recommendations.md +9 -2
  369. package/skills/session-end/plan-verification.md +16 -1
  370. package/skills/session-end/references/phase-3-7e-vault-orphans.md +63 -0
  371. package/skills/session-end/references/phase-3-documentation-updates.md +25 -13
  372. package/skills/session-end/references/phase-5-issue-cleanup.md +1 -1
  373. package/skills/session-end/references/session-summary-template.md +8 -0
  374. package/skills/session-end/session-metrics-write.md +7 -4
  375. package/skills/session-plan/SKILL.md +5 -3
  376. package/skills/session-start/SKILL.md +10 -4
  377. package/skills/session-start/phase-4-5-resource-health.md +11 -5
  378. package/skills/session-start/phase-7-5-mode-selector.md +3 -1
  379. package/skills/session-start/phase-8-5-express-path.md +4 -3
  380. package/skills/session-start/references/phase-1-2-session-lock.md +7 -5
  381. package/skills/session-start/references/phase-1-5-session-continuity.md +13 -5
  382. package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
  383. package/skills/session-start/references/phase-4-ssot-environment-check.md +3 -2
  384. package/skills/tmux-layout/SKILL.md +2 -2
  385. package/skills/vault-sync/validator.mjs +3 -2
  386. package/skills/wave-executor/SKILL.md +5 -4
  387. package/skills/wave-executor/references/wave-executor-state-init.md +3 -3
  388. package/skills/wave-executor/references/wave-loop-dispatch.md +11 -5
  389. package/skills/wave-executor/references/wave-loop-review.md +13 -5
  390. package/skills/wave-executor/references/wave-loop-scope-manifest.md +1 -1
  391. package/AGENTS.md +0 -207
  392. package/assets/og-card.png +0 -0
  393. package/assets/og-card.svg +0 -47
  394. package/assets/wave-lifecycle.svg +0 -98
@@ -15,6 +15,10 @@
15
15
  * Then reads the navigator lease and emits `orchestrator.fleet.checkin` with the
16
16
  * payload `{ session, repo, modus, kandidaten, navigator_state }` — no paths, no
17
17
  * quota, no `auftrag_ref`.
18
+ * Without fleet integration, valid input exits 0 with `skipped: "no-fleet"` and no directory, file or event.
19
+ * That holds for `plattform: "kopflos"` too: the record cannot prove a coordinator started the run,
20
+ * and a write would create the navigator directory, which then flips every later solo session to
21
+ * fleet. A headless run without fleet integration applies the Standard-Auflagen instead (#1559 review).
18
22
  *
19
23
  * stdout: one JSON line
20
24
  * `{ ok, path, navigator_state, navigator_adresse, fallback_due_at, event }`.
@@ -29,7 +33,7 @@
29
33
  * Exit codes:
30
34
  * 0 — check-in written (an event failure only WARNs; `event: false` on stdout)
31
35
  * 1 — write error
32
- * 2 — invalid input (not JSON, not an object, or validation errors; no file)
36
+ * 2 — invalid input or semantic session ID occupied by another repo (no write/event)
33
37
  */
34
38
 
35
39
  import { randomBytes } from 'node:crypto';
@@ -39,6 +43,7 @@ import path from 'node:path';
39
43
  import { emitEvent } from './lib/events.mjs';
40
44
  import {
41
45
  checkinPath,
46
+ fleetIntegrationEnabled,
42
47
  fallbackDueAt,
43
48
  navigatorDir,
44
49
  readNavigatorLease,
@@ -46,6 +51,7 @@ import {
46
51
  validateCheckin,
47
52
  } from './lib/fleet-protocol.mjs';
48
53
  import { isMainModule } from './lib/is-main-module.mjs';
54
+ import { openNoFollow } from './lib/no-follow-open.mjs';
49
55
 
50
56
  const PREFIX = 'fleet-checkin:';
51
57
 
@@ -63,8 +69,14 @@ async function readStdin() {
63
69
  * planted symlink cannot redirect the chmod or the write.
64
70
  * @param {string} target
65
71
  * @param {object} record
72
+ * @param {object} [options]
73
+ * @param {typeof openNoFollow} [options.open] opener for the ownership read.
74
+ * Injectable only so tests can reach the fail-closed `ERR_FILE_CHANGED`
75
+ * branch, which `openNoFollow` never takes on POSIX; production passes nothing.
76
+ * @returns {Promise<boolean>} false when another repo already owns the target,
77
+ * or the target changed during the ownership check
66
78
  */
67
- async function writeCheckinFile(target, record) {
79
+ export async function writeCheckinFile(target, record, { open = openNoFollow } = {}) {
68
80
  const checkinDir = path.dirname(target);
69
81
  await fs.mkdir(navigatorDir(), { recursive: true, mode: 0o700 });
70
82
  await fs.mkdir(checkinDir, { recursive: true, mode: 0o700 });
@@ -79,7 +91,52 @@ async function writeCheckinFile(target, record) {
79
91
  await fs.writeFile(tmp, JSON.stringify(record, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
80
92
  // The creation mode is filtered through the umask; pin it explicitly.
81
93
  await fs.chmod(tmp, 0o600);
94
+ let existing;
95
+ try {
96
+ // Never follow a symlink at the target and never block on a FIFO; the
97
+ // helper also covers platforms without O_NOFOLLOW (win32, #1744).
98
+ const handle = await open(target);
99
+ try {
100
+ const stat = await handle.stat();
101
+ const limit = 64 * 1024;
102
+ // Check-ins are small: non-files and larger targets are unreadable for
103
+ // this guard and fail-open. Bound reads even if the file grows after stat.
104
+ if (stat.isFile() && stat.size <= limit) {
105
+ const buffer = Buffer.alloc(limit);
106
+ let length = 0;
107
+ while (length < limit) {
108
+ const { bytesRead } = await handle.read(buffer, length, limit - length, length);
109
+ if (bytesRead === 0) break;
110
+ length += bytesRead;
111
+ }
112
+ if ((await handle.stat()).size <= limit) {
113
+ existing = JSON.parse(buffer.subarray(0, length).toString('utf8'));
114
+ }
115
+ }
116
+ } finally {
117
+ await handle.close();
118
+ }
119
+ } catch (err) {
120
+ // A target swapped during the check may hide a foreign check-in: fail closed.
121
+ if (err?.code === 'ERR_FILE_CHANGED') {
122
+ await fs.rm(tmp, { force: true });
123
+ process.stderr.write(`${PREFIX} check-in target ${path.basename(target)} changed during the safety check; refusing to overwrite\n`);
124
+ return false;
125
+ }
126
+ // Fail-open otherwise: a missing (first write), symlinked, non-regular,
127
+ // unreadable or invalid-JSON target gives the navigator no readable
128
+ // check-in to preserve. `rename` replaces a symlink itself, never follows it.
129
+ }
130
+ if (typeof existing?.repo === 'string' && existing.repo !== record.repo) {
131
+ await fs.rm(tmp, { force: true });
132
+ process.stderr.write(`${PREFIX} semantic session ID ${record.session} is already occupied by another repo: ${JSON.stringify(existing.repo)}\n`);
133
+ return false;
134
+ }
135
+ // BV-004 ceiling: protects an already-visible foreign check-in, not concurrent
136
+ // writers between read and rename; revisit with atomic coordination when a
137
+ // simultaneous cross-repo collision is observed.
82
138
  await fs.rename(tmp, target);
139
+ return true;
83
140
  } catch (err) {
84
141
  await fs.rm(tmp, { force: true }).catch(() => {});
85
142
  throw err;
@@ -107,9 +164,22 @@ async function main() {
107
164
  return 2;
108
165
  }
109
166
 
167
+ if (!fleetIntegrationEnabled()) {
168
+ process.stdout.write(JSON.stringify({
169
+ ok: true,
170
+ skipped: 'no-fleet',
171
+ path: null,
172
+ navigator_state: 'none',
173
+ navigator_adresse: null,
174
+ fallback_due_at: null,
175
+ event: false,
176
+ }) + '\n');
177
+ return 0;
178
+ }
179
+
110
180
  const target = checkinPath(record.session);
111
181
  try {
112
- await writeCheckinFile(target, record);
182
+ if (!await writeCheckinFile(target, record)) return 2;
113
183
  } catch (err) {
114
184
  process.stderr.write(`${PREFIX} write failed: ${err instanceof Error ? err.message : String(err)}\n`);
115
185
  return 1;
@@ -27,6 +27,7 @@ import path from 'node:path';
27
27
 
28
28
  import { validateWorkspacePath } from './lib/worktree/lifecycle.mjs';
29
29
  import { isMainModule } from './lib/is-main-module.mjs';
30
+ import { resolveEventsLedgerRoot } from './lib/platform.mjs';
30
31
 
31
32
  // ---------------------------------------------------------------------------
32
33
  // Constants
@@ -79,7 +80,11 @@ export function isPidAlive(pid) {
79
80
  */
80
81
  function loadRecentSessionRefs(repoRoot) {
81
82
  const refs = new Set();
82
- const sessionsPath = path.join(repoRoot, SESSIONS_JSONL);
83
+ // #1592: the repo's ONE ledger (main checkout from a linked worktree, which
84
+ // has none since #1584) — reading the worktree's would drop every "kept"
85
+ // reference and let GC remove a worktree a recent session still names.
86
+ // The worktree-meta dir stays per checkout: it is tmp state, not the ledger.
87
+ const sessionsPath = path.join(resolveEventsLedgerRoot(repoRoot), SESSIONS_JSONL);
83
88
 
84
89
  let raw;
85
90
  try {
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * github-export.mjs — build the curated public GitHub snapshot (`mirror: curated`, #1631).
4
+ *
5
+ * Usage:
6
+ * node scripts/github-export.mjs [--source <rev>] [--dry-run]
7
+ * node scripts/github-export.mjs --verify <export sha> --source <source sha>
8
+ * node scripts/github-export.mjs --plain-guard --mirror-key <github|unset>
9
+ *
10
+ * The parent is ALWAYS the public GitHub main: `refs/remotes/github/main`, required to
11
+ * equal `git ls-remote github refs/heads/main` (one read-only network call). There is
12
+ * deliberately no parent override, and refs/heads/github-export is only the output slot.
13
+ *
14
+ * Writes ONE snapshot commit to refs/heads/github-export (see scripts/lib/github-export.mjs);
15
+ * session-end then pushes it to github main (skills/session-end/SKILL.md § 4.4).
16
+ *
17
+ * Exit codes:
18
+ * 0 snapshot created (stdout: new sha) — or, with --dry-run, stats printed
19
+ * 3 nothing to publish: the curated tree equals github main's (stdout: that sha;
20
+ * refs/heads/github-export is not touched)
21
+ * 1 refused, one-line reason on stderr (replace refs or grafts present, github main
22
+ * not verifiable or tracking ref stale, source not descended from the published
23
+ * snapshot's source, excluded path survived, leakage scan failed, a public file
24
+ * still cites an excluded path, push URL differs from fetch URL, compare-and-swap
25
+ * lost) — or a usage error
26
+ *
27
+ * --verify re-checks an already built snapshot BY SHA right before session-end pushes
28
+ * that sha: graph integrity, push URL == fetch URL, github main verified via ls-remote,
29
+ * exactly one commit on it with `Source-Commit: <source>`, no excluded path in its tree.
30
+ * Exit 0 ok, 1 refused. stdout stays empty in every mode except the sha lines above.
31
+ *
32
+ * --plain-guard answers whether a PLAIN push of source history (`git push github HEAD`)
33
+ * is allowed: refused (exit 1, reason on stderr) when HEAD or origin's main carries
34
+ * .orchestrator/policy/public-export.json, i.e. the repo has adopted the curated export
35
+ * even if this checkout's Session Config predates `mirror: curated` (#1645), and — without
36
+ * an `origin` remote to consult — unless `--mirror-key github` (explicit `mirror: github`).
37
+ * Exit 0 allowed.
38
+ */
39
+
40
+ import { spawnSync } from 'node:child_process';
41
+
42
+ import { buildGithubExport, createGitRunner, plainPushGuard, verifyExportCommit } from './lib/github-export.mjs';
43
+
44
+ function usage(msg) {
45
+ if (msg) console.error(`github-export: ${msg}`);
46
+ console.error('Usage: node scripts/github-export.mjs [--source <rev>] [--dry-run] | --verify <sha> --source <sha> | --plain-guard --mirror-key <github|unset>');
47
+ process.exit(1);
48
+ }
49
+
50
+ const opts = { source: 'HEAD', dryRun: false };
51
+ let verifySha = null;
52
+ let plainGuard = false;
53
+ let mirrorKey = null;
54
+ const args = process.argv.slice(2);
55
+ for (let i = 0; i < args.length; i++) {
56
+ const a = args[i];
57
+ if (a === '--dry-run') opts.dryRun = true;
58
+ else if (a === '--plain-guard') plainGuard = true;
59
+ else if (a === '--mirror-key') {
60
+ mirrorKey = args[++i];
61
+ if (mirrorKey !== 'github' && mirrorKey !== 'unset') usage('--mirror-key must be github or unset');
62
+ }
63
+ else if (a === '--source' || a === '--verify') {
64
+ const v = args[++i];
65
+ if (!v || v.startsWith('--')) usage(`${a} requires a value`);
66
+ if (a === '--source') opts.source = v;
67
+ else verifySha = v;
68
+ } else if (a === '--help' || a === '-h') usage();
69
+ else usage(`unknown argument '${a}'`);
70
+ }
71
+
72
+ const top = spawnSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' });
73
+ if (top.status !== 0) {
74
+ console.error('github-export: refused — not inside a git work tree');
75
+ process.exit(1);
76
+ }
77
+
78
+ const git = createGitRunner(top.stdout.trim());
79
+
80
+ if (plainGuard) {
81
+ if (args.length !== 3 || mirrorKey === null) usage('--plain-guard takes exactly --mirror-key <github|unset>');
82
+ const verdict = plainPushGuard({ git, explicitGithub: mirrorKey === 'github' });
83
+ if (!verdict.ok) {
84
+ console.error(`github-export: plain push refused — ${verdict.reason}`);
85
+ process.exit(1);
86
+ }
87
+ process.exit(0);
88
+ }
89
+
90
+ if (verifySha !== null) {
91
+ if (opts.dryRun) usage('--verify does not combine with --dry-run');
92
+ const verdict = verifyExportCommit({ git, sha: verifySha, source: opts.source });
93
+ if (!verdict.ok) {
94
+ console.error(`github-export: verify refused — ${verdict.reason}`);
95
+ process.exit(1);
96
+ }
97
+ process.exit(0);
98
+ }
99
+
100
+ const result = buildGithubExport({ git, ...opts });
101
+
102
+ if (result.stats) {
103
+ const s = result.stats;
104
+ console.error(
105
+ `github-export: source files ${s.sourceFiles}, excluded ${s.excludedFiles}, snapshot files ${s.snapshotFiles}, changed vs parent ${s.changedVsParent}`,
106
+ );
107
+ }
108
+ for (const line of result.findings ?? []) console.error(line);
109
+ for (const line of result.warnings ?? []) console.error(`github-export: WARN — ${line} (a real export would refuse)`);
110
+
111
+ switch (result.status) {
112
+ case 'created':
113
+ console.log(result.sha);
114
+ process.exit(0);
115
+ break;
116
+ case 'unchanged':
117
+ console.log(result.sha);
118
+ console.error('github-export: nothing to publish — snapshot tree equals github main, no new commit');
119
+ process.exit(3);
120
+ break;
121
+ case 'dry-run':
122
+ console.error(`github-export: dry-run — ${result.stats?.unchanged ? 'tree unchanged' : 'snapshot built and scanned'}, refs/heads/github-export not updated`);
123
+ process.exit(0);
124
+ break;
125
+ default:
126
+ console.error(`github-export: refused — ${result.reason}`);
127
+ process.exit(1);
128
+ }
@@ -9,7 +9,9 @@
9
9
  * the DEPLOYING path is the SAME as the REVIEWING path. This repo's GitHub
10
10
  * mirror breaks that: `origin` (GitLab) is reviewed via MR, `github` is
11
11
  * pushed directly by `skills/session-end/SKILL.md`'s mirror-push block
12
- * (`git push github HEAD`, no MR, no review) — and since a Vercel Git
12
+ * (`git push github HEAD`, or with `mirror: curated` (#1631) one verified
13
+ * snapshot `git push github <sha>:refs/heads/main`, published only through
14
+ * /close; no MR, no review either way) — and since a Vercel Git
13
15
  * integration deploys on every push to that mirror's `main`, a compromised
14
16
  * mirror token or an admin bypass IS a deploy with zero review. See
15
17
  * `docs/github-mirror-protection.md` for the full runbook.
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * handover-queue-check.mjs — every open point of a fleet handover carries a
4
+ * queue mark before the session reports "Bereit zum Schließen." (#1639).
5
+ *
6
+ * Callers: `skills/_shared/fleet-protocol.md` § Übergabe und Zug-Manifest
7
+ * Schritt 1a (the waiting turn that writes the file) and session-end § 4.5
8
+ * (re-check at `/close`). Parsing lives in `lib/handover-queue-marks.mjs`.
9
+ *
10
+ * Usage:
11
+ * node scripts/handover-queue-check.mjs (--file <path> | --fleet-dir <dir> --since <iso>)
12
+ * [--name <name>] [--apply --repo <repo> [--navigator-cli <path>]] [--json]
13
+ *
14
+ * --file the handover or train manifest to check
15
+ * --fleet-dir/--since newest of uebergabe-<name>.md, uebergabe-<name>-*.md,
16
+ * <name>-zug-*.md in <dir> modified at or after <iso> or
17
+ * epoch ms (the session's start, e.g. `startedAt` of its
18
+ * ~/.claude/sessions entry): session names repeat across days, so an
19
+ * older file of the same name is a predecessor's (#1639 F1)
20
+ * --name the session name; queue titles read `<name>: <point>`
21
+ * (required with --fleet-dir and with --apply)
22
+ * --apply file each unmarked point via `owner-queue add --json`
23
+ * and write `queue:#<id>` into the file; points under an
24
+ * owner/decision heading are reported, never filed. Only
25
+ * for an own file (the --fleet-dir name pattern for
26
+ * --name); a foreign file is refused untouched (#1647).
27
+ * Check mode without --apply reads any file.
28
+ * --repo owner-queue repo name (required with --apply)
29
+ * --navigator-cli coordinator CLI: a `.js` path runs under node, anything
30
+ * else as a binary; default `navigator` from PATH
31
+ *
32
+ * Exit: 0 every open point marked (or no fleet integration: nothing to do) ·
33
+ * 3 unmarked points remain (listed on stdout) · 2 usage error ·
34
+ * 4 --apply on a file that is not the session's own — foreign name, a
35
+ * symlink, or older than --since (nothing filed or written).
36
+ */
37
+
38
+ import { spawnSync } from 'node:child_process';
39
+ import { lstatSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
40
+ import { basename, join } from 'node:path';
41
+ import { fleetIntegrationEnabled } from './lib/fleet-protocol.mjs';
42
+ import { findOpenPoints, insertMarks, kindFor, refFor, unmarkedPoints } from './lib/handover-queue-marks.mjs';
43
+
44
+ const EXIT_OK = 0;
45
+ const EXIT_USAGE = 2;
46
+ const EXIT_UNMARKED = 3;
47
+ const EXIT_FOREIGN = 4;
48
+
49
+ function parseArgs(argv) {
50
+ const out = { apply: false, json: false };
51
+ for (let i = 0; i < argv.length; i++) {
52
+ const a = argv[i];
53
+ if (a === '--apply') out.apply = true;
54
+ else if (a === '--json') out.json = true;
55
+ else if (['--file', '--name', '--fleet-dir', '--since', '--repo', '--navigator-cli'].includes(a)) {
56
+ const v = argv[++i];
57
+ if (v === undefined || v.startsWith('--')) throw new Error(`${a} needs a value`);
58
+ out[a.slice(2).replace(/-([a-z])/g, (_, c) => c.toUpperCase())] = v;
59
+ } else throw new Error(`unknown argument: ${a}`);
60
+ }
61
+ if (!out.file && !(out.name && out.fleetDir && out.since)) {
62
+ throw new Error('need --file, or --fleet-dir with --name and --since');
63
+ }
64
+ if (out.since !== undefined) {
65
+ // ISO date, or epoch ms as the session registry's `startedAt` carries it.
66
+ out.sinceMs = /^\d+$/.test(out.since) ? Number(out.since) : Date.parse(out.since);
67
+ if (Number.isNaN(out.sinceMs)) throw new Error(`--since is not a date: ${out.since}`);
68
+ }
69
+ if (out.apply && !out.repo) throw new Error('--apply needs --repo');
70
+ if (out.apply && !out.name) throw new Error('--apply needs --name');
71
+ return out;
72
+ }
73
+
74
+ /**
75
+ * True when basename `f` is a handover or train file of session `name`:
76
+ * `uebergabe-<name>.md`, `uebergabe-<name>-*.md` or `<name>-zug-*.md`. The
77
+ * hyphen after `<name>` keeps `uebergabe-foo2-*.md` out of `foo`'s files.
78
+ */
79
+ function isOwnHandover(f, name) {
80
+ return (
81
+ f.endsWith('.md') &&
82
+ (f === `uebergabe-${name}.md` || f.startsWith(`uebergabe-${name}-`) || f.startsWith(`${name}-zug-`))
83
+ );
84
+ }
85
+
86
+ /** Newest handover/train file of `name` in `dir` modified at/after `sinceMs`, or null. */
87
+ function newestHandover(dir, name, sinceMs) {
88
+ let names;
89
+ try {
90
+ names = readdirSync(dir);
91
+ } catch {
92
+ return null;
93
+ }
94
+ const own = names.filter((f) => isOwnHandover(f, name));
95
+ const withTime = own.map((f) => ({ f, t: statSync(join(dir, f)).mtimeMs })).filter((x) => x.t >= sinceMs);
96
+ withTime.sort((a, b) => b.t - a.t);
97
+ return withTime.length ? join(dir, withTime[0].f) : null;
98
+ }
99
+
100
+ /** `owner-queue add --json` → queue id (exit 0: new, exit 4: existing), or an error string. */
101
+ function fileQueueItem(cli, { repo, kind, title, ref }) {
102
+ const args = ['--json', 'owner-queue', 'add', '--repo', repo, '--kind', kind, '--waits-on', 'lead', '--title', title];
103
+ if (ref) args.push('--ref', ref);
104
+ const [cmd, argv] = cli.endsWith('.js') ? [process.execPath, [cli, ...args]] : [cli, args];
105
+ const r = spawnSync(cmd, argv, { encoding: 'utf8', timeout: 30_000 });
106
+ if (r.error) return { error: r.error.code ?? r.error.message };
107
+ let parsed;
108
+ try {
109
+ parsed = JSON.parse((r.stdout || '').trim().split('\n').pop() || 'null');
110
+ } catch {
111
+ parsed = null;
112
+ }
113
+ if (r.status === 0 && Number.isInteger(parsed?.id)) return { id: parsed.id };
114
+ if (r.status === 4 && Number.isInteger(parsed?.ids?.[0])) return { id: parsed.ids[0] };
115
+ return { error: `owner-queue add exit ${r.status}` };
116
+ }
117
+
118
+ function main() {
119
+ let opts;
120
+ try {
121
+ opts = parseArgs(process.argv.slice(2));
122
+ } catch (err) {
123
+ process.stderr.write(`handover-queue-check: ${err.message}\n`);
124
+ return EXIT_USAGE;
125
+ }
126
+ const say = (obj, text) => process.stdout.write(opts.json ? `${JSON.stringify(obj)}\n` : `${text}\n`);
127
+
128
+ if (!fleetIntegrationEnabled()) {
129
+ say({ fleet: false, unmarked: 0 }, 'handover-queue-check: keine Flottenintegration — nichts zu prüfen');
130
+ return EXIT_OK;
131
+ }
132
+ const file = opts.file ?? newestHandover(opts.fleetDir, opts.name, opts.sinceMs);
133
+ if (!file) {
134
+ say({ fleet: true, file: null, unmarked: 0 }, 'handover-queue-check: keine Übergabe-Datei gefunden');
135
+ return EXIT_OK;
136
+ }
137
+ // --apply writes into the file and files queue items titled with --name:
138
+ // only the session's own file qualifies (#1647: a fleet-wide loop over
139
+ // --file marked 17 foreign handovers).
140
+ // The name alone is not enough: a symlink named like an own file writes
141
+ // through to its target, and session names repeat across days, so with
142
+ // --since an older file of the same name is a predecessor's (#1639 F1).
143
+ if (opts.apply) {
144
+ let st = null;
145
+ try {
146
+ st = lstatSync(file);
147
+ } catch {
148
+ // unreadable: the read below reports it
149
+ }
150
+ const reason = !isOwnHandover(basename(file), opts.name)
151
+ ? 'fremde Datei'
152
+ : st?.isSymbolicLink()
153
+ ? 'Symlink'
154
+ : st && opts.sinceMs !== undefined && st.mtimeMs < opts.sinceMs
155
+ ? 'Datei älter als --since (Vorgänger gleichen Namens)'
156
+ : null;
157
+ if (reason) {
158
+ process.stderr.write(`handover-queue-check: ${reason}: ${basename(file)} — --apply nur für die eigene Übergabe von ${opts.name}\n`);
159
+ return EXIT_FOREIGN;
160
+ }
161
+ }
162
+ let content;
163
+ try {
164
+ content = readFileSync(file, 'utf8');
165
+ } catch (err) {
166
+ process.stderr.write(`handover-queue-check: ${file} unlesbar (${err.code ?? err.message})\n`);
167
+ return EXIT_USAGE;
168
+ }
169
+
170
+ let open = unmarkedPoints(findOpenPoints(content));
171
+ const filed = [];
172
+ const failed = [];
173
+ if (opts.apply && open.length > 0) {
174
+ const cli = opts.navigatorCli ?? 'navigator';
175
+ const ids = new Map();
176
+ for (const p of open) {
177
+ const kind = kindFor(p);
178
+ if (kind === null) {
179
+ failed.push({ line: p.line + 1, error: 'Owner-Entscheid: mit --options/--class selbst anlegen' });
180
+ continue;
181
+ }
182
+ // The CLI shortens titles over 200 code units itself (cleanly, with a
183
+ // WARN); slicing here could split a surrogate pair.
184
+ const title = `${opts.name}: ${p.text}`;
185
+ const r = fileQueueItem(cli, { repo: opts.repo, kind, title, ref: refFor(p, opts.repo) });
186
+ if (r.id !== undefined) {
187
+ ids.set(p.line, r.id);
188
+ filed.push({ line: p.line + 1, id: r.id });
189
+ } else {
190
+ failed.push({ line: p.line + 1, error: r.error });
191
+ }
192
+ }
193
+ if (ids.size > 0) {
194
+ content = insertMarks(content, ids);
195
+ writeFileSync(file, content);
196
+ }
197
+ open = open.filter((p) => !ids.has(p.line));
198
+ }
199
+
200
+ const unmarked = open.map((p) => ({ line: p.line + 1, section: p.section, text: p.text }));
201
+ const lines = [`handover-queue-check: ${basename(file)} — ${unmarked.length} Punkt(e) ohne Queue-ID`];
202
+ for (const f of filed) lines.push(` angelegt: Zeile ${f.line} → queue:#${f.id}`);
203
+ for (const u of unmarked) lines.push(` offen: Zeile ${u.line} [${u.section}] ${u.text}`);
204
+ for (const f of failed) lines.push(` Anlegen gescheitert: Zeile ${f.line} (${f.error})`);
205
+ say({ fleet: true, file, unmarked: unmarked.length, filed, failed, points: unmarked }, lines.join('\n'));
206
+ return unmarked.length > 0 ? EXIT_UNMARKED : EXIT_OK;
207
+ }
208
+
209
+ process.exitCode = main();
@@ -15,7 +15,7 @@
15
15
  */
16
16
 
17
17
  import { existsSync, readFileSync, mkdirSync, appendFileSync } from 'node:fs';
18
- import { join } from 'node:path';
18
+ import { dirname, join } from 'node:path';
19
19
  import { execFileSync } from 'node:child_process';
20
20
 
21
21
  import {
@@ -29,6 +29,7 @@ import {
29
29
  runCategory8,
30
30
  runCategory9,
31
31
  } from './lib/harness-audit/categories.mjs';
32
+ import { resolveLedgerPath } from './lib/ledger-path.mjs';
32
33
 
33
34
  const RUBRIC_VERSION = '2026-06';
34
35
 
@@ -193,11 +194,12 @@ function writeSummary(categories, summary) {
193
194
  // ---------------------------------------------------------------------------
194
195
 
195
196
  function appendAuditRecord(root, record) {
196
- const metricsDir = join(root, '.orchestrator/metrics');
197
+ // #1722: main checkout's copy when root is a linked worktree that gitignores it.
198
+ const ledger = resolveLedgerPath('.orchestrator/metrics/audit.jsonl', root);
197
199
  try {
198
- mkdirSync(metricsDir, { recursive: true });
200
+ mkdirSync(dirname(ledger), { recursive: true });
199
201
  const line = JSON.stringify(record) + '\n';
200
- appendFileSync(join(metricsDir, 'audit.jsonl'), line, 'utf8');
202
+ appendFileSync(ledger, line, 'utf8');
201
203
  } catch (err) {
202
204
  process.stderr.write(`harness-audit: Failed to append audit.jsonl: ${err.message}\n`);
203
205
  }
@@ -31,6 +31,35 @@ const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
31
31
  export const ALLOWED_MODEL_ALIASES = new Set(['inherit', 'sonnet', 'opus', 'haiku', 'fable']);
32
32
  export const MODEL_ID_RE = /^claude-(opus|sonnet|haiku|fable)-\d+(-\d+)?(-\d{8})?$/;
33
33
 
34
+ // Reasoning effort per https://code.claude.com/docs/en/sub-agents (#1614).
35
+ // REQUIRED whenever `model` is not `inherit`: the per-model default differs
36
+ // (Sonnet 5.5 defaults to `high`, Opus 5.5 to `medium`), so a pinned model
37
+ // without an explicit effort silently thinks longer than the role needs.
38
+ // With `model: inherit` the agent follows the session, effort included.
39
+ export const ALLOWED_EFFORTS = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
40
+
41
+ /**
42
+ * The effort rule shared by this validator and check-agents.mjs (#1614).
43
+ *
44
+ * @param {unknown} model - the frontmatter `model` value
45
+ * @param {unknown} effort - the frontmatter `effort` value (undefined when absent)
46
+ * @returns {{ rule: 'required'|'enum', message: string } | null} null when valid
47
+ */
48
+ export function checkAgentEffort(model, effort) {
49
+ const effortList = [...ALLOWED_EFFORTS].join('|');
50
+ if (effort === undefined || effort === null || effort === '') {
51
+ if (model === undefined || model === '' || model === 'inherit') return null;
52
+ return {
53
+ rule: 'required',
54
+ message: `effort is required when model is not inherit (got model ${JSON.stringify(model)}); set effort: ${effortList}`,
55
+ };
56
+ }
57
+ if (!ALLOWED_EFFORTS.has(/** @type {string} */ (effort))) {
58
+ return { rule: 'enum', message: `effort must be one of ${effortList} (got ${JSON.stringify(effort)})` };
59
+ }
60
+ return null;
61
+ }
62
+
34
63
  // Canonical Anthropic palette (red|blue|green|yellow|purple|orange|pink|cyan)
35
64
  // plus magenta from plugin-dev SKILL.md for backward-compat.
36
65
  const ALLOWED_COLORS = new Set(['blue', 'cyan', 'green', 'yellow', 'magenta', 'red', 'purple', 'orange', 'pink']);
@@ -129,8 +158,14 @@ export function parseAgentFrontmatter(contents) {
129
158
  /**
130
159
  * Validates a parsed frontmatter object against CLAUDE.md Agent Authoring Rules.
131
160
  *
161
+ * `warnings` is present only when non-empty and never flips `ok`. A missing
162
+ * `effort` on a pinned model is a warning here, because this validator also
163
+ * reads consumer `.claude/agents` (discovery probe); the plugin's own agents
164
+ * fail on it in check-agents.mjs. A set but invalid `effort` stays an error.
165
+ *
132
166
  * @param {Record<string,string>|null|undefined} frontmatter
133
- * @returns {{ ok: true }|{ ok: false, errors: Array<{path: string, rule: string, message: string}> }}
167
+ * @returns {{ ok: true, warnings?: Array<{path: string, rule: string, message: string}> }
168
+ * |{ ok: false, errors: Array<{path: string, rule: string, message: string}>, warnings?: Array<{path: string, rule: string, message: string}> }}
134
169
  */
135
170
  export function validateAgentFrontmatter(frontmatter) {
136
171
  if (frontmatter === null || frontmatter === undefined || typeof frontmatter !== 'object') {
@@ -141,6 +176,7 @@ export function validateAgentFrontmatter(frontmatter) {
141
176
  }
142
177
 
143
178
  const errors = /** @type {Array<{path: string, rule: string, message: string}>} */ ([]);
179
+ const warnings = /** @type {Array<{path: string, rule: string, message: string}>} */ ([]);
144
180
 
145
181
  // --- name ---
146
182
  const name = frontmatter['name'];
@@ -171,7 +207,7 @@ export function validateAgentFrontmatter(frontmatter) {
171
207
  path: 'description',
172
208
  rule: 'no-block-scalar',
173
209
  message:
174
- 'description must be an inline single-line string, not a YAML block scalar (> or |). Put <example> blocks inline.',
210
+ 'description must be an inline single-line string, not a YAML block scalar (> or |). Keep it to the routing sentence; put <example> blocks in a body `## Examples` section.',
175
211
  });
176
212
  }
177
213
 
@@ -191,6 +227,20 @@ export function validateAgentFrontmatter(frontmatter) {
191
227
  });
192
228
  }
193
229
 
230
+ // --- effort (#1614): missing on a pinned model = warning, invalid value = error ---
231
+ const effortError = checkAgentEffort(model, frontmatter['effort']);
232
+ if (effortError) (effortError.rule === 'required' ? warnings : errors).push({ path: 'effort', ...effortError });
233
+
234
+ // --- omitClaudeMd (optional boolean, #1616) ---
235
+ const omit = frontmatter['omitClaudeMd'];
236
+ if (omit !== undefined && omit !== 'true' && omit !== 'false' && omit !== true && omit !== false) {
237
+ errors.push({
238
+ path: 'omitClaudeMd',
239
+ rule: 'boolean',
240
+ message: `omitClaudeMd must be true or false (got ${JSON.stringify(omit)})`,
241
+ });
242
+ }
243
+
194
244
  // --- color ---
195
245
  const color = frontmatter['color'];
196
246
  if (color === undefined || color === '') {
@@ -244,7 +294,8 @@ export function validateAgentFrontmatter(frontmatter) {
244
294
  }
245
295
  }
246
296
 
247
- return errors.length > 0 ? { ok: false, errors } : { ok: true };
297
+ const warn = warnings.length > 0 ? { warnings } : {};
298
+ return errors.length > 0 ? { ok: false, errors, ...warn } : { ok: true, ...warn };
248
299
  }
249
300
 
250
301
  /**
@@ -253,8 +304,8 @@ export function validateAgentFrontmatter(frontmatter) {
253
304
  * Never throws. Returns a result object with the file path attached.
254
305
  *
255
306
  * @param {string} filePath - absolute or relative path to the agent .md file
256
- * @returns {{ ok: true, file: string }
257
- * |{ ok: false, file: string, errors: Array<{path: string, rule: string, message: string}> }}
307
+ * @returns {{ ok: true, file: string, warnings?: Array<{path: string, rule: string, message: string}> }
308
+ * |{ ok: false, file: string, errors: Array<{path: string, rule: string, message: string}>, warnings?: Array<{path: string, rule: string, message: string}> }}
258
309
  */
259
310
  export function validateAgentFile(filePath) {
260
311
  let contents;
@@ -280,9 +331,5 @@ export function validateAgentFile(filePath) {
280
331
  }
281
332
 
282
333
  const validation = validateAgentFrontmatter(parsed.frontmatter);
283
- if (!validation.ok) {
284
- return { ok: false, file: filePath, errors: validation.errors };
285
- }
286
-
287
- return { ok: true, file: filePath };
334
+ return { ...validation, file: filePath };
288
335
  }