session-orchestrator 3.23.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (393) hide show
  1. package/.agents/skills/architecture/SKILL.md +18 -0
  2. package/.agents/skills/autopilot/SKILL.md +17 -0
  3. package/.agents/skills/bootstrap/SKILL.md +20 -0
  4. package/.agents/skills/brainstorm/SKILL.md +22 -0
  5. package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
  6. package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
  7. package/.agents/skills/debug/SKILL.md +22 -0
  8. package/.agents/skills/discovery/SKILL.md +20 -0
  9. package/.agents/skills/dispatcher/SKILL.md +15 -0
  10. package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
  11. package/.agents/skills/ecosystem-health/SKILL.md +20 -0
  12. package/.agents/skills/eli5/SKILL.md +20 -0
  13. package/.agents/skills/eval/SKILL.md +21 -0
  14. package/.agents/skills/evolve/SKILL.md +21 -0
  15. package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
  16. package/.agents/skills/gitlab-ops/SKILL.md +20 -0
  17. package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
  18. package/.agents/skills/grill/SKILL.md +22 -0
  19. package/.agents/skills/hook-development/SKILL.md +15 -0
  20. package/.agents/skills/mcp-builder/SKILL.md +15 -0
  21. package/.agents/skills/memory-cleanup/SKILL.md +21 -0
  22. package/.agents/skills/mode-selector/SKILL.md +17 -0
  23. package/.agents/skills/npm-publish/SKILL.md +16 -0
  24. package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
  25. package/.agents/skills/persona-panel/SKILL.md +17 -0
  26. package/.agents/skills/plan/SKILL.md +20 -0
  27. package/.agents/skills/playwright-driver/SKILL.md +20 -0
  28. package/.agents/skills/quality-gates/SKILL.md +20 -0
  29. package/.agents/skills/reconcile/SKILL.md +21 -0
  30. package/.agents/skills/remote-offload/SKILL.md +20 -0
  31. package/.agents/skills/repo-audit/SKILL.md +16 -0
  32. package/.agents/skills/session-end/SKILL.md +20 -0
  33. package/.agents/skills/session-plan/SKILL.md +20 -0
  34. package/.agents/skills/session-start/SKILL.md +20 -0
  35. package/.agents/skills/spinout/SKILL.md +16 -0
  36. package/.agents/skills/sunset-review/SKILL.md +16 -0
  37. package/.agents/skills/test-runner/SKILL.md +20 -0
  38. package/.agents/skills/tmux-layout/SKILL.md +21 -0
  39. package/.agents/skills/using-orchestrator/SKILL.md +17 -0
  40. package/.agents/skills/vault-mirror/SKILL.md +15 -0
  41. package/.agents/skills/vault-sync/SKILL.md +15 -0
  42. package/.agents/skills/wave-executor/SKILL.md +20 -0
  43. package/.agents/skills/write-executable-plan/SKILL.md +22 -0
  44. package/.claude-plugin/marketplace.json +1 -1
  45. package/.claude-plugin/plugin.json +1 -1
  46. package/.codex-plugin/plugin.json +1 -1
  47. package/.cursor/commands/autopilot.md +2 -2
  48. package/.cursor/commands/bootstrap.md +1 -1
  49. package/.cursor/commands/brainstorm.md +1 -1
  50. package/.cursor/commands/debug.md +1 -1
  51. package/.cursor/commands/discovery.md +1 -1
  52. package/.cursor/commands/dispatcher.md +2 -2
  53. package/.cursor/commands/eli5.md +2 -2
  54. package/.cursor/commands/eval.md +2 -2
  55. package/.cursor/commands/evolve.md +1 -1
  56. package/.cursor/commands/go.md +1 -1
  57. package/.cursor/commands/grill.md +2 -2
  58. package/.cursor/commands/memory-cleanup.md +2 -2
  59. package/.cursor/commands/persona-panel.md +1 -1
  60. package/.cursor/commands/plan.md +1 -1
  61. package/.cursor/commands/portfolio.md +1 -1
  62. package/.cursor/commands/reconcile.md +2 -2
  63. package/.cursor/commands/release.md +2 -2
  64. package/.cursor/commands/session.md +2 -2
  65. package/.cursor/commands/spinout.md +2 -2
  66. package/.cursor/commands/sunset-review.md +2 -2
  67. package/.cursor/commands/templates-ack.md +2 -2
  68. package/.cursor/commands/test.md +2 -2
  69. package/.cursor/skills/brainstorm/SKILL.md +1 -1
  70. package/.cursor/skills/eval/SKILL.md +1 -1
  71. package/.cursor/skills/quality-gates/SKILL.md +1 -1
  72. package/.cursor/skills/remote-offload/SKILL.md +13 -0
  73. package/.orchestrator/policy/blocked-commands.json +121 -0
  74. package/.orchestrator/policy/ecosystem.schema.json +66 -0
  75. package/.orchestrator/policy/quality-gates.example.json +16 -0
  76. package/.orchestrator/policy/quality-gates.schema.json +38 -0
  77. package/.orchestrator/policy/templates-policy.json +27 -0
  78. package/.orchestrator/policy/test-profiles.json +47 -0
  79. package/AGENTS.md +225 -0
  80. package/CHANGELOG.md +1401 -0
  81. package/NOTICE +11 -6
  82. package/README.md +127 -92
  83. package/agents/db-specialist.md +0 -1
  84. package/agents/eval-judge.md +1 -1
  85. package/agents/skill-applied-judge.md +1 -1
  86. package/assets/wave-lifecycle.svg +98 -0
  87. package/commands/release.md +6 -3
  88. package/commands/session.md +18 -3
  89. package/docs/README.md +4 -0
  90. package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
  91. package/docs/baseline.md +67 -0
  92. package/docs/ci-setup.md +249 -48
  93. package/docs/codex-setup.md +66 -22
  94. package/docs/components.md +37 -16
  95. package/docs/cursor-setup.md +6 -2
  96. package/docs/events-schema.md +51 -10
  97. package/docs/instruction-delivery.md +62 -0
  98. package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
  99. package/docs/migration-v4.md +341 -0
  100. package/docs/pi-setup.md +6 -1
  101. package/docs/plugin-architecture-v3.md +1 -1
  102. package/docs/rule-authoring.md +85 -19
  103. package/docs/scope-collision-guard.md +8 -8
  104. package/docs/session-config-reference.md +120 -61
  105. package/docs/session-config-template.md +40 -33
  106. package/docs/telemetry/telemetry-claims.md +11 -10
  107. package/docs/telemetry.md +187 -4
  108. package/docs/vault-docs-architecture.md +50 -11
  109. package/hooks/_lib/atomic-json.mjs +111 -0
  110. package/hooks/_lib/hook-import-set.json +1487 -0
  111. package/hooks/_lib/subagent-paths.mjs +143 -0
  112. package/hooks/_lib/subagent-transcript.mjs +562 -0
  113. package/hooks/config-protection.mjs +2 -2
  114. package/hooks/cwd-change-restore.mjs +11 -31
  115. package/hooks/enforce-commands.mjs +69 -0
  116. package/hooks/enforce-scope.mjs +35 -6
  117. package/hooks/hooks-codex.json +1 -1
  118. package/hooks/hooks-cursor.json +10 -0
  119. package/hooks/hooks-pi.json +5 -0
  120. package/hooks/hooks.json +6 -1
  121. package/hooks/loop-guard.mjs +3 -3
  122. package/hooks/on-session-end.mjs +280 -14
  123. package/hooks/on-session-start.mjs +153 -4
  124. package/hooks/on-stop.mjs +371 -17
  125. package/hooks/operator-steer.mjs +2 -2
  126. package/hooks/post-bash-write-verify.mjs +189 -4
  127. package/hooks/post-edit-import-probe.mjs +344 -0
  128. package/hooks/post-subagent-discovery-validator.mjs +278 -392
  129. package/hooks/post-tool-batch-wave-signal.mjs +272 -44
  130. package/hooks/post-tool-failure-corrective-context.mjs +11 -34
  131. package/hooks/post-tooluse-frontend-slop.mjs +3 -3
  132. package/hooks/pre-bash-destructive-guard.mjs +39 -13
  133. package/hooks/pre-bash-memory-propose-audit.mjs +13 -7
  134. package/hooks/skill-invocation-telemetry.mjs +17 -5
  135. package/hooks/subagent-telemetry.mjs +24 -30
  136. package/monitors/monitors.json +3 -3
  137. package/package.json +9 -1
  138. package/pi/prompts/session.md +2 -2
  139. package/plugin.json +27 -0
  140. package/scripts/autopilot.mjs +26 -12
  141. package/scripts/backfill-abandoned-sessions.mjs +130 -15
  142. package/scripts/backfill-learnings-from-vault.mjs +9 -3
  143. package/scripts/dialectic-deriver.mjs +73 -8
  144. package/scripts/emit-event.mjs +10 -2
  145. package/scripts/export-hw-learnings.mjs +113 -1
  146. package/scripts/generate-agents-skills.mjs +378 -0
  147. package/scripts/generate-cursor-adapter.mjs +45 -8
  148. package/scripts/generate-hook-import-set.mjs +249 -0
  149. package/scripts/lib/agent-status.mjs +13 -2
  150. package/scripts/lib/auq/parse.mjs +5 -29
  151. package/scripts/lib/auto-dialectic.mjs +68 -0
  152. package/scripts/lib/auto-dream.mjs +38 -36
  153. package/scripts/lib/autonomy/suitability.mjs +6 -0
  154. package/scripts/lib/autopilot/loop.mjs +2 -2
  155. package/scripts/lib/autopilot/worktree-pipeline.mjs +82 -6
  156. package/scripts/lib/build-live-signals.mjs +25 -22
  157. package/scripts/lib/ci-status-banner.mjs +220 -75
  158. package/scripts/lib/codex/plugin-contract.mjs +82 -6
  159. package/scripts/lib/cold-start-detector.mjs +23 -14
  160. package/scripts/lib/config/auto-dream.mjs +2 -1
  161. package/scripts/lib/config/block-header.mjs +63 -0
  162. package/scripts/lib/config/block-preprocess.mjs +177 -0
  163. package/scripts/lib/config/broken-window.mjs +2 -1
  164. package/scripts/lib/config/cold-start.mjs +2 -1
  165. package/scripts/lib/config/config-protection.mjs +22 -2
  166. package/scripts/lib/config/context-coverage.mjs +2 -1
  167. package/scripts/lib/config/cross-repo.mjs +2 -1
  168. package/scripts/lib/config/custom-phases.mjs +2 -1
  169. package/scripts/lib/config/dialectic.mjs +2 -1
  170. package/scripts/lib/config/discovery-validator.mjs +9 -3
  171. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
  172. package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
  173. package/scripts/lib/config/docs-orchestrator.mjs +2 -1
  174. package/scripts/lib/config/docs-staleness.mjs +2 -1
  175. package/scripts/lib/config/drift-check.mjs +2 -1
  176. package/scripts/lib/config/eval.mjs +2 -1
  177. package/scripts/lib/config/events-rotation.mjs +2 -1
  178. package/scripts/lib/config/evolve.mjs +8 -2
  179. package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
  180. package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
  181. package/scripts/lib/config/handover-gate.mjs +2 -1
  182. package/scripts/lib/config/health-endpoints.mjs +388 -0
  183. package/scripts/lib/config/issue-budget.mjs +2 -1
  184. package/scripts/lib/config/loop-guard.mjs +2 -1
  185. package/scripts/lib/config/memory.mjs +2 -1
  186. package/scripts/lib/config/moc-staleness.mjs +2 -1
  187. package/scripts/lib/config/persona-gate-wave.mjs +2 -1
  188. package/scripts/lib/config/private-config-dir.mjs +67 -0
  189. package/scripts/lib/config/reconcile.mjs +2 -1
  190. package/scripts/lib/config/remote-hosts.mjs +234 -0
  191. package/scripts/lib/config/section-extractor.mjs +7 -1
  192. package/scripts/lib/config/skill-evolution.mjs +2 -1
  193. package/scripts/lib/config/slopcheck.mjs +2 -1
  194. package/scripts/lib/config/state-md-lock.mjs +2 -1
  195. package/scripts/lib/config/templates-first.mjs +2 -1
  196. package/scripts/lib/config/test.mjs +2 -1
  197. package/scripts/lib/config/vault-integration.mjs +7 -1
  198. package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
  199. package/scripts/lib/config/vault-staleness.mjs +2 -1
  200. package/scripts/lib/config/vault-sync.mjs +2 -1
  201. package/scripts/lib/config/verification-auto-fix.mjs +2 -1
  202. package/scripts/lib/config/wave-reviewers.mjs +2 -1
  203. package/scripts/lib/config/worktree-orphans.mjs +2 -1
  204. package/scripts/lib/config.mjs +31 -3
  205. package/scripts/lib/convergence-monitor.mjs +82 -16
  206. package/scripts/lib/dispatcher/enumerate.mjs +2 -17
  207. package/scripts/lib/dispatcher/rank.mjs +124 -48
  208. package/scripts/lib/ecosystem-health.mjs +16 -2
  209. package/scripts/lib/eval/engine.mjs +9 -1
  210. package/scripts/lib/eval/session-resolve.mjs +23 -4
  211. package/scripts/lib/events-schema.mjs +48 -0
  212. package/scripts/lib/events.mjs +256 -7
  213. package/scripts/lib/evolve/autonomy-verdict.mjs +9 -4
  214. package/scripts/lib/evolve/autopilot-effectiveness.mjs +18 -1
  215. package/scripts/lib/frontmatter-guard.mjs +131 -13
  216. package/scripts/lib/gates/gate-full.mjs +26 -0
  217. package/scripts/lib/gates/gate-helpers.mjs +76 -0
  218. package/scripts/lib/gitlab-portfolio/cli.mjs +3 -15
  219. package/scripts/lib/hardware-pattern-detector.mjs +18 -1
  220. package/scripts/lib/harness-audit/categories/category1.mjs +17 -6
  221. package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
  222. package/scripts/lib/host-identity.mjs +50 -11
  223. package/scripts/lib/instruction-budget-guard.mjs +171 -5
  224. package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
  225. package/scripts/lib/learnings/io.mjs +60 -6
  226. package/scripts/lib/memory-banner.mjs +20 -8
  227. package/scripts/lib/memory-proposals/store.mjs +30 -22
  228. package/scripts/lib/owner-config-banner.mjs +43 -6
  229. package/scripts/lib/owner-config-loader.mjs +21 -10
  230. package/scripts/lib/owner-interview.mjs +3 -3
  231. package/scripts/lib/owner-yaml.mjs +207 -14
  232. package/scripts/lib/peer-discovery.mjs +20 -2
  233. package/scripts/lib/platform.mjs +108 -15
  234. package/scripts/lib/plugin-update-banner.mjs +406 -0
  235. package/scripts/lib/project-hygiene.mjs +38 -2
  236. package/scripts/lib/qg-command-drift-banner.mjs +50 -12
  237. package/scripts/lib/quality-gate.mjs +133 -44
  238. package/scripts/lib/reconcile/emitter.mjs +68 -6
  239. package/scripts/lib/reconcile/engine.mjs +249 -9
  240. package/scripts/lib/reconcile/idempotency.mjs +37 -4
  241. package/scripts/lib/reconcile/writer.mjs +40 -18
  242. package/scripts/lib/scope-gate.mjs +36 -0
  243. package/scripts/lib/session-close-backfill.mjs +125 -18
  244. package/scripts/lib/session-discovery.mjs +57 -3
  245. package/scripts/lib/session-end/phase-skip.mjs +2 -2
  246. package/scripts/lib/session-id.mjs +12 -23
  247. package/scripts/lib/session-identity/own-session.mjs +187 -11
  248. package/scripts/lib/session-lock-shape.mjs +43 -0
  249. package/scripts/lib/session-lock.mjs +5 -10
  250. package/scripts/lib/session-registry.mjs +25 -9
  251. package/scripts/lib/session-schema/constants.mjs +36 -2
  252. package/scripts/lib/session-schema/validator.mjs +38 -4
  253. package/scripts/lib/session-start-probes.mjs +18 -1
  254. package/scripts/lib/session-transition.mjs +1 -1
  255. package/scripts/lib/sessions-canonical.mjs +446 -0
  256. package/scripts/lib/sessions-staleness-banner.mjs +18 -11
  257. package/scripts/lib/skill-health/join.mjs +17 -4
  258. package/scripts/lib/state-md.mjs +78 -0
  259. package/scripts/lib/sunset/walker.mjs +6 -0
  260. package/scripts/lib/telemetry/schema.mjs +255 -17
  261. package/scripts/lib/telemetry/sync.mjs +417 -24
  262. package/scripts/lib/tmux-layout/telemetry.mjs +14 -2
  263. package/scripts/lib/validate/check-agents-skills.mjs +327 -0
  264. package/scripts/lib/validate/check-agents.mjs +3 -3
  265. package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
  266. package/scripts/lib/validate/check-doc-cli-commands.mjs +9 -33
  267. package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +370 -0
  268. package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
  269. package/scripts/lib/validate/check-owner-leakage.mjs +281 -20
  270. package/scripts/lib/validate/check-skill-links.mjs +163 -0
  271. package/scripts/lib/validate/check-skill-script-paths.mjs +455 -0
  272. package/scripts/lib/validate/check-untracked-test-deps.mjs +10 -0
  273. package/scripts/lib/validate/check-unwired-features.mjs +0 -9
  274. package/scripts/lib/validate/check-validator-registration.mjs +254 -0
  275. package/scripts/lib/validate/check-vcs-repo-flag.mjs +6 -28
  276. package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
  277. package/scripts/lib/validate/markdown-fences.mjs +196 -0
  278. package/scripts/lib/vault-backfill/template.mjs +63 -6
  279. package/scripts/lib/vault-mirror/process.mjs +165 -42
  280. package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
  281. package/scripts/lib/vault-status/board-lock.mjs +185 -0
  282. package/scripts/lib/vault-status/board-writer.mjs +174 -135
  283. package/scripts/lib/vault-status/narrative-mirror.mjs +129 -37
  284. package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
  285. package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
  286. package/scripts/lib/wave-executor/remote-dispatch.mjs +502 -0
  287. package/scripts/lib/wave-resource-gate.mjs +133 -7
  288. package/scripts/lib/wave-sizing.mjs +4 -1
  289. package/scripts/lib/wave-transcript-tail.mjs +142 -8
  290. package/scripts/materialize-wave-scope.mjs +32 -9
  291. package/scripts/memory-propose.mjs +146 -8
  292. package/scripts/migrate-cold-start-seed.mjs +4 -1
  293. package/scripts/parse-config.mjs +60 -3
  294. package/scripts/promote-vault-strict.mjs +4 -15
  295. package/scripts/release.mjs +337 -29
  296. package/scripts/repair-invalid-sessions.mjs +3 -3
  297. package/scripts/run-quality-gate.mjs +128 -11
  298. package/scripts/site-numbers.mjs +36 -4
  299. package/scripts/sweep-expired-learnings.mjs +90 -0
  300. package/scripts/sync-vault-schema.mjs +3 -1
  301. package/scripts/telemetry.mjs +2 -2
  302. package/scripts/validate-plugin.mjs +187 -0
  303. package/scripts/validate-wave-scope.mjs +28 -8
  304. package/scripts/vault-consolidate.mjs +3 -11
  305. package/scripts/vault-integration-watcher.mjs +2 -4
  306. package/scripts/vault-mirror.mjs +111 -26
  307. package/scripts/wave-scope-binding.mjs +215 -0
  308. package/skills/_shared/instruction-file-resolution.md +10 -0
  309. package/skills/_shared/parallel-aware-auq.md +31 -2
  310. package/skills/_shared/parallel-aware-preamble.md +18 -4
  311. package/skills/_shared/platform-tools.md +1 -1
  312. package/skills/_shared/state-ownership.md +1 -1
  313. package/skills/architecture/SKILL.md +7 -5
  314. package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
  315. package/skills/autopilot/SKILL.md +4 -18
  316. package/skills/claude-md-drift-check/SKILL.md +5 -1
  317. package/skills/claude-md-drift-check/checker.mjs +62 -2
  318. package/skills/convergence-monitoring/SIGNALS.md +55 -0
  319. package/skills/discovery/probes/vault-staleness.mjs +37 -13
  320. package/skills/discovery/probes-arch.md +20 -18
  321. package/skills/dispatcher/SKILL.md +3 -2
  322. package/skills/ecosystem-health/SKILL.md +4 -1
  323. package/skills/ecosystem-health/wizard.md +5 -0
  324. package/skills/evolve/SKILL.md +87 -11
  325. package/skills/frontmatter-guard/SKILL.md +11 -5
  326. package/skills/npm-publish/SKILL.md +1 -1
  327. package/skills/reconcile/SKILL.md +38 -2
  328. package/skills/remote-offload/SKILL.md +89 -0
  329. package/skills/session-end/SKILL.md +18 -905
  330. package/skills/session-end/phase-3-6-tail.md +19 -9
  331. package/skills/session-end/plan-verification.md +221 -155
  332. package/skills/session-end/references/phase-2-quality-gate.md +93 -0
  333. package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
  334. package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
  335. package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
  336. package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
  337. package/skills/session-end/references/session-summary-template.md +62 -0
  338. package/skills/session-plan/SKILL.md +49 -0
  339. package/skills/session-start/SKILL.md +41 -900
  340. package/skills/session-start/phase-8-5-express-path.md +1 -1
  341. package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
  342. package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
  343. package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
  344. package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
  345. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
  346. package/skills/session-start/references/phase-4-ssot-environment-check.md +155 -0
  347. package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
  348. package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
  349. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
  350. package/skills/vault-sync/validator.mjs +21 -27
  351. package/skills/wave-executor/SKILL.md +16 -2
  352. package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
  353. package/skills/wave-executor/references/wave-loop-review.md +570 -0
  354. package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
  355. package/skills/wave-executor/wave-loop.md +14 -1271
  356. package/templates/_shared/journey-manifest.md +10 -6
  357. package/.cursor/commands/autopilot-multi.md +0 -14
  358. package/.cursor/commands/contract-version-bump.md +0 -14
  359. package/.cursor/commands/journey-audit.md +0 -14
  360. package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
  361. package/.cursor/skills/daily/SKILL.md +0 -12
  362. package/.cursor/skills/domain-model/SKILL.md +0 -13
  363. package/.cursor/skills/journey-audit/SKILL.md +0 -13
  364. package/.cursor/skills/skill-creator/SKILL.md +0 -13
  365. package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
  366. package/commands/autopilot-multi.md +0 -74
  367. package/commands/contract-version-bump.md +0 -28
  368. package/commands/journey-audit.md +0 -43
  369. package/pi/prompts/autopilot-multi.md +0 -12
  370. package/pi/prompts/contract-version-bump.md +0 -12
  371. package/pi/prompts/journey-audit.md +0 -12
  372. package/scripts/autopilot-multi.mjs +0 -885
  373. package/scripts/backfill-learnings-expires.mjs +0 -196
  374. package/scripts/backfill-learnings.mjs +0 -203
  375. package/scripts/fleet-instruction-scan.mjs +0 -141
  376. package/scripts/lib/autopilot/dep-graph.mjs +0 -417
  377. package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
  378. package/scripts/lib/webhook-url.mjs +0 -105
  379. package/scripts/lifecycle-sim-v6.mjs +0 -347
  380. package/scripts/migrate-learnings-jsonl.mjs +0 -189
  381. package/scripts/migrate-subagents-jsonl.mjs +0 -196
  382. package/scripts/upload-social-preview.mjs +0 -316
  383. package/skills/_shared/model-selection.md +0 -64
  384. package/skills/contract-version-bump/SKILL.md +0 -219
  385. package/skills/daily/SKILL.md +0 -222
  386. package/skills/daily/generate.sh +0 -92
  387. package/skills/daily/templates/daily.md.tpl +0 -36
  388. package/skills/journey-audit/SKILL.md +0 -269
  389. package/skills/skill-creator/SKILL.md +0 -168
  390. package/skills/ubiquitous-language/SKILL.md +0 -97
  391. package/skills/vault-sync/package-lock.json +0 -40
  392. /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
  393. /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
