session-orchestrator 3.24.0 β†’ 4.0.1

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 (435) 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 +3 -2
  47. package/.codex-plugin/skills/architecture/SKILL.md +20 -0
  48. package/.codex-plugin/skills/autopilot/SKILL.md +21 -0
  49. package/.codex-plugin/skills/autopilot/agents/openai.yaml +5 -0
  50. package/.codex-plugin/skills/bootstrap/SKILL.md +22 -0
  51. package/.codex-plugin/skills/bootstrap/agents/openai.yaml +5 -0
  52. package/.codex-plugin/skills/brainstorm/SKILL.md +22 -0
  53. package/.codex-plugin/skills/brainstorm/agents/openai.yaml +5 -0
  54. package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +17 -0
  55. package/.codex-plugin/skills/close/SKILL.md +21 -0
  56. package/.codex-plugin/skills/close/agents/openai.yaml +5 -0
  57. package/.codex-plugin/skills/convergence-monitoring/SKILL.md +24 -0
  58. package/.codex-plugin/skills/debug/SKILL.md +21 -0
  59. package/.codex-plugin/skills/debug/agents/openai.yaml +5 -0
  60. package/.codex-plugin/skills/discovery/SKILL.md +21 -0
  61. package/.codex-plugin/skills/discovery/agents/openai.yaml +5 -0
  62. package/.codex-plugin/skills/dispatcher/SKILL.md +21 -0
  63. package/.codex-plugin/skills/dispatcher/agents/openai.yaml +5 -0
  64. package/.codex-plugin/skills/docs-orchestrator/SKILL.md +20 -0
  65. package/.codex-plugin/skills/ecosystem-health/SKILL.md +22 -0
  66. package/.codex-plugin/skills/eli5/SKILL.md +21 -0
  67. package/.codex-plugin/skills/eli5/agents/openai.yaml +5 -0
  68. package/.codex-plugin/skills/eval/SKILL.md +21 -0
  69. package/.codex-plugin/skills/eval/agents/openai.yaml +5 -0
  70. package/.codex-plugin/skills/evolve/SKILL.md +21 -0
  71. package/.codex-plugin/skills/evolve/agents/openai.yaml +5 -0
  72. package/.codex-plugin/skills/frontmatter-guard/SKILL.md +17 -0
  73. package/.codex-plugin/skills/gitlab-ops/SKILL.md +22 -0
  74. package/.codex-plugin/skills/gitlab-portfolio/SKILL.md +17 -0
  75. package/.codex-plugin/skills/go/SKILL.md +22 -0
  76. package/.codex-plugin/skills/go/agents/openai.yaml +5 -0
  77. package/.codex-plugin/skills/grill/SKILL.md +21 -0
  78. package/.codex-plugin/skills/grill/agents/openai.yaml +5 -0
  79. package/.codex-plugin/skills/harness-audit/SKILL.md +19 -0
  80. package/.codex-plugin/skills/harness-audit/agents/openai.yaml +5 -0
  81. package/.codex-plugin/skills/hook-development/SKILL.md +17 -0
  82. package/.codex-plugin/skills/mcp-builder/SKILL.md +17 -0
  83. package/.codex-plugin/skills/memory-cleanup/SKILL.md +21 -0
  84. package/.codex-plugin/skills/memory-cleanup/agents/openai.yaml +5 -0
  85. package/.codex-plugin/skills/mode-selector/SKILL.md +19 -0
  86. package/.codex-plugin/skills/npm-publish/SKILL.md +18 -0
  87. package/.codex-plugin/skills/peekaboo-driver/SKILL.md +20 -0
  88. package/.codex-plugin/skills/persona-panel/SKILL.md +22 -0
  89. package/.codex-plugin/skills/persona-panel/agents/openai.yaml +5 -0
  90. package/.codex-plugin/skills/plan/SKILL.md +22 -0
  91. package/.codex-plugin/skills/plan/agents/openai.yaml +5 -0
  92. package/.codex-plugin/skills/playwright-driver/SKILL.md +22 -0
  93. package/.codex-plugin/skills/portfolio/SKILL.md +21 -0
  94. package/.codex-plugin/skills/portfolio/agents/openai.yaml +5 -0
  95. package/.codex-plugin/skills/quality-gates/SKILL.md +22 -0
  96. package/.codex-plugin/skills/reconcile/SKILL.md +21 -0
  97. package/.codex-plugin/skills/reconcile/agents/openai.yaml +5 -0
  98. package/.codex-plugin/skills/release/SKILL.md +22 -0
  99. package/.codex-plugin/skills/release/agents/openai.yaml +5 -0
  100. package/.codex-plugin/skills/remote-offload/SKILL.md +22 -0
  101. package/.codex-plugin/skills/repo-audit/SKILL.md +19 -0
  102. package/.codex-plugin/skills/repo-audit/agents/openai.yaml +5 -0
  103. package/.codex-plugin/skills/session/SKILL.md +21 -0
  104. package/.codex-plugin/skills/session/agents/openai.yaml +5 -0
  105. package/.codex-plugin/skills/session-end/SKILL.md +22 -0
  106. package/.codex-plugin/skills/session-plan/SKILL.md +22 -0
  107. package/.codex-plugin/skills/session-start/SKILL.md +22 -0
  108. package/.codex-plugin/skills/spinout/SKILL.md +21 -0
  109. package/.codex-plugin/skills/spinout/agents/openai.yaml +5 -0
  110. package/.codex-plugin/skills/sunset-review/SKILL.md +21 -0
  111. package/.codex-plugin/skills/sunset-review/agents/openai.yaml +5 -0
  112. package/.codex-plugin/skills/templates-ack/SKILL.md +21 -0
  113. package/.codex-plugin/skills/templates-ack/agents/openai.yaml +5 -0
  114. package/.codex-plugin/skills/test/SKILL.md +21 -0
  115. package/.codex-plugin/skills/test/agents/openai.yaml +5 -0
  116. package/.codex-plugin/skills/test-runner/SKILL.md +22 -0
  117. package/.codex-plugin/skills/tmux-layout/SKILL.md +23 -0
  118. package/.codex-plugin/skills/using-orchestrator/SKILL.md +19 -0
  119. package/.codex-plugin/skills/vault-mirror/SKILL.md +17 -0
  120. package/.codex-plugin/skills/vault-sync/SKILL.md +17 -0
  121. package/.codex-plugin/skills/wave-executor/SKILL.md +22 -0
  122. package/.codex-plugin/skills/write-executable-plan/SKILL.md +24 -0
  123. package/.cursor/commands/autopilot.md +2 -2
  124. package/.cursor/commands/bootstrap.md +1 -1
  125. package/.cursor/commands/brainstorm.md +1 -1
  126. package/.cursor/commands/debug.md +1 -1
  127. package/.cursor/commands/discovery.md +1 -1
  128. package/.cursor/commands/dispatcher.md +2 -2
  129. package/.cursor/commands/eli5.md +2 -2
  130. package/.cursor/commands/eval.md +2 -2
  131. package/.cursor/commands/evolve.md +1 -1
  132. package/.cursor/commands/go.md +1 -1
  133. package/.cursor/commands/grill.md +2 -2
  134. package/.cursor/commands/memory-cleanup.md +2 -2
  135. package/.cursor/commands/persona-panel.md +1 -1
  136. package/.cursor/commands/plan.md +1 -1
  137. package/.cursor/commands/portfolio.md +1 -1
  138. package/.cursor/commands/reconcile.md +2 -2
  139. package/.cursor/commands/release.md +2 -2
  140. package/.cursor/commands/session.md +2 -2
  141. package/.cursor/commands/spinout.md +2 -2
  142. package/.cursor/commands/sunset-review.md +2 -2
  143. package/.cursor/commands/templates-ack.md +2 -2
  144. package/.cursor/commands/test.md +2 -2
  145. package/.cursor/skills/brainstorm/SKILL.md +1 -1
  146. package/.cursor/skills/eval/SKILL.md +1 -1
  147. package/.cursor/skills/quality-gates/SKILL.md +1 -1
  148. package/.cursor/skills/remote-offload/SKILL.md +1 -1
  149. package/.cursor-plugin/plugin.json +30 -0
  150. package/.orchestrator/policy/blocked-commands.json +121 -0
  151. package/.orchestrator/policy/ecosystem.schema.json +66 -0
  152. package/.orchestrator/policy/quality-gates.example.json +16 -0
  153. package/.orchestrator/policy/quality-gates.schema.json +38 -0
  154. package/.orchestrator/policy/templates-policy.json +27 -0
  155. package/.orchestrator/policy/test-profiles.json +47 -0
  156. package/AGENTS.md +225 -0
  157. package/CHANGELOG.md +1314 -2
  158. package/NOTICE +11 -6
  159. package/README.md +135 -94
  160. package/agents/eval-judge.md +1 -1
  161. package/agents/skill-applied-judge.md +1 -1
  162. package/assets/wave-lifecycle.svg +98 -0
  163. package/commands/release.md +6 -3
  164. package/commands/session.md +18 -3
  165. package/docs/README.md +4 -0
  166. package/{agents/AGENTS.md β†’ docs/agent-authoring.md} +19 -26
  167. package/docs/baseline.md +67 -0
  168. package/docs/ci-setup.md +108 -62
  169. package/docs/codex-setup.md +107 -29
  170. package/docs/components.md +38 -16
  171. package/docs/cursor-setup.md +6 -2
  172. package/docs/events-schema.md +9 -6
  173. package/docs/instruction-delivery.md +69 -0
  174. package/{agents/memory-proposal-collector.md β†’ docs/memory-proposal-flow.md} +1 -8
  175. package/docs/migration-v4.md +365 -0
  176. package/docs/pi-setup.md +6 -1
  177. package/docs/plugin-architecture-v3.md +1 -1
  178. package/docs/rule-authoring.md +85 -19
  179. package/docs/scope-collision-guard.md +5 -5
  180. package/docs/session-config-reference.md +57 -56
  181. package/docs/session-config-template.md +6 -29
  182. package/docs/telemetry.md +157 -3
  183. package/docs/vault-docs-architecture.md +50 -11
  184. package/hooks/_lib/hook-import-set.json +1488 -0
  185. package/hooks/_lib/subagent-transcript.mjs +562 -0
  186. package/hooks/config-protection.mjs +2 -2
  187. package/hooks/cwd-change-restore.mjs +2 -2
  188. package/hooks/enforce-commands.mjs +69 -0
  189. package/hooks/hooks-codex.json +1 -1
  190. package/hooks/hooks-cursor.json +10 -0
  191. package/hooks/hooks-pi.json +5 -0
  192. package/hooks/hooks.json +6 -1
  193. package/hooks/loop-guard.mjs +3 -3
  194. package/hooks/on-session-end.mjs +2 -2
  195. package/hooks/on-session-start.mjs +103 -2
  196. package/hooks/on-stop.mjs +60 -14
  197. package/hooks/operator-steer.mjs +2 -2
  198. package/hooks/post-bash-write-verify.mjs +85 -0
  199. package/hooks/post-edit-import-probe.mjs +344 -0
  200. package/hooks/post-subagent-discovery-validator.mjs +187 -431
  201. package/hooks/post-tool-batch-wave-signal.mjs +118 -4
  202. package/hooks/post-tool-failure-corrective-context.mjs +2 -2
  203. package/hooks/post-tooluse-frontend-slop.mjs +3 -3
  204. package/hooks/pre-bash-destructive-guard.mjs +39 -13
  205. package/hooks/skill-invocation-telemetry.mjs +17 -5
  206. package/hooks/subagent-telemetry.mjs +13 -4
  207. package/monitors/monitors.json +3 -3
  208. package/package.json +9 -1
  209. package/pi/prompts/session.md +2 -2
  210. package/scripts/backfill-abandoned-sessions.mjs +50 -4
  211. package/scripts/backfill-learnings-from-vault.mjs +9 -3
  212. package/scripts/dialectic-deriver.mjs +73 -8
  213. package/scripts/export-hw-learnings.mjs +113 -1
  214. package/scripts/generate-agents-skills.mjs +378 -0
  215. package/scripts/generate-codex-skills.mjs +246 -0
  216. package/scripts/generate-cursor-adapter.mjs +45 -8
  217. package/scripts/generate-hook-import-set.mjs +292 -0
  218. package/scripts/lib/agent-status.mjs +13 -2
  219. package/scripts/lib/auto-dream.mjs +38 -36
  220. package/scripts/lib/autonomy/suitability.mjs +6 -0
  221. package/scripts/lib/autopilot/loop.mjs +2 -2
  222. package/scripts/lib/ci-status-banner.mjs +220 -75
  223. package/scripts/lib/codex/plugin-contract.mjs +88 -6
  224. package/scripts/lib/config/auto-dream.mjs +2 -1
  225. package/scripts/lib/config/block-header.mjs +8 -0
  226. package/scripts/lib/config/block-preprocess.mjs +177 -0
  227. package/scripts/lib/config/broken-window.mjs +2 -1
  228. package/scripts/lib/config/cold-start.mjs +2 -1
  229. package/scripts/lib/config/config-protection.mjs +22 -2
  230. package/scripts/lib/config/context-coverage.mjs +2 -1
  231. package/scripts/lib/config/cross-repo.mjs +2 -1
  232. package/scripts/lib/config/custom-phases.mjs +2 -1
  233. package/scripts/lib/config/dialectic.mjs +2 -1
  234. package/scripts/lib/config/discovery-validator.mjs +2 -1
  235. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
  236. package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
  237. package/scripts/lib/config/docs-orchestrator.mjs +2 -1
  238. package/scripts/lib/config/docs-staleness.mjs +2 -1
  239. package/scripts/lib/config/drift-check.mjs +2 -1
  240. package/scripts/lib/config/eval.mjs +2 -1
  241. package/scripts/lib/config/events-rotation.mjs +2 -1
  242. package/scripts/lib/config/evolve.mjs +8 -2
  243. package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
  244. package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
  245. package/scripts/lib/config/handover-gate.mjs +2 -1
  246. package/scripts/lib/config/health-endpoints.mjs +7 -2
  247. package/scripts/lib/config/host-paths.mjs +20 -4
  248. package/scripts/lib/config/issue-budget.mjs +2 -1
  249. package/scripts/lib/config/loop-guard.mjs +2 -1
  250. package/scripts/lib/config/memory.mjs +2 -1
  251. package/scripts/lib/config/moc-staleness.mjs +2 -1
  252. package/scripts/lib/config/persona-gate-wave.mjs +2 -1
  253. package/scripts/lib/config/private-config-dir.mjs +67 -0
  254. package/scripts/lib/config/reconcile.mjs +2 -1
  255. package/scripts/lib/config/remote-hosts.mjs +2 -1
  256. package/scripts/lib/config/section-extractor.mjs +7 -1
  257. package/scripts/lib/config/skill-evolution.mjs +2 -1
  258. package/scripts/lib/config/slopcheck.mjs +2 -1
  259. package/scripts/lib/config/state-md-lock.mjs +2 -1
  260. package/scripts/lib/config/templates-first.mjs +2 -1
  261. package/scripts/lib/config/test.mjs +2 -1
  262. package/scripts/lib/config/vault-integration.mjs +7 -1
  263. package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
  264. package/scripts/lib/config/vault-staleness.mjs +2 -1
  265. package/scripts/lib/config/vault-sync.mjs +2 -1
  266. package/scripts/lib/config/verification-auto-fix.mjs +2 -1
  267. package/scripts/lib/config/wave-reviewers.mjs +2 -1
  268. package/scripts/lib/config/worktree-orphans.mjs +2 -1
  269. package/scripts/lib/convergence-monitor.mjs +82 -16
  270. package/scripts/lib/dispatcher/rank.mjs +124 -48
  271. package/scripts/lib/ecosystem-health.mjs +16 -2
  272. package/scripts/lib/eval/engine.mjs +9 -1
  273. package/scripts/lib/eval/session-resolve.mjs +23 -4
  274. package/scripts/lib/events.mjs +22 -6
  275. package/scripts/lib/frontmatter-guard.mjs +131 -13
  276. package/scripts/lib/gates/gate-full.mjs +30 -0
  277. package/scripts/lib/gates/gate-helpers.mjs +76 -0
  278. package/scripts/lib/hardware-pattern-detector.mjs +18 -1
  279. package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
  280. package/scripts/lib/host-identity.mjs +50 -11
  281. package/scripts/lib/instruction-budget-guard.mjs +171 -5
  282. package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
  283. package/scripts/lib/learnings/io.mjs +60 -6
  284. package/scripts/lib/memory-proposals/store.mjs +30 -22
  285. package/scripts/lib/owner-config-banner.mjs +41 -6
  286. package/scripts/lib/owner-config-loader.mjs +21 -10
  287. package/scripts/lib/owner-interview.mjs +3 -3
  288. package/scripts/lib/owner-yaml.mjs +215 -15
  289. package/scripts/lib/platform.mjs +108 -15
  290. package/scripts/lib/plugin-update-banner.mjs +414 -0
  291. package/scripts/lib/project-hygiene.mjs +38 -2
  292. package/scripts/lib/qg-command-drift-banner.mjs +50 -12
  293. package/scripts/lib/quality-gate.mjs +133 -44
  294. package/scripts/lib/reconcile/emitter.mjs +68 -6
  295. package/scripts/lib/reconcile/engine.mjs +51 -11
  296. package/scripts/lib/reconcile/idempotency.mjs +37 -4
  297. package/scripts/lib/reconcile/writer.mjs +40 -18
  298. package/scripts/lib/session-close-backfill.mjs +67 -9
  299. package/scripts/lib/session-id.mjs +12 -23
  300. package/scripts/lib/session-identity/own-session.mjs +125 -10
  301. package/scripts/lib/session-lock-shape.mjs +43 -0
  302. package/scripts/lib/session-lock.mjs +5 -10
  303. package/scripts/lib/session-registry.mjs +25 -9
  304. package/scripts/lib/session-schema/constants.mjs +64 -3
  305. package/scripts/lib/session-schema/validator.mjs +38 -4
  306. package/scripts/lib/session-start-probes.mjs +30 -1
  307. package/scripts/lib/sessions-staleness-banner.mjs +18 -11
  308. package/scripts/lib/skill-health/join.mjs +17 -4
  309. package/scripts/lib/state-md.mjs +78 -0
  310. package/scripts/lib/sunset/walker.mjs +6 -0
  311. package/scripts/lib/telemetry/schema.mjs +202 -9
  312. package/scripts/lib/telemetry/sync.mjs +368 -12
  313. package/scripts/lib/telemetry-flush-health-banner.mjs +211 -0
  314. package/scripts/lib/validate/check-agents-skills.mjs +327 -0
  315. package/scripts/lib/validate/check-agents.mjs +3 -3
  316. package/scripts/lib/validate/check-codex-skills.mjs +191 -0
  317. package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
  318. package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
  319. package/scripts/lib/validate/check-owner-leakage.mjs +319 -22
  320. package/scripts/lib/validate/check-skill-links.mjs +193 -0
  321. package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
  322. package/scripts/lib/validate/check-test-git-config-target.mjs +192 -12
  323. package/scripts/lib/validate/check-unwired-features.mjs +163 -15
  324. package/scripts/lib/validate/check-validator-registration.mjs +10 -4
  325. package/scripts/lib/validate/confidential-names.mjs +95 -30
  326. package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
  327. package/scripts/lib/validate/repo-files.mjs +48 -14
  328. package/scripts/lib/vault-backfill/template.mjs +63 -6
  329. package/scripts/lib/vault-mirror/process.mjs +165 -42
  330. package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
  331. package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
  332. package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
  333. package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
  334. package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
  335. package/scripts/lib/wave-resource-gate.mjs +8 -2
  336. package/scripts/lib/wave-sizing.mjs +4 -1
  337. package/scripts/lib/wave-transcript-tail.mjs +118 -4
  338. package/scripts/materialize-wave-scope.mjs +12 -5
  339. package/scripts/memory-propose.mjs +19 -5
  340. package/scripts/migrate-cold-start-seed.mjs +4 -1
  341. package/scripts/parse-config.mjs +60 -3
  342. package/scripts/release.mjs +430 -31
  343. package/scripts/repair-invalid-sessions.mjs +3 -3
  344. package/scripts/run-quality-gate.mjs +128 -11
  345. package/scripts/site-numbers.mjs +344 -8
  346. package/scripts/sweep-expired-learnings.mjs +90 -0
  347. package/scripts/sync-vault-schema.mjs +3 -1
  348. package/scripts/telemetry.mjs +2 -2
  349. package/scripts/validate-plugin.mjs +164 -0
  350. package/scripts/validate-wave-scope.mjs +28 -8
  351. package/scripts/wave-scope-binding.mjs +215 -0
  352. package/skills/_shared/instruction-file-resolution.md +10 -0
  353. package/skills/_shared/parallel-aware-preamble.md +1 -0
  354. package/skills/_shared/platform-tools.md +1 -1
  355. package/skills/_shared/state-ownership.md +1 -1
  356. package/skills/architecture/SKILL.md +7 -5
  357. package/skills/{domain-model/SKILL.md β†’ architecture/references/domain-model.md} +9 -9
  358. package/skills/autopilot/SKILL.md +4 -18
  359. package/skills/claude-md-drift-check/SKILL.md +5 -1
  360. package/skills/claude-md-drift-check/checker.mjs +62 -2
  361. package/skills/convergence-monitoring/SIGNALS.md +55 -0
  362. package/skills/discovery/probes/vault-staleness.mjs +37 -13
  363. package/skills/discovery/probes-arch.md +20 -18
  364. package/skills/dispatcher/SKILL.md +3 -2
  365. package/skills/evolve/SKILL.md +65 -26
  366. package/skills/frontmatter-guard/SKILL.md +11 -5
  367. package/skills/npm-publish/SKILL.md +1 -1
  368. package/skills/reconcile/SKILL.md +33 -0
  369. package/skills/remote-offload/SKILL.md +1 -1
  370. package/skills/session-end/SKILL.md +18 -905
  371. package/skills/session-end/phase-3-6-tail.md +10 -3
  372. package/skills/session-end/plan-verification.md +221 -155
  373. package/skills/session-end/references/phase-2-quality-gate.md +93 -0
  374. package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
  375. package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
  376. package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
  377. package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
  378. package/skills/session-end/references/session-summary-template.md +62 -0
  379. package/skills/session-plan/SKILL.md +49 -0
  380. package/skills/session-start/SKILL.md +22 -904
  381. package/skills/session-start/phase-8-5-express-path.md +1 -1
  382. package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
  383. package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
  384. package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
  385. package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
  386. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
  387. package/skills/session-start/references/phase-4-ssot-environment-check.md +160 -0
  388. package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
  389. package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
  390. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
  391. package/skills/vault-sync/SKILL.md +10 -0
  392. package/skills/vault-sync/validator.mjs +21 -27
  393. package/skills/wave-executor/SKILL.md +15 -1
  394. package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
  395. package/skills/wave-executor/references/wave-loop-review.md +570 -0
  396. package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
  397. package/skills/wave-executor/wave-loop.md +14 -1309
  398. package/templates/_shared/journey-manifest.md +10 -6
  399. package/.cursor/commands/autopilot-multi.md +0 -14
  400. package/.cursor/commands/contract-version-bump.md +0 -14
  401. package/.cursor/commands/journey-audit.md +0 -14
  402. package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
  403. package/.cursor/skills/daily/SKILL.md +0 -12
  404. package/.cursor/skills/domain-model/SKILL.md +0 -13
  405. package/.cursor/skills/journey-audit/SKILL.md +0 -13
  406. package/.cursor/skills/skill-creator/SKILL.md +0 -13
  407. package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
  408. package/commands/autopilot-multi.md +0 -74
  409. package/commands/contract-version-bump.md +0 -28
  410. package/commands/journey-audit.md +0 -43
  411. package/pi/prompts/autopilot-multi.md +0 -12
  412. package/pi/prompts/contract-version-bump.md +0 -12
  413. package/pi/prompts/journey-audit.md +0 -12
  414. package/scripts/autopilot-multi.mjs +0 -885
  415. package/scripts/backfill-learnings-expires.mjs +0 -196
  416. package/scripts/backfill-learnings.mjs +0 -203
  417. package/scripts/fleet-instruction-scan.mjs +0 -141
  418. package/scripts/lib/autopilot/dep-graph.mjs +0 -417
  419. package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
  420. package/scripts/lib/webhook-url.mjs +0 -105
  421. package/scripts/lifecycle-sim-v6.mjs +0 -347
  422. package/scripts/migrate-learnings-jsonl.mjs +0 -189
  423. package/scripts/migrate-subagents-jsonl.mjs +0 -196
  424. package/scripts/upload-social-preview.mjs +0 -316
  425. package/skills/_shared/model-selection.md +0 -64
  426. package/skills/contract-version-bump/SKILL.md +0 -219
  427. package/skills/daily/SKILL.md +0 -222
  428. package/skills/daily/generate.sh +0 -92
  429. package/skills/daily/templates/daily.md.tpl +0 -36
  430. package/skills/journey-audit/SKILL.md +0 -270
  431. package/skills/skill-creator/SKILL.md +0 -168
  432. package/skills/ubiquitous-language/SKILL.md +0 -97
  433. package/skills/vault-sync/package-lock.json +0 -40
  434. /package/skills/{domain-model β†’ architecture/references}/ADR-FORMAT.md +0 -0
  435. /package/skills/{domain-model β†’ architecture/references}/CONTEXT-FORMAT.md +0 -0