package/docs/telemetry.md CHANGED
@@ -39,18 +39,55 @@ projection unit test enforces the drop of any non-whitelisted input field.
39
39
  | `arch` | CPU architecture (e.g. `arm64`, `x64`). |
40
40
  | `node_major` | Major Node.js version in use. |
41
41
  | `ci` | Boolean — whether the run was detected as a CI environment. |
42
- | `fleet` | Boolean — whether this send came from an operator's own fleet-mode host (`owner.yaml` opt-in), as opposed to an external install. |
43
- | `session_type` | One of `housekeeping`, `feature`, `deep`, `other`. |
42
+ | `fleet` | Boolean — **DEPRECATED since 2026-09-06, removal 2027-03-06.** Identical in value to `fleet_self_declared` for the whole deprecation generation; kept so the server's existing `fleet` column stays comparable across the rename. |
43
+ | `fleet_self_declared` | Boolean, optional — the client's own claim that this send came from an operator host. **Self-declared, and the name says so on purpose:** the authoritative classification is server-side (see below). Derived from the *resolved consent state* (`enabled-fleet` from an `owner.yaml` opt-in, or `enabled-env` from `SO_TELEMETRY=1`), no longer from a raw `owner.yaml` read. |
44
+ | `session_profile` | Optional — the STATE.md frontmatter `session-profile`, and **whitelisted profile names only** (today exactly `ultradeep`). Anything else — a value your repo invented, a client name, a typo — is **omitted from the ping entirely**: never sent verbatim, and never flattened to `other` either. A SECOND axis beside `session_type`, never a substitute for it: an ultradeep session is `session_type: "deep"` PLUS `session_profile: "ultradeep"`. **Absent when no profile is set or the profile is not on the whitelist** (the key is omitted, never `null`), including on derived pings, which never invent one. The whitelist is enforced twice — client-side before the send, and again server-side, which rejects a record carrying an unlisted profile rather than storing it. |
45
+ | `session_record` | Optional — WHICH source the session facts in this ping came from: `ledger` (a matching `sessions.jsonl` record), `derived` (reconstructed from `events.jsonl`), `absent` (neither). When `absent`, `session_type` is `unknown` and `duration_bucket` is **not a measurement**. |
46
+ | `session_type` | One of `housekeeping`, `feature`, `deep`, `other`, `unknown`. `other` means MEASURED but not one of the three modes; `unknown` means NOT MEASURED. Before 2026-09-06 both collapsed to `other`. |
44
47
  | `duration_bucket` | One of `<15m`, `15-60m`, `1-3h`, `>3h` — a coarse bucket, never an exact duration. |
45
48
  | `skills[]` | Names of invoked skills, filtered against the shipped plugin roster — any name not in that roster becomes `"other"`. |
46
- | `commands[]` | Same filtering rule as `skills[]`. |
49
+ | `commands[]` | Names of invoked slash-commands, same filtering rule as `skills[]`. |
50
+
51
+ ### How a name lands in `skills[]` or `commands[]`
52
+
53
+ Both buckets are fed from one local ledger of invocations
54
+ (`.orchestrator/metrics/skill-invocations.jsonl`), so a single classification
55
+ rule decides which bucket a name reaches — and it is deliberately biased
56
+ towards anonymizing rather than towards attributing:
57
+
58
+ - Shipped **skills** are recorded plugin-prefixed
59
+ (`session-orchestrator:session-end`); shipped **commands** are recorded bare
60
+ (`session`). The Skill tool surfaces a slash-command that has no backing
61
+ `skills/` directory under the *prefixed* form too, so a prefixed name whose
62
+ bare form is a shipped command is reported in `commands[]` under that bare
63
+ name.
64
+ - A name is only ever reported as one of our commands when it carries the
65
+ plugin prefix. A **bare** name is never credited to a command, even when it
66
+ collides with one of our command names — a third-party or personal skill
67
+ invoked bare as `test` would otherwise be reported as our `/test` command.
68
+ Bare unknown names take the skills path and are reduced to `"other"`. The one
69
+ exception is a name arriving in the ledger's `.command` **field**: that field
70
+ is itself the "this is one of ours" provenance signal a bare `.skill` arrival
71
+ lacks, so `buildUsagePing` prefixes every `.command` value before
72
+ classification (`scripts/lib/telemetry/schema.mjs`). Without that step the
73
+ `.command` producer would be wired but dead — every record it writes would
74
+ silently become `"other"`.
75
+ - On a spelling collision (`memory-cleanup` exists as both a skill and a
76
+ command) the skill roster wins, so exactly one bucket is credited. Counting
77
+ distinct surfaces across `skills[]` and `commands[]` therefore never
78
+ double-counts a single one.
47
79
 