@@ -268,6 +268,82 @@ export function extractTestCounts(output) {
268
268
  return { passed, failed, total, files };
269
269
  }
270
270
 
271
+ /**
272
+ * ANSI SGR / CSI escapes vitest emits when it believes it writes to a TTY.
273
+ * `runCheck` captures through a pipe, where vitest disables colour β€” but a
274
+ * runner invoked through a pty wrapper, or with `FORCE_COLOR`, still colours
275
+ * its output and would defeat every anchored match below.
276
+ */
277
+ // eslint-disable-next-line no-control-regex
278
+ const ANSI_ESCAPE = /\u001b\[[0-9;]*[A-Za-z]/g;
279
+
280
+ /** Path shape of a test/spec file, as printed by vitest/jest. */
281
+ const TEST_FILE_PATH = String.raw`[^\s()]+\.(?:test|spec)\.[cm]?[jt]sx?`;
282
+
283
+ /**
284
+ * `FAIL <path>` β€” printed once per failing FILE in the "Failed Tests" and
285
+ * "Failed Suites" sections. Two real shapes, both covered by this anchor
286
+ * (measured against vitest 4.1.5, 2026-09-06):
287
+ *
288
+ * ` FAIL dead.test.mjs [ dead.test.mjs ]` ← suite died at import
289
+ * ` FAIL red.test.mjs > red > fails` ← a test case failed
290
+ */
291
+ const FAIL_LINE = new RegExp(String.raw`^\s*FAIL\s+(${TEST_FILE_PATH})(?:\s|$)`);
292
+
293
+ /**
294
+ * `❯ <path> (N tests | M failed)` β€” the per-file line of the run summary.
295
+ *
296
+ * The `(… | … failed)` group is load-bearing, NOT decoration: vitest prefixes
297
+ * STACK FRAMES with the same `❯` and the frame carries the same file name
298
+ * (` ❯ red.test.mjs:3:33`). Requiring the parenthesised counts is what keeps a
299
+ * frame β€” and a PASSING file's summary line, ` ❯ green.test.mjs (2 tests)` β€”
300
+ * out of the result.
301
+ */
302
+ const FILE_SUMMARY_FAIL_LINE = new RegExp(
303
+ String.raw`^\s*❯\s+(${TEST_FILE_PATH})\s+\(\d+\s+tests?[^)]*\|\s*\d+\s+failed`,
304
+ );
305
+
306
+ /**
307
+ * Name the test FILES a failing run blamed β€” the one thing the gate envelope
308
+ * never carried.
309
+ *
310
+ * Measured 2026-09-06: a husky pre-push run blocked `git push origin main` with
311
+ * `{"test":{"status":"fail","total":16417,"passed":16415,"failed":2,
312
+ * "files_total":662,"files_passed":661,"files_failed":1,…}}` β€” a COUNT and no
313
+ * name. `files_failed: 1` says a file died; reconstructing WHICH one cost a
314
+ * full manual re-materialisation of the tracked tree, even though the runner
315
+ * had printed the path in the very output this envelope was built from.
316
+ *
317
+ * Order is first-appearance and duplicates are dropped: vitest names the same
318
+ * file on its summary line AND once per failing case inside it, so a raw match
319
+ * list would repeat one path N times and read as N failing files.
320
+ *
321
+ * Absolute paths are relativised against `cwd` when it is a prefix β€” a gate
322
+ * report is read next to `git status`, so a repo-relative path is the useful
323
+ * form. A path outside `cwd` is left verbatim rather than turned into a `../..`
324
+ * chain that names nothing an operator can act on.
325
+ *
326
+ * @param {string} output - Captured test-runner stdout/stderr.
327
+ * @param {string} [cwd=process.cwd()] - Root to relativise absolute paths against.
328
+ * @returns {string[]} De-duplicated file paths, in first-appearance order.
329
+ */
330
+ export function extractFailedTestFiles(output, cwd = process.cwd()) {
331
+ if (!output || typeof output !== 'string') return [];
332
+
333
+ const prefix = cwd.endsWith('/') ? cwd : `${cwd}/`;
334
+ const seen = new Set();
335
+
336
+ for (const rawLine of output.split('\n')) {
337
+ const line = rawLine.replace(ANSI_ESCAPE, '');
338
+ const match = FAIL_LINE.exec(line) ?? FILE_SUMMARY_FAIL_LINE.exec(line);
339
+ if (!match) continue;
340
+ const file = match[1].startsWith(prefix) ? match[1].slice(prefix.length) : match[1];
341
+ seen.add(file);
342
+ }
343
+
344
+ return [...seen];
345
+ }
346
+
271
347
  /**
272
348
  * Admit a suite-count triple, or refuse to claim a measurement.
273
349
  *
@@ -49,9 +49,26 @@ export const HW_SIGNALS = Object.freeze([
49
49
  // Per-signal detectors β€” stateless, pure functions over arrays
50
50
  // ---------------------------------------------------------------------------
51
51
 
52
+ /**
53
+ * Terminal turn-event names accepted by the OOM detector.
54
+ *
55
+ * `orchestrator.turn.stopped` is the canonical name; `orchestrator.session.stopped`
56
+ * is the DEPRECATED legacy alias `hooks/on-stop.mjs` still emits beside it
57
+ * (carrying `deprecated: true`) for one deprecation generation. BOTH must be
58
+ * accepted here: every OOM record already on disk carries only the legacy name,
59
+ * so a hard switch would blind this detector to the entire history it exists to
60
+ * mine β€” and the `β‰₯2 occurrences` aggregation threshold makes that silent
61
+ * (fewer hits, never an error).
62
+ *
63
+ * REMOVAL: drop `'orchestrator.session.stopped'` from this set on 2027-03-06,
64
+ * the date `hooks/on-stop.mjs` stops emitting the legacy name. See
65
+ * `skills/evolve/SKILL.md` Β§ hardware-pattern, which documents the same window.
66
+ */
67
+ const OOM_TERMINAL_EVENTS = new Set(['orchestrator.turn.stopped', 'orchestrator.session.stopped']);
68
+
52
69
  function detectOomKill(events) {
53
70
  const hits = events.filter((e) => {
54
- if (e.event !== 'orchestrator.session.stopped') return false;
71
+ if (!OOM_TERMINAL_EVENTS.has(e.event)) return false;
55
72
  if (e.exit_code === 137) return true;
56
73
  if (typeof e.error === 'string' && /out of memory|killed/i.test(e.error)) return true;
57
74
  return false;
@@ -4,7 +4,9 @@
4
4
  * Checks: state-md-schema, sessions-jsonl-recent, learnings-prunable,
5
5
  * vault-sync-validator, orphaned-session-lock
6
6
  *
7
- * Stdlib only: node:fs, node:path.
7
+ * Stdlib + intra-repo helpers only (node:fs, node:path, ./helpers.mjs,
8
+ * ../../session-schema/filters.mjs, ../../sessions-canonical.mjs,
9
+ * ../../config/section-extractor.mjs).
8
10
  */
9
11
 
10
12
  import { existsSync } from 'node:fs';
@@ -12,6 +14,7 @@ import { join, relative, sep } from 'node:path';
12
14
 
13
15
  import { parseFrontmatter, safeRead, parseJsonl, pass, fail } from './helpers.mjs';
14
16
  import { isRealSession } from '../../session-schema/filters.mjs';
17
+ import { canonicalizeSessions } from '../../sessions-canonical.mjs';
15
18
  import { findSessionConfigBlock } from '../../config/section-extractor.mjs';
16
19
 
17
20
  // Default session-lock TTL in hours β€” mirrors DEFAULT_TTL_HOURS in
@@ -95,17 +98,34 @@ export function runCategory4(root) {
95
98
  { latestCompletedAt: null, ageInDays: null },
96
99
  'sessions.jsonl is empty'));
97
100
  } else {
98
- // Scan backward past any trailing `status: 'abandoned'` phantom stubs
99
- // (#834, session-close-backfill) to the last REAL session β€” otherwise a
100
- // recent phantom lets a dormant repo pass a check meant to certify
101
- // recent REAL engagement.
101
+ // Most-recent REAL session by completed_at, over the CANONICAL record
102
+ // set (#1209b) β€” canonicalizeSessions() already collapses duplicate
103
+ // `session_id` lines to the newest occurrence, drops the systemic
104
+ // double-stub twin, and removes any record a surviving one
105
+ // `supersedes`. A raw backward-scan-to-last-real-LINE (the pre-#1209b
106
+ // approach) cannot see this: a duplicate id whose LAST occurrence was
107
+ // retroactively corrected to `status: 'abandoned'` still let an
108
+ // EARLIER, now-superseded raw line for the same identity count as
109
+ // "recent". Max, not backward-scan, because canonical order is
110
+ // first-appearance order per id, not completed_at order.
111
+ //
112
+ // `keepUnidentified: true` β€” unlike the bare `readCanonicalSessions()`
113
+ // wrapper, this check has never required a `session_id` (it only ever
114
+ // read `completed_at`/`status`), so silently dropping id-less records
115
+ // would be a behaviour change this migration does not intend.
116
+ const parsedLines = [];
117
+ for (const line of lines) {
118
+ try { parsedLines.push(JSON.parse(line)); } catch { /* skip malformed */ }
119
+ }
120
+ const records = canonicalizeSessions(parsedLines, { keepUnidentified: true });
102
121
  let lastObj = null;
103
- for (let i = lines.length - 1; i >= 0; i -= 1) {
104
- let parsed = null;
105
- try { parsed = JSON.parse(lines[i]); } catch { /* ignore */ }
106
- if (parsed && isRealSession(parsed)) {
107
- lastObj = parsed;
108
- break;
122
+ let lastMs = -Infinity;
123
+ for (const rec of records) {
124
+ if (!isRealSession(rec)) continue;
125
+ const ms = Date.parse(rec?.completed_at);
126
+ if (Number.isFinite(ms) && ms > lastMs) {
127
+ lastMs = ms;
128
+ lastObj = rec;
109
129
  }
110
130
  }
111
131
  const completedAt = lastObj ? lastObj.completed_at : null;
@@ -17,7 +17,7 @@
17
17
  * ram_total_gb: 18,
18
18
  * hostname_hash: '<sha256 hex>',
19
19
  * is_ssh: false,
20
- * platform: 'claude' | 'codex' | 'cursor' | null,
20
+ * platform: 'claude' | 'codex' | 'cursor' | 'pi',
21
21
  * first_seen: '2026-04-19T11:00:00Z',
22
22
  * }
23
23
  *
@@ -40,16 +40,25 @@ import {
40
40
  unlinkSync,
41
41
  } from 'node:fs';
42
42
  import { digestSha256WithSalt } from './crypto-digest-utils.mjs';
43
+ import { resolvePrivateConfigDir } from './config/private-config-dir.mjs';
43
44
  import path from 'node:path';
44
45
  import os from 'node:os';
45
46
  // NOTE (#1072 gate fix): platform.mjs is deliberately NOT imported statically.
46
- // Its module level computes SO_PLATFORM/SO_PLUGIN_ROOT via detectPlatform()/
47
- // resolvePluginRoot(), which walk the filesystem (existsSync + readFileSync of
48
- // package.json up the tree). session-lock.mjs now imports this module, which
49
- // put those import-time reads into every graph that imports session-lock β€”
50
- // including fs-mocked test graphs (tests/lib/vault-mirror/process.test.mjs went
51
- // red: the walk consumed the mocks' sequenced return values). The only two
52
- // consumers (SO_OS/SO_PLATFORM in collectFingerprint) are async β€” load lazily.
47
+ // Its module level used to compute SO_PLATFORM/SO_PLUGIN_ROOT via
48
+ // detectPlatform()/resolvePluginRoot(), which walk the filesystem
49
+ // (existsSync + readFileSync of package.json up the tree). session-lock.mjs
50
+ // now imports this module, which put those import-time reads into every
51
+ // graph that imports session-lock β€” including fs-mocked test graphs
52
+ // (tests/lib/vault-mirror/process.test.mjs went red: the walk consumed the
53
+ // mocks' sequenced return values). The only two consumers in
54
+ // collectFingerprint (os name + platform) are async β€” load lazily.
55
+ //
56
+ // (#1153 P5) platform.mjs now exports memoized lazy getters
57
+ // (getPlatform(), getPluginRoot(), ...); the old SO_PLATFORM/SO_PROJECT_DIR/…
58
+ // names are deprecated `export let` live bindings that stay `undefined`
59
+ // until the matching getter has run elsewhere in the process. Destructure
60
+ // and CALL getPlatform() below β€” never read the deprecated SO_PLATFORM
61
+ // binding directly, or `platform` silently records null/undefined.
53
62
 
54
63
  const FINGERPRINT_TTL_MS = 24 * 60 * 60 * 1000; // 24h
55
64
  const PLACEHOLDER_SALT = 'env-aware-v1-default-salt-replaced-by-owner-yaml';
@@ -345,8 +354,38 @@ export function lockHostCandidate(lock) {
345
354
  // Paths
346
355
  // ---------------------------------------------------------------------------
347
356
 
357
+ /**
358
+ * The host-private config directory β€” every per-host artefact below it
359
+ * (`host-private.json`, `owner.yaml`, the alias ledger) moves with it.
360
+ *
361
+ * Precedence (`SO_CONFIG_HOME` = the dir itself > `XDG_CONFIG_HOME` = its
362
+ * parent > homedir, each trimmed) now lives in ONE place: `resolvePrivateConfigDir`
363
+ * in `config/private-config-dir.mjs` (#1223) β€” a ZERO-IMPORT leaf (`node:os` +
364
+ * `node:path` only).
365
+ *
366
+ * The leaf is deliberately NOT `owner-yaml.mjs`, although that module re-exports
367
+ * the same function: owner-yaml.mjs statically imports `js-yaml`, and THIS module
368
+ * sits on `on-stop.mjs`'s import subgraph (via `session-lock.mjs`). Importing the
369
+ * resolver from owner-yaml.mjs therefore put a bare specifier on THAT subgraph and
370
+ * broke the GH#63 contract that on-stop degrades with one actionable line when
371
+ * node_modules is absent (measured: `ERR_MODULE_NOT_FOUND: Cannot find package
372
+ * 'js-yaml' imported from …/scripts/lib/owner-yaml.mjs`, 2 red tests in
373
+ * tests/hooks/on-stop.test.mjs). Never import owner-yaml.mjs from here.
374
+ *
375
+ * This says nothing about the WHOLE hook import graph, which is NOT bare-free.
376
+ * Measured 2026-09-05 over `hooks/_lib/hook-import-set.json` (150 entries, head
377
+ * 4b45130): two bare specifiers remain, both pre-existing β€” `owner-yaml.mjs β†’
378
+ * js-yaml` (reachable from on-session-start, on-session-end, post-edit-validate,
379
+ * skill-invocation-telemetry) and `scripts/lib/worktree/listing.mjs β†’ zx`
380
+ * (on-session-start). Only `on-stop.mjs` degrades without node_modules today;
381
+ * the other four throw ERR_MODULE_NOT_FOUND. The guard in
382
+ * `tests/hooks/on-stop.test.mjs` allowlists exactly those two, so a THIRD one
383
+ * turns red.
384
+ *
385
+ * @returns {string} absolute path to the host-private config directory
386
+ */
348
387
  function _privateDir() {
349
- return path.join(os.homedir(), '.config', 'session-orchestrator');
388
+ return resolvePrivateConfigDir();
350
389
  }
351
390
 
352
391
  function _privateFile() {
@@ -393,7 +432,7 @@ export async function resolveSalt() {
393
432
  * @param {string} [opts.salt] β€” override salt (tests)
394
433
  */
395
434
  export async function collectFingerprint(opts = {}) {
396
- const { SO_OS, SO_PLATFORM } = await import('./platform.mjs');
435
+ const { SO_OS, getPlatform } = await import('./platform.mjs');
397
436
  const osName = SO_OS;
398
437
  const arch = process.arch;
399
438
  const cpus = os.cpus() || [];
@@ -417,7 +456,7 @@ export async function collectFingerprint(opts = {}) {
417
456
  ram_total_gb: ramTotalGb,
418
457
  hostname_hash: hostnameHash,
419
458
  is_ssh: isSSH(),
420
- platform: SO_PLATFORM || null,
459
+ platform: getPlatform(),
421
460
  first_seen: new Date().toISOString(),
422
461
  };
423
462
  }
@@ -76,9 +76,9 @@
76
76
  * self-relative growth ratchet calibrated just above our own baseline.
77
77
  */
78
78
 
79
- import { existsSync, readFileSync } from 'node:fs';
79
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
80
80
  import { basename, join } from 'node:path';
81
- import { loadApplicableRules } from './rule-loader.mjs';
81
+ import { loadApplicableRules, parseGlobsFrontmatter } from './rule-loader.mjs';
82
82
 
83
83
  /** Default directive ceiling (operator-chosen growth ratchet just above the ~457 baseline). */
84
84
  export const DEFAULT_CEILING = 480;
@@ -140,6 +140,76 @@ export const DEFAULT_CEILING = 480;
140
140
  */
141
141
  export const DEFAULT_BYTE_CEILING = 121000;
142
142
 
143
+ /**
144
+ * Default byte ceiling for the PATH-SCOPED rule surface β€” the third axis, and
145
+ * the one that closes this guard's largest blind spot.
146
+ *
147
+ * Both ceilings above measure ONLY always-on rules (`loadApplicableRules`
148
+ * with `scopePaths: []` returns nothing else), so every `globs:`-scoped rule
149
+ * file was invisible to this guard by construction. That blind spot was
150
+ * MEASURED, not hypothesised: on 2026-09-06 @ `e4674109` the reconciliation
151
+ * engine had accumulated **43 generated rule files / 112,443 B** in
152
+ * `.claude/rules/` β€” 46.2 % of it pure frontmatter+provenance overhead β€” while
153
+ * this guard reported a comfortable 470/480 directives over 15 files and could
154
+ * not have fired on that growth at any ceiling, because it never counted it.
155
+ * A wave agent paid those bytes twice (rule-injection block + native delivery,
156
+ * see `docs/instruction-delivery.md` Β§5).
157
+ *
158
+ * Derivation (HR-105 β€” a threshold whose firing rate nothing records is not a
159
+ * rule): measured 2026-09-06 after the 43β†’8 consolidation AND the
160
+ * harness-parity repair that followed it, the path-scoped surface is
161
+ * **96,757 B over 11 files** (8 consolidated + the 3 hand-written scoped rules
162
+ * `testing.md` / `cli-design.md` / `bash-harness-pitfalls.md`), counted the
163
+ * same way as `totalBytes` β€” frontmatter stripped, via
164
+ * {@link countContentBytes}. RE-MEASURED 2026-09-06 later the same day
165
+ * (`computeInstructionBudget({repoRoot}).bySurface.generated` @ `bc49301b` β†’
166
+ * `{ bytes: 109593, files: 11 }`): the live figure is **109,593 B over the same
167
+ * 11 files**, Γ—1.13 under the unchanged 124,000 ceiling β€” restored prose, not
168
+ * reconciliation growth (the consolidation pass had dropped 10 learnings'
169
+ * bodies, which were written back into the same 8 files).
170
+ *
171
+ * CORRECTED 2026-09-06, one wave after the line above was first written. Two
172
+ * things in that first derivation no longer hold:
173
+ *
174
+ * - The base was **95,277 B**, measured while the 8 consolidated files still
175
+ * carried `globs:` with no `paths:`. Adding `paths:` + `learning-key:` did
176
+ * NOT move this number by a single byte β€” {@link countContentBytes} strips
177
+ * frontmatter β€” so the +1,480 B is entirely the 8 Γ— 185 B provenance note
178
+ * the repair added to the BODIES. Written down because the number moved and
179
+ * the cause was hand-editing, NOT the reconciliation growth this axis
180
+ * exists to catch.
181
+ * - "The ~30 % headroom is deliberately the SAME relative slack the byte
182
+ * ceiling above carries" was simply false. {@link DEFAULT_BYTE_CEILING}
183
+ * carries +5 % (121,000 / 115,730 = 1.046), not +30 %. The axes are NOT
184
+ * calibrated alike, and saying they were hid a 6Γ— difference in tolerance.
185
+ *
186
+ * The ceiling STAYS at 124,000 rather than re-deriving upward to
187
+ * 96,757 Γ— 1.30 β‰ˆ 126,000. The guard has never fired at this base, and raising
188
+ * an unfired ceiling to preserve a round multiplier is the threshold-patch
189
+ * `.claude/rules/development.md` Β§ Guard & Threshold Design forbids. 124,000 is
190
+ * now Γ—1.28 over the live corpus β€” tighter than before, not looser.
191
+ * Re-derive it (never merely raise it) with:
192
+ *
193
+ * `node -e "…parseGlobsFrontmatter over .claude/rules/*.md…"` β€” or simply
194
+ * read `computeInstructionBudget().bySurface.generated` and re-apply Γ—1.30.
195
+ *
196
+ * What makes this ceiling reachable rather than decorative (HR-105 again β€” a
197
+ * threshold nobody can falsify is not a rule): the SAME measurement replayed
198
+ * against `git show HEAD:.claude/rules/*` at `e4674109`, i.e. the tree one
199
+ * commit before the consolidation, returns **137,410 B over 46 path-scoped
200
+ * files** β€” over this ceiling, so the guard WOULD have fired there and is
201
+ * silent at 96,757 B / 11 files now. Firing rate on the two states that exist:
202
+ * 1 of 2. That is the intended condition β€” reconciliation growth, not the
203
+ * hand-written corpus, which contributes 3 of the 11 files.
204
+ *
205
+ * (Note 137,410 is not the 112,443 B the audit quotes for the 43 generated
206
+ * files: that figure is raw bytes INCLUDING frontmatter and EXCLUDING the 3
207
+ * hand-written scoped rules; this axis strips frontmatter and counts all 46.
208
+ * Two different populations β€” see `.claude/rules/measurement-discipline.md`
209
+ * Β§ "the unnamed population".)
210
+ */
211
+ export const DEFAULT_GENERATED_BYTE_CEILING = 124000;
212
+
143
213
  /**
144
214
  * Read the `instruction-budget:` nested block from the `## Session Config`
145
215
  * section of CLAUDE.md (or AGENTS.md) at `repoRoot`. Synchronous + never throws.
@@ -412,6 +482,65 @@ function sumBytes(entries) {
412
482
  return bytes;
413
483
  }
414
484
 
485
+ /**
486
+ * Measure the PATH-SCOPED rule surface: every `.claude/rules/*.md` whose
487
+ * frontmatter carries `globs:` (or its `paths:` alias β€” issue #795), i.e.
488
+ * exactly the complement of the always-on set the three tier surfaces above
489
+ * measure.
490
+ *
491
+ * This cannot reuse `loadApplicableRules`: that loader takes a `scopePaths`
492
+ * list and returns the rules APPLICABLE to it, so with `scopePaths: []` it
493
+ * yields always-on rules only, and with a non-empty list it yields a
494
+ * scope-dependent subset. Neither answers "how big is the path-scoped corpus",
495
+ * which is a property of the DIRECTORY, not of any one wave's file scope. The
496
+ * frontmatter reading is still delegated (`parseGlobsFrontmatter`), so the
497
+ * always-on/path-scoped split stays decided in exactly one place.
498
+ *
499
+ * Named `generated` at the call site because reconciliation output is what
500
+ * grows here, but it deliberately measures every path-scoped file, including
501
+ * the hand-written ones β€” a ceiling that skipped them would report a number
502
+ * the operator cannot reproduce from `ls .claude/rules/` (HR-106: the banner
503
+ * reports what the rule judges).
504
+ *
505
+ * Bytes are counted with {@link countContentBytes} β€” frontmatter stripped β€”
506
+ * so this axis is directly comparable to `totalBytes` and to the tier
507
+ * surfaces. Never throws: an unreadable dir or file yields `{bytes:0,files:0}`
508
+ * / is skipped, matching this module's never-throw posture.
509
+ *
510
+ * @param {string} rulesDir
511
+ * @returns {{ bytes: number, files: number }}
512
+ */
513
+ function measurePathScopedSurface(rulesDir) {
514
+ let names;
515
+ try {
516
+ names = readdirSync(rulesDir);
517
+ } catch {
518
+ return { bytes: 0, files: 0 };
519
+ }
520
+
521
+ let bytes = 0;
522
+ let files = 0;
523
+ for (const name of names) {
524
+ if (!name.endsWith('.md')) continue;
525
+ let content;
526
+ try {
527
+ content = readFileSync(join(rulesDir, name), 'utf8');
528
+ } catch {
529
+ continue; // unreadable file β€” skip, never throw
530
+ }
531
+ let globs;
532
+ try {
533
+ ({ globs } = parseGlobsFrontmatter(content));
534
+ } catch {
535
+ continue;
536
+ }
537
+ if (globs === null) continue; // always-on β€” already counted by the tier surfaces
538
+ files += 1;
539
+ bytes += countContentBytes(content);
540
+ }
541
+ return { bytes, files };
542
+ }
543
+
415
544
  /**
416
545
  * Pure computation β€” always returns the full shape (never null, never throws).
417
546
  *
@@ -517,6 +646,10 @@ export function computeInstructionBudget(opts = {}) {
517
646
  const ceiling = typeof opts.ceiling === 'number' ? opts.ceiling : DEFAULT_CEILING;
518
647
  const byteCeiling =
519
648
  typeof opts.byteCeiling === 'number' ? opts.byteCeiling : DEFAULT_BYTE_CEILING;
649
+ const generatedByteCeiling =
650
+ typeof opts.generatedByteCeiling === 'number'
651
+ ? opts.generatedByteCeiling
652
+ : DEFAULT_GENERATED_BYTE_CEILING;
520
653
  // #893 fix: 'coordinator' used to fall through to the `null` (untiered)
521
654
  // branch below β€” silently measuring the WRONG rule set for a coordinator
522
655
  // context (it never excluded `tier: wave-only`). Now explicitly recognised
@@ -531,11 +664,13 @@ export function computeInstructionBudget(opts = {}) {
531
664
  perFile: [],
532
665
  ceiling,
533
666
  byteCeiling,
667
+ generatedByteCeiling,
534
668
  overDirectiveBudget: false,
535
669
  overByteBudget: false,
670
+ overGeneratedBudget: false,
536
671
  overBudget: false,
537
672
  severity: 'ok',
538
- bySurface: { coordinator: 0, wave: 0, always: 0 },
673
+ bySurface: { coordinator: 0, wave: 0, always: 0, generated: { bytes: 0, files: 0 } },
539
674
  };
540
675
 
541
676
  let allEntries;
@@ -574,6 +709,13 @@ export function computeInstructionBudget(opts = {}) {
574
709
  coordinator: sumBytes(alwaysOnCoordinator),
575
710
  wave: sumBytes(alwaysOnWave),
576
711
  always: sumBytes(alwaysOnAll.filter((e) => e.tier === 'always')),
712
+ // The fourth surface is deliberately a different SHAPE from its three
713
+ // siblings ({bytes, files} vs. a bare byte number): a path-scoped corpus
714
+ // grows by FILE COUNT as much as by size β€” 43 files averaging 2.6 kB is
715
+ // the shape this axis exists to catch β€” and a bare number would hide that.
716
+ // It is also the only surface disjoint from `totalBytes`, which counts
717
+ // always-on rules exclusively.
718
+ generated: measurePathScopedSurface(rulesDir),
577
719
  };
578
720
 
579
721
  // Surface-selected entry set for the PRIMARY totals. `context: null`
@@ -605,7 +747,9 @@ export function computeInstructionBudget(opts = {}) {
605
747
  // long-standing boundary semantics rather than inventing a second rule.
606
748
  const overDirectiveBudget = totalDirectives > ceiling;
607
749
  const overByteBudget = totalBytes > byteCeiling;
608
- const overBudget = overDirectiveBudget || overByteBudget;
750
+ // Third axis, same strict `>` boundary semantics as the two above.
751
+ const overGeneratedBudget = bySurface.generated.bytes > generatedByteCeiling;
752
+ const overBudget = overDirectiveBudget || overByteBudget || overGeneratedBudget;
609
753
 
610
754
  return {
611
755
  totalDirectives,
@@ -613,8 +757,10 @@ export function computeInstructionBudget(opts = {}) {
613
757
  perFile,
614
758
  ceiling,
615
759
  byteCeiling,
760
+ generatedByteCeiling,
616
761
  overDirectiveBudget,
617
762
  overByteBudget,
763
+ overGeneratedBudget,
618
764
  overBudget,
619
765
  severity: overBudget ? 'warn' : 'ok',
620
766
  bySurface,
@@ -655,6 +801,7 @@ export function checkInstructionBudget(opts = {}) {
655
801
  enabled: true,
656
802
  ceiling: DEFAULT_CEILING,
657
803
  'byte-ceiling': DEFAULT_BYTE_CEILING,
804
+ 'generated-byte-ceiling': DEFAULT_GENERATED_BYTE_CEILING,
658
805
  mode: 'warn',
659
806
  };
660
807
  }
@@ -671,9 +818,19 @@ export function checkInstructionBudget(opts = {}) {
671
818
  ? cfg['byte-ceiling']
672
819
  : DEFAULT_BYTE_CEILING;
673
820
 
821
+ // Same precedence as the two axes above: explicit opt > Session Config >
822
+ // module default. Config-key is optional, so a repo that never heard of this
823
+ // axis still gets the measured default rather than `undefined`.
824
+ const generatedByteCeiling =
825
+ typeof opts.generatedByteCeiling === 'number'
826
+ ? opts.generatedByteCeiling
827
+ : typeof cfg['generated-byte-ceiling'] === 'number'
828
+ ? cfg['generated-byte-ceiling']
829
+ : DEFAULT_GENERATED_BYTE_CEILING;
830
+
674
831
  let budget;
675
832
  try {
676
- budget = computeInstructionBudget({ ...opts, ceiling, byteCeiling });
833
+ budget = computeInstructionBudget({ ...opts, ceiling, byteCeiling, generatedByteCeiling });
677
834
  } catch {
678
835
  return null; // never throw out of the banner wrapper
679
836
  }
@@ -689,6 +846,15 @@ export function checkInstructionBudget(opts = {}) {
689
846
  if (budget.overByteBudget) {
690
847
  axes.push(`bytes ${budget.totalBytes} > ${budget.byteCeiling}`);
691
848
  }
849
+ if (budget.overGeneratedBudget) {
850
+ // Reported with its FILE COUNT, because the actionable lever on this axis
851
+ // is consolidating files, not trimming prose inside them (HR-106: the
852
+ // banner reports the number the rule judged).
853
+ axes.push(
854
+ `path-scoped ${budget.bySurface.generated.bytes} B over ` +
855
+ `${budget.bySurface.generated.files} files > ${budget.generatedByteCeiling} B`,
856
+ );
857
+ }
692
858
 
693
859
  // `perFile` arrives sorted DESC by directive count. When ONLY the byte axis
694
860
  // broke, that ordering points at the wrong files β€” re-sort by bytes so the