48
80
  ## What we never collect
49
81
 
50
82
  This list is a hard invariant, not a deferral:
51
83
 
52
84
  - No repository names, no file paths, no git remotes.
53
- - No prompts, no session transcripts, no free-form text of any kind.
85
+ - No prompts, no session transcripts, no free-form text of any kind. Every
86
+ field on the wire is either a number, a boolean, or a value from a closed
87
+ set this repository ships. The last free-text field, `session_profile`, was
88
+ closed on 2026-09-06: it now carries whitelisted profile names only, and an
89
+ unlisted value is dropped before the payload is built (and refused again by
90
+ the server, so it cannot be stored even if some other client sent it).
54
91
  - No command arguments — only whitelisted command/skill *names*, and only
55
92
  from the shipped roster (anything else is reduced to `"other"`).
56
93
  - No hostnames.
@@ -151,6 +188,38 @@ the offline queue is non-empty or a session has completed since — the latter
151
188
  clause is what lets the fallback originate a ping instead of only retrying a
152
189
  failed one (#1138).
153
190
 
191
+ ## The other thing that leaves the host: the update check
192
+
193
+ Telemetry is not the only outbound request this plugin can make, so the second
194
+ one is documented here rather than somewhere an egress audit would miss it.
195
+
196
+ On **SessionStart**, `hooks/on-session-start.mjs` calls the plugin-update banner
197
+ (`scripts/lib/plugin-update-banner.mjs`), which asks npm whether a newer release
198
+ exists:
199
+
200
+ ```
201
+ GET https://registry.npmjs.org/session-orchestrator/latest
202
+ ```
203
+
204
+ What it is, precisely:
205
+
206
+ - **No payload and no identifier.** It is a plain `GET` of a constant, public
207
+ URL — no body, no query string, no `anon_id`, no headers this plugin adds.
208
+ npm sees a request for a public package's metadata, as `npm view` would.
209
+ - **At most once per 24 h.** The answer is cached
210
+ (`plugin-latest.json`, `CACHE_TTL_MS = 24 h`); within the TTL no request is
211
+ made at all. The request has a short timeout and every failure is silent.
212
+ - **Independent of telemetry consent.** It is not a ping and sends nothing about
213
+ you — but it is still traffic, so it honours the offline flags below.
214
+
215
+ **Kill switches** (any one of them, set to anything other than empty / `0` /
216
+ `false`, turns the whole check off — not merely the request; the SessionStart
217
+ record then also omits `plugin_version_latest`):
218
+
219
+ - `SO_DISABLE_UPDATE_CHECK` — this probe's own switch.
220
+ - `DO_NOT_TRACK` — the standard flag, also honoured by the telemetry path.
221
+ - `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`.
222
+
154
223
  ## Retention
155
224
 
156
225
  - **Raw records:** kept 24 months, then pruned. The retention window exists
@@ -161,6 +230,99 @@ failed one (#1138).
161
230
  - **Anonymous ID rotation:** every 90 days, independent of retention — a
162
231
  rotated ID cannot be linked back to the one it replaced.
163
232
 
233
+ ## When a ping is sent
234
+
235
+ Two triggers, deliberately independent of each other:
236
+
237
+ 1. **SessionEnd** (`hooks/on-session-end.mjs`) — the mechanical close-time
238
+ flush.
239
+ 2. **SessionStart** (`backfillOnSessionStart` in
240
+ `scripts/backfill-abandoned-sessions.mjs`) — drains whatever the PREVIOUS
241
+ session left queued. Since 4.0.0 every queued record is re-projected and
242
+ `session_profile`-whitelisted at the transport boundary before it is sent,
243
+ and a batch the server rejects with HTTP 400/422 is EVICTED (breadcrumb
244
+ `reason: rejected-evicted`) instead of being re-queued forever — a poison
245
+ record can no longer block every later flush.
246
+
247
+ Trigger 2 exists because trigger 1 fires only on a REGULAR close, and most
248
+ sessions do not have one: measured 2026-09-06 over 90 fleet days, **429 clean
249
+ closes against 2.016 distinct `session.started` ids = 21,3 %**. Roughly four
250
+ sessions in five never reached the only code path that sends. SessionStart is
251
+ the trigger that survives whatever killed the previous session — the same
252
+ argument the abandoned-session backfill already makes for the ledger.
253
+
254
+ The start-time flush is bounded (1,5 s POST budget), lazily imported, gated by
255
+ the same consent check, and swallows every error: it can never delay or break a
256
+ session start. A timeout is lossless — the batch lands in the offline queue.
257
+
258
+ A ping no longer depends on `sessions.jsonl`. When the ledger has no matching
259
+ record, `session_type` and `duration_bucket` are reconstructed from
260
+ `events.jsonl` and the ping is stamped `session_record: "derived"`; when neither
261
+ source has a type, it is `session_type: "unknown"` with `session_record:
262
+ "absent"` — never a measured-looking `other`.
263
+
264
+ ### Sandbox guard
265
+
266
+ The sender refuses to send when it is not running in a real operator session.
267
+ This is not a nicety: on 2026-09-06 six agent sandboxes ran the SessionEnd hook
268
+ from a repo checkout and sent **six real pings to the production ingest server**,
269
+ minted against the operator's real `anon_id`, because `telemetry/paths.mjs`
270
+ resolves `~/.config/session-orchestrator/` from `homedir()` and does not honour
271
+ `SO_CONFIG_HOME` — faking the source never faked the destination.
272
+
273
+ A send is refused (no network, no queue write, no anon-ID mint) when **any** of:
274
+
275
+ - `SO_TELEMETRY_DISABLED=1` or `DO_NOT_TRACK` is set;
276
+ - `SO_CONFIG_HOME` / `XDG_CONFIG_HOME` points somewhere other than the directory
277
+ the telemetry state is actually read from (unless the caller redirected the
278
+ state path too — that redirect succeeded, which is the opposite of the leak);
279
+ - `CLAUDE_PROJECT_DIR`, or the cwd, sits under the OS temp directory or `/tmp`.
280
+
281
+ The guard also **fails closed**: if any of its own probes throws, the send is
282
+ refused with `sandbox:probe-failed` rather than permitted. An environment the
283
+ guard could not classify is treated as one it would have refused; nothing is
284
+ lost, because the next session re-probes from scratch.
285
+
286
+ If you invoke any telemetry writer by hand, export `SO_TELEMETRY_DISABLED=1`.
287
+
288
+ ## Server-side fleet attribution
289
+
290
+ The `fleet` flag on the wire is **self-declared and was measurably wrong**.
291
+ Until 2026-09-06 the client derived it as `ownerConfig?.telemetry?.enabled
292
+ === true` — a statement about a FILE, not about a person. The operator's
293
+ second Mac has consent granted but no `telemetry:` block in `owner.yaml`,
294
+ so it declared itself external: **394 of 490 server records (80,4 %)**
295
+ counted the operator as an external user, and every week's
296
+ `fleet_vs_external` was wrong by that margin.
297
+
298
+ Two independent repairs, because the client alone cannot close this:
299
+
300
+ 1. **Client-side** — `fleet_self_declared` is derived from the resolved
301
+ consent state (`enabled-fleet` / `enabled-env`), so a host opted in via
302
+ `SO_TELEMETRY=1` is no longer mistaken for an external install. The name
303
+ states the limit: a sandbox, or a host whose `owner.yaml` is unreachable,
304
+ still declares `false` however honest it is.
305
+ 2. **Server-side (authoritative)** — set `SO_INGEST_FLEET_ANON_IDS` on the
306
+ ingest server to a comma-separated list of the operator's own `anon_id`
307
+ values. A matching record is **stored** as fleet regardless of what it
308
+ claims. The allowlist can only PROMOTE, never demote: a host that honestly
309
+ declares itself fleet stays fleet even if the operator forgot to list it.
310
+
311
+ ```
312
+ SO_INGEST_FLEET_ANON_IDS=a3bb4907-…,c29cac99-…
313
+ ```
314
+
315
+ The record survives verbatim in `raw_json`, including its own `fleet` /
316
+ `fleet_self_declared` claim, so the client's declaration and the server's
317
+ verdict remain separable forever and the disagreement rate stays measurable.
318
+ `fleet_vs_external` in the weekly digest reads the stored column, i.e. the
319
+ server verdict. **The allowlist applies at INSERT time**, so it cannot repair
320
+ rows already written — a `fleet_vs_external` computed over a range that
321
+ predates the change is known-wrong and re-running the digest will not fix it.
322
+
323
+ Unset by default: with no `SO_INGEST_FLEET_ANON_IDS`, storage takes the
324
+ client's word exactly as it did before.
325
+
164
326
  ## Schema evolution
165
327
 
166
328
  The schema is **additive-only** within a given `schema_version`: new
@@ -169,6 +331,27 @@ without a version bump. The server accepts both the current and the
169
331
  immediately previous `schema_version`, so a slightly-outdated client is
170
332
  never hard-broken by a server-side schema update.
171
333
 
334
+ Unknown top-level fields are accepted by the server and preserved verbatim
335
+ inside `raw_json`, so an additive field round-trips through a server that
336
+ predates it — which is what makes a *rename* safe: emit both names for one
337
+ generation, then drop the old one.
338
+
339
+ **In flight now (added 2026-09-06, schema v1, additive):**
340
+
341
+ | Field | Status | Removal |
342
+ |---|---|---|
343
+ | `fleet_self_declared` | new name for `fleet` | — |
344
+ | `fleet` | deprecated alias, same value | **2027-03-06** |
345
+ | `session_record` | new (`ledger` \| `derived` \| `absent`) | — |
346
+ | `session_profile` | new (STATE.md `session-profile`, whitelisted names only; omitted when unset or unlisted) | — |
347
+
348
+ Client-side the frozen whitelist is split in two: `USAGE_PING_FIELDS` (the
349
+ REQUIRED v1 contract, which `tests/telemetry/parity.test.mjs` asserts the
350
+ server independently requires field by field) and
351
+ `USAGE_PING_OPTIONAL_FIELDS`. `projectUsagePing` projects the UNION, so the
352
+ data-minimization tripwire still holds — a field must be on a reviewed list
353
+ before it can reach the wire.
354
+
172
355
  ## Relationship to `telemetry-claims.md`
173
356
 
174
357
  This page describes the **opt-in, client-side usage-telemetry pipeline**
@@ -1,12 +1,19 @@
1
1
  # Vault & Docs Architecture — Umbrella Narrative
2
2
 
3
3
  **Audience:** Plugin contributors (Dev). New contributors who need to understand
4
- how the four documentation skills, one orchestrator skill, one agent, and two
5
- discovery probes fit together — what fires when, who owns which file, and how
4
+ how the three documentation skills, one orchestrator skill, one agent, two
5
+ vault-status writers, and two discovery probes fit together — what fires when, who owns which file, and how
6
6
  to recover when something breaks.
7
7
 
8
8
  **Status:** Living document. Tracks Epic #229 (Vault & Docs Orchestration).
9
9
 
10
+ **Last verified:** 2026-09-06 at `e4674109`. The vault-file layout below was
11
+ re-measured against the code that writes it — `scripts/lib/vault-status/narrative-mirror.mjs`
12
+ (`resolveNarrativePath`) and `scripts/lib/vault-status/board-writer.mjs` (`resolveBoardPath`) —
13
+ after the pre-#673/#674/#675 layout in this file was found stale by the 2026-09-06
14
+ 360°-Audit (`docs/audits/2026-09-06-360-audit/w1/d6-docs-drift.md`). The `daily` skill
15
+ rows are gone with the skill itself (§ 5A of that audit).
16
+
10
17
  ---
11
18
 
12
19
  ## 1. Purpose
@@ -48,6 +55,8 @@ Data flow within a single `/session feature → /go → /close` cycle:
48
55
  ↓
49
56
  ┌──────────────────────────────────────────────────────────────────┐
50
57
  │ session-start │
58
+ │ Phase 1.7 vault-status board (opt-in) │
59
+ │ └─ this repo → in-progress in _active-sessions.md │
51
60
  │ Phase 2.5 docs-orchestrator (opt-in) │
52
61
  │ └─ audience detection → docs-tasks block in STATE.md │
53
62
  │ Source: skills/session-start/phase-2-5-docs-planning.md │
@@ -78,6 +87,8 @@ Data flow within a single `/session feature → /go → /close` cycle:
78
87
  │ Phase 2.3 vault-staleness (opt-in: stale projects) │
79
88
  │ Phase 3.2 docs-verify (per-task ok/partial/gap) │
80
89
  │ Phase 3.7 vault-mirror (sessions.jsonl → 50-sessions/) │
90
+ │ Phase 3.7 narrative-mirror (STATE.md → _session-narrative) │
91
+ │ Phase 3.7c vault board (this repo's row → closed) │
81
92
  │ Source: skills/session-end/SKILL.md (phase markers) │
82
93
  └──────────────────────────────────────────────────────────────────┘
83
94
  ↓
@@ -86,7 +97,10 @@ Data flow within a single `/session feature → /go → /close` cycle:
86
97
  │ 01-projects/<slug>/ ← context.md / decisions.md / people.md │
87
98
  │ (docs-writer, Vault audience) │
88
99
  │ 01-projects/<slug>/ ← _overview.md (vault-mirror, no humans) │
89
- │ 03-daily/YYYY-MM-DD.md (daily skill, idempotent) │
100
+ │ 01-projects/<slug>/ ← _session-narrative.md │
101
+ │ (narrative-mirror, session-end 3.7) │
102
+ │ 01-projects/_active-sessions.md │
103
+ │ (board-writer, start 1.7 / end 3.7c) │
90
104
  │ 40-learnings/<slug>.md (vault-mirror, evolve hook) │
91
105
  │ 50-sessions/<id>.md (vault-mirror, session-end Phase 3.7) │
92
106
  └──────────────────────────────────────────────────────────────────┘
@@ -109,7 +123,8 @@ Data flow within a single `/session feature → /go → /close` cycle:
109
123
  | `vault-staleness` probes | `skills/discovery/probes-vault.md` + `skills/discovery/probes/vault-staleness.mjs` | `/discovery vault` (on-demand) and session-end Phase 2.3 (opt-in close-time gate) | `VAULT_DIR/01-projects/*/` `_overview.md` + narrative files | JSONL findings under `.orchestrator/metrics/vault-staleness.jsonl` and `vault-narrative-staleness.jsonl` | Vault/Ops (telemetry) |
110
124
  | `docs-orchestrator` | `skills/docs-orchestrator/SKILL.md` | session-start Phase 2.5, session-plan Step 1.5/1.8, session-end Phase 3.2 (all gated on `enabled: true`) | Session scope + Session Config audience list | `docs-tasks` block in STATE.md (write side); `### Documentation Coverage` block in final report (verify side) | All three (User / Dev / Vault) |
111
125
  | `docs-writer` agent | `agents/docs-writer.md` | Dispatched by `wave-executor` for each `Docs`-classified task | `diff`, `git-log`, `session-memory`, `affected-files` | Audience-targeted Markdown writes (Edit/Write); `[docs-orchestrator] Docs task complete` report line | All three (per task) |
112
- | `daily` | `skills/daily/SKILL.md` | User-invocable (`/daily`), idempotent | `VAULT_DIR/03-daily/`, `templates/daily.md.tpl` | `<vault>/03-daily/YYYY-MM-DD.md` (created or no-op) | Vault/Ops (PKM anchor) |
126
+ | `narrative-mirror` | `scripts/lib/vault-status/narrative-mirror.mjs` (`mirrorNarrative`) | session-end Phase 3.7, gated on `vault-integration.enabled` | `.claude/STATE.md` narrative sections + the session record | `<vault>/01-projects/<repo-slug>/_session-narrative.md` (generator-marked) | Vault/Ops (durable per-repo narrative) |
127
+ | `board-writer` | `scripts/lib/vault-status/board-writer.mjs` (`sweepBoard` / `mirrorBoard`) | session-start Phase 1.7 (`in-progress`) and session-end Phase 3.7c (`closed`), gated on `vault-integration.enabled` | live session registry + this repo's root | `<vault>/01-projects/_active-sessions.md` — one row per repo (generator-marked, idempotent, never touches `_overview.md`) | Vault/Ops (cross-repo occupancy board) |
113
128
  | `vault-mirror` | `skills/vault-mirror/SKILL.md` + `scripts/vault-mirror.mjs` | session-end Phase 3.7 (sessions); evolve Phase 3.5 (learnings) | `.orchestrator/metrics/sessions.jsonl`, `.orchestrator/metrics/learnings.jsonl` | `<vault>/50-sessions/<id>.md`, `<vault>/40-learnings/<slug>.md` (`_generator` marker `session-orchestrator-vault-mirror@1`) | Vault/Ops (telemetry → Markdown) |
114
129
  | `vault-backfill` CLI | `scripts/vault-backfill.mjs` | Manual, also surfaced via `/plan retro vault-backfill` sub-mode | `vault-integration.gitlab-groups` config + GitLab API | `.vault.yaml` per repo + Vault stub directories | Vault/Ops (one-shot migration) |
115
130
 
@@ -131,6 +146,15 @@ which audience. Never inline this table elsewhere — always cross-link.
131
146
  `<vault>/01-projects/<slug>/context.md`, `decisions.md`, `people.md`.
132
147
  Source: same.
133
148
 
149
+ Those three are the **authored** half of a project folder and are the only vault
150
+ files docs-writer may touch. The other four under `01-projects/` are **generated**
151
+ and carry a `_` prefix plus a generator marker: `_overview.md` (vault-mirror),
152
+ `_session-narrative.md` (narrative-mirror, #675), and — one level up, per vault
153
+ rather than per project — `_active-sessions.md` (board-writer, #674). Editing a
154
+ generated file by hand is not forbidden by a rule; it is simply overwritten on the
155
+ next run. Source: the `resolveNarrativePath` / `resolveBoardPath` path builders in
156
+ `scripts/lib/vault-status/`.
157
+
134
158
  The Session Config field `docs-orchestrator.audiences` accepts any subset of
135
159
  `[user, dev, vault]`; narrowing it (e.g., `[user, dev]` on a project without a
136
160
  Vault) suppresses Vault-targeted docs without disabling the orchestrator
@@ -169,7 +193,7 @@ silent REVIEW-marker-only output. Source:
169
193
 
170
194
  ## 6. Non-Overlap Discipline
171
195
 
172
- Three forbidden cross-writes are enforced by the architecture, not just by
196
+ Four forbidden cross-writes are enforced by the architecture, not just by
173
197
  convention:
174
198
 
175
199
  - **`<vault>/01-projects/*/_overview.md` is owned by `vault-mirror`.** The
@@ -179,11 +203,24 @@ convention:
179
203
  `skills/docs-orchestrator/audience-mapping.md` § Non-Overlap (vault-mirror
180
204
  row) and `skills/vault-mirror/SKILL.md` § Idempotency (the `_generator`
181
205
  marker `session-orchestrator-vault-mirror@1` is the discriminator).
182
- - **`<vault>/03-daily/YYYY-MM-DD.md` is owned by `daily`.** Idempotent by
183
- design — re-running `/daily` opens the existing note, never overwrites.
184
- Source: `skills/daily/SKILL.md` § Idempotency Guarantee. A second writer
185
- would corrupt the day's scratch notes. Source:
186
- `skills/docs-orchestrator/audience-mapping.md` § Non-Overlap (daily row).
206
+ - **`<vault>/01-projects/_active-sessions.md` and
207
+ `<vault>/01-projects/<slug>/_session-narrative.md` are owned by the two
208
+ vault-status writers.** Both are generator-marked and rewritten wholesale on
209
+ the next session-start / session-end, so a hand edit is silently lost rather
210
+ than merged. `board-writer` additionally holds a cross-repo mutex at
211
+ `<vault>/.orchestrator/board.lock` while writing
212
+ (`scripts/lib/vault-status/board-lock.mjs`, #1180 — deliberately not beside
213
+ the board) because the board is the one vault file several repos write
214
+ concurrently.
215
+ - **`<vault>/03-daily/*` remains a forbidden path for `docs-writer`, but no
216
+ plugin component writes it any more.** The `daily` skill was retired on
217
+ 2026-09-06 (360°-Audit § 5A) and `templates/daily.md.tpl` is gone with it;
218
+ measured the same day, `03-daily/` survives in code only inside
219
+ `scripts/lib/frontmatter-guard.mjs`'s vault-subdirectory *detector*, which
220
+ reads paths and writes none. The forbidden-path entries in
221
+ `skills/docs-orchestrator/audience-mapping.md` § Non-Overlap and
222
+ `agents/docs-writer.md` still name `daily` as the owner; that is stale
223
+ attribution for a path the operator's own PKM now owns, not a live contract.
187
224
  - **`CLAUDE.md` may be remediated by `docs-writer` (Dev audience), but
188
225
  `claude-md-drift-check` only diagnoses it.** The two skills must not run
189
226
  on `CLAUDE.md` in parallel within the same wave. Source:
@@ -201,6 +238,7 @@ Concrete answer to "when does each component fire":
201
238
 
202
239
  | Phase | Skill / Probe | Gating |
203
240
  |-------|---------------|--------|
241
+ | `/session` start, Phase 1.7 | `board-writer` marks this repo `in-progress` on `_active-sessions.md` | `vault-integration.enabled: true` |
204
242
  | `/session` start, Phase 2.5 | `docs-orchestrator` audience detection | `docs-orchestrator.enabled: true` |
205
243
  | `/session` start, Phase 4.5 | resource-health probe | always (env-aware) |
206
244
  | session-plan Step 1.5/1.8 | `docs-writer` registered + Docs role classified | `docs-orchestrator.enabled: true` |
@@ -210,9 +248,10 @@ Concrete answer to "when does each component fire":
210
248
  | `/close` Phase 2.3 | `vault-staleness` + `vault-narrative-staleness` probes | `vault-staleness.enabled: true` |
211
249
  | `/close` Phase 3.2 | `docs-orchestrator` verification | `docs-orchestrator.enabled: true` AND `docs-tasks` block present |
212
250
  | `/close` Phase 3.7 | `vault-mirror` (sessions) | `vault-integration.enabled: true` AND `mode != off` |
251
+ | `/close` Phase 3.7 | `narrative-mirror` → `_session-narrative.md` | `vault-integration.enabled: true`; `mode: strict` blocks the close on failure, otherwise WARN |
252
+ | `/close` Phase 3.7c | `board-writer` transitions this repo's row to `closed` | `vault-integration.enabled: true`; non-blocking |
213
253
  | evolve Phase 3.5 | `vault-mirror` (learnings) | same as above |
214
254
  | `/discovery vault` | `vault-staleness` probes (on-demand) | `.vault.yaml` present OR `vault-integration.enabled: true` |
215
- | `/daily` | `daily` skill | user-invocable; no Session Config gate |
216
255
 
217
256
  Sources: `skills/session-end/SKILL.md` (Phase markers), `docs/session-config-reference.md`
218
257
  (per-skill enabled-flag semantics), `skills/discovery/probes-vault.md` (probe
@@ -0,0 +1,111 @@
1
+ /**
2
+ * atomic-json.mjs — shared atomic read-modify-write helper for JSON hook state.
3
+ *
4
+ * Extracted (issue #1197) from four byte-identical private copies of
5
+ * `atomicMutateJson` that had accumulated independently in
6
+ * `hooks/cwd-change-restore.mjs`, `hooks/on-session-end.mjs`,
7
+ * `hooks/post-tool-batch-wave-signal.mjs`, and
8
+ * `hooks/post-tool-failure-corrective-context.mjs` (measured 2026-09-02 @
9
+ * a019d5a4 via `rg -n "function atomicMutateJson" hooks/` — 4 hits, one per
10
+ * file, logic byte-identical modulo the per-file tmp-suffix and the
11
+ * `fs.`-namespace-vs-named-import style `on-session-end.mjs` uses).
12
+ *
13
+ * F-H fix (W3-reviewer finding, #1197): the pre-extraction copies treated
14
+ * EVERY read/parse failure as "file absent" and silently fell back to
15
+ * `defaultValue` — so an unparsable file, `EISDIR`, `EACCES`, or a file
16
+ * truncated mid-write by a concurrent writer was overwritten with
17
+ * `defaultValue`-derived content instead of being left alone. Only `ENOENT`
18
+ * (file genuinely does not exist yet) is a legitimate "start fresh" case;
19
+ * every other failure now aborts BEFORE the tmp-write/rename stage and
20
+ * reports `{ ok: false, reason }` — the original file is never touched.
21
+ *
22
+ * Return-object over throw (deliberate — see #1197 task note): all four
23
+ * callers already run under `main().catch(() => {}).finally(() =>
24
+ * process.exit(0))` — informational, never-deny hooks (verified 2026-09-02:
25
+ * none of the four match a deny/block pattern) — so a thrown error would be
26
+ * swallowed safely too. But in `post-tool-batch-wave-signal.mjs` a throw at
27
+ * the FIRST call site (line ~278, the `last_batch` write) would abort
28
+ * `main()` before it reaches the independent heartbeat-refresh block that
29
+ * follows — a real behavioural loss the thrown-error path would introduce
30
+ * silently. A result object lets every caller decide locally whether an RMW
31
+ * failure should short-circuit the rest of `main()` or just get logged and
32
+ * ignored, so no caller loses unrelated post-call behaviour by construction.
33
+ *
34
+ * NAMED CEILING (BV-004): tmp-file + rename makes each individual write
35
+ * atomic, but not the full read-modify-write — two concurrent callers can
36
+ * still interleave (both read the same `current`, both compute an update
37
+ * from it, the second rename wins and silently drops the first mutation).
38
+ * This module does not defend against that race. Revisit with a per-file
39
+ * lock (e.g. an flock-style sidecar or `session-lock.mjs`'s lease pattern)
40
+ * if `.orchestrator/current-session.json` writes start dropping fields
41
+ * under concurrent-session load — no such loss has been measured yet.
42
+ *
43
+ * @module hooks/_lib/atomic-json
44
+ */
45
+
46
+ import { readFile, writeFile, rename, mkdir, unlink } from 'node:fs/promises';
47
+ import path from 'node:path';
48
+
49
+ /**
50
+ * Atomic read-modify-write of a JSON file via temp-file + rename.
51
+ *
52
+ * Reads the existing file and parses it as JSON. When the file does not
53
+ * exist (`ENOENT`), starts from `defaultValue` — the only case in which
54
+ * "file absent" is legitimate. Any OTHER read or parse failure (directory
55
+ * at `filePath`, permission denied, truncated/corrupt JSON, …) aborts
56
+ * WITHOUT calling `mutate` and WITHOUT writing anything — the original file
57
+ * (if any) is left exactly as it was.
58
+ *
59
+ * On success, applies the synchronous `mutate` transformer, writes the
60
+ * result to a `${filePath}.tmp-<suffix>-<pid>-<ts>` sibling, then renames it
61
+ * over `filePath` (atomic on POSIX same-filesystem rename; best-effort on
62
+ * Windows). If the write/rename stage itself fails, the tmp file is
63
+ * best-effort unlinked so a failure never leaves an orphaned `.tmp-*`
64
+ * artifact behind.
65
+ *
66
+ * @param {string} filePath — absolute path to the JSON file.
67
+ * @param {object} defaultValue — starting value used ONLY when the file does
68
+ * not exist yet (`ENOENT`). Never applied on top of an unreadable-but-
69
+ * present file.
70
+ * @param {function(object): object} mutate — pure synchronous transformer;
71
+ * receives the parsed current value (or `defaultValue`), returns the next
72
+ * value to persist.
73
+ * @param {string} [tmpTag] — short tag folded into the tmp filename so
74
+ * concurrent callers targeting the same `filePath` from different hooks
75
+ * don't collide on the same tmp path (mirrors the per-caller suffixes the
76
+ * four pre-extraction copies used: `-cwd-`, `-ose-`, `-ptb-`, `-ptf-`).
77
+ * @returns {Promise<{ ok: true, value: object } | { ok: false, reason: string }>}
78
+ */
79
+ export async function atomicMutateJson(filePath, defaultValue, mutate, tmpTag = 'ajs') {
80
+ let current = defaultValue;
81
+ try {
82
+ const raw = await readFile(filePath, 'utf8');
83
+ current = JSON.parse(raw);
84
+ } catch (err) {
85
+ if (err && err.code === 'ENOENT') {
86
+ // File genuinely does not exist yet — the only legitimate "fresh
87
+ // file" case. `current` already holds `defaultValue`.
88
+ } else {
89
+ // EISDIR, EACCES, a JSON.parse SyntaxError (unparsable/truncated
90
+ // content), or anything else — never silently treat as "absent".
91
+ // Abort before mutate/write; the original file is untouched.
92
+ return { ok: false, reason: (err && err.code) || 'parse-error' };
93
+ }
94
+ }
95
+
96
+ const updated = mutate(current);
97
+ const tmp = `${filePath}.tmp-${tmpTag}-${process.pid}-${Date.now()}`;
98
+ try {
99
+ await mkdir(path.dirname(filePath), { recursive: true });
100
+ await writeFile(tmp, JSON.stringify(updated, null, 2) + '\n', 'utf8');
101
+ await rename(tmp, filePath);
102
+ } catch (err) {
103
+ try {
104
+ await unlink(tmp);
105
+ } catch {
106
+ // tmp was never created, or is already gone — nothing to clean up.
107
+ }
108
+ return { ok: false, reason: (err && err.code) || 'write-error' };
109
+ }
110
+ return { ok: true, value: updated };
111
+ }