session-orchestrator 5.2.0 → 5.4.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 (427) hide show
  1. package/.agents/skills/architecture/SKILL.md +3 -1
  2. package/.agents/skills/autopilot/SKILL.md +5 -1
  3. package/.agents/skills/autopilot/agents/openai.yaml +5 -0
  4. package/.agents/skills/bootstrap/SKILL.md +5 -1
  5. package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
  6. package/.agents/skills/brainstorm/SKILL.md +5 -1
  7. package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
  8. package/.agents/skills/claude-md-drift-check/SKILL.md +4 -2
  9. package/.agents/skills/close/SKILL.md +5 -1
  10. package/.agents/skills/close/agents/openai.yaml +5 -0
  11. package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
  12. package/.agents/skills/debug/SKILL.md +5 -1
  13. package/.agents/skills/debug/agents/openai.yaml +5 -0
  14. package/.agents/skills/discovery/SKILL.md +5 -1
  15. package/.agents/skills/discovery/agents/openai.yaml +5 -0
  16. package/.agents/skills/dispatcher/SKILL.md +5 -1
  17. package/.agents/skills/dispatcher/agents/openai.yaml +5 -0
  18. package/.agents/skills/docs-orchestrator/SKILL.md +3 -1
  19. package/.agents/skills/ecosystem-health/SKILL.md +3 -1
  20. package/.agents/skills/eli5/SKILL.md +5 -1
  21. package/.agents/skills/eli5/agents/openai.yaml +5 -0
  22. package/.agents/skills/eval/SKILL.md +6 -2
  23. package/.agents/skills/eval/agents/openai.yaml +5 -0
  24. package/.agents/skills/evolve/SKILL.md +6 -2
  25. package/.agents/skills/evolve/agents/openai.yaml +5 -0
  26. package/.agents/skills/frontmatter-guard/SKILL.md +3 -1
  27. package/.agents/skills/gitlab-ops/SKILL.md +3 -1
  28. package/.agents/skills/gitlab-portfolio/SKILL.md +3 -1
  29. package/.agents/skills/go/SKILL.md +5 -1
  30. package/.agents/skills/go/agents/openai.yaml +5 -0
  31. package/.agents/skills/grill/SKILL.md +5 -1
  32. package/.agents/skills/grill/agents/openai.yaml +5 -0
  33. package/.agents/skills/harness-audit/SKILL.md +5 -1
  34. package/.agents/skills/harness-audit/agents/openai.yaml +5 -0
  35. package/.agents/skills/hook-development/SKILL.md +3 -1
  36. package/.agents/skills/mcp-builder/SKILL.md +3 -1
  37. package/.agents/skills/memory-cleanup/SKILL.md +5 -1
  38. package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
  39. package/.agents/skills/mode-selector/SKILL.md +3 -1
  40. package/.agents/skills/navigator/SKILL.md +22 -0
  41. package/.agents/skills/navigator/agents/openai.yaml +5 -0
  42. package/.agents/skills/npm-publish/SKILL.md +4 -2
  43. package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
  44. package/.agents/skills/persona-panel/SKILL.md +5 -1
  45. package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
  46. package/.agents/skills/plan/SKILL.md +5 -1
  47. package/.agents/skills/plan/agents/openai.yaml +5 -0
  48. package/.agents/skills/playwright-driver/SKILL.md +3 -1
  49. package/.agents/skills/portfolio/SKILL.md +5 -1
  50. package/.agents/skills/portfolio/agents/openai.yaml +5 -0
  51. package/.agents/skills/quality-gates/SKILL.md +3 -1
  52. package/.agents/skills/reconcile/SKILL.md +5 -1
  53. package/.agents/skills/reconcile/agents/openai.yaml +5 -0
  54. package/.agents/skills/release/SKILL.md +5 -1
  55. package/.agents/skills/release/agents/openai.yaml +5 -0
  56. package/.agents/skills/remote-offload/SKILL.md +3 -1
  57. package/.agents/skills/repo-audit/SKILL.md +5 -1
  58. package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
  59. package/.agents/skills/session/SKILL.md +21 -0
  60. package/.agents/skills/session/agents/openai.yaml +5 -0
  61. package/.agents/skills/session-end/SKILL.md +3 -1
  62. package/.agents/skills/session-plan/SKILL.md +3 -1
  63. package/.agents/skills/session-start/SKILL.md +3 -1
  64. package/.agents/skills/spinout/SKILL.md +5 -1
  65. package/.agents/skills/spinout/agents/openai.yaml +5 -0
  66. package/.agents/skills/sunset-review/SKILL.md +5 -1
  67. package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
  68. package/.agents/skills/templates-ack/SKILL.md +21 -0
  69. package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
  70. package/.agents/skills/test/SKILL.md +5 -1
  71. package/.agents/skills/test/agents/openai.yaml +5 -0
  72. package/.agents/skills/test-audit/SKILL.md +21 -0
  73. package/.agents/skills/test-audit/agents/openai.yaml +5 -0
  74. package/.agents/skills/test-runner/SKILL.md +3 -1
  75. package/.agents/skills/tmux-layout/SKILL.md +3 -1
  76. package/.agents/skills/using-orchestrator/SKILL.md +3 -1
  77. package/.agents/skills/ux-grill/SKILL.md +5 -1
  78. package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
  79. package/.agents/skills/vault-mirror/SKILL.md +3 -1
  80. package/.agents/skills/vault-sync/SKILL.md +3 -1
  81. package/.agents/skills/wave-executor/SKILL.md +3 -1
  82. package/.agents/skills/write-executable-plan/SKILL.md +3 -1
  83. package/.claude-plugin/marketplace.json +1 -1
  84. package/.claude-plugin/plugin.json +1 -1
  85. package/.codex-plugin/plugin.json +4 -4
  86. package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -1
  87. package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -3
  88. package/.codex-plugin/skills/eval/SKILL.md +1 -1
  89. package/.codex-plugin/skills/evolve/SKILL.md +1 -1
  90. package/.codex-plugin/skills/navigator/SKILL.md +22 -0
  91. package/.codex-plugin/skills/{convergence-monitoring → navigator}/agents/openai.yaml +2 -2
  92. package/.codex-plugin/skills/npm-publish/SKILL.md +1 -3
  93. package/.codex-plugin/skills/session/SKILL.md +1 -1
  94. package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
  95. package/.codex-plugin/skills/{npm-publish → test-audit}/agents/openai.yaml +1 -1
  96. package/.cursor/commands/eval.md +1 -1
  97. package/.cursor/commands/navigator.md +15 -0
  98. package/.cursor/commands/session.md +1 -1
  99. package/.cursor/commands/test-audit.md +14 -0
  100. package/.cursor/rules/000-session-orchestrator.mdc +0 -2
  101. package/.cursor/rules/050-plan.mdc +1 -1
  102. package/.cursor/rules/060-evolve.mdc +27 -12
  103. package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
  104. package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
  105. package/.cursor/skills/eval/SKILL.md +1 -1
  106. package/.cursor/skills/navigator/SKILL.md +13 -0
  107. package/.cursor/skills/npm-publish/SKILL.md +1 -0
  108. package/.cursor/skills/test-audit/SKILL.md +12 -0
  109. package/.cursor-plugin/plugin.json +1 -1
  110. package/.orchestrator/policy/blocked-commands.json +12 -3
  111. package/AGENTS.md +9 -30
  112. package/CHANGELOG.md +206 -0
  113. package/README.md +13 -9
  114. package/SECURITY.md +12 -0
  115. package/agents/dialectic-deriver.md +13 -10
  116. package/agents/eval-judge.md +67 -45
  117. package/agents/skill-applied-judge.md +34 -19
  118. package/commands/session.md +7 -3
  119. package/docs/agent-authoring.md +2 -2
  120. package/docs/baseline.md +22 -7
  121. package/docs/ci-setup.md +57 -4
  122. package/docs/codex-setup.md +14 -2
  123. package/docs/components.md +12 -10
  124. package/docs/events-schema.md +65 -15
  125. package/docs/persona-panel.md +5 -4
  126. package/docs/rule-authoring.md +84 -17
  127. package/docs/scope-collision-guard.md +26 -4
  128. package/docs/session-config-reference.md +154 -12
  129. package/docs/session-config-template.md +41 -3
  130. package/docs/telemetry.md +2 -0
  131. package/hooks/_lib/hook-import-set.json +87 -6
  132. package/hooks/_lib/subagent-transcript.mjs +582 -31
  133. package/hooks/config-protection.mjs +11 -3
  134. package/hooks/cwd-change-restore.mjs +11 -3
  135. package/hooks/enforce-commands.mjs +70 -23
  136. package/hooks/enforce-scope.mjs +143 -33
  137. package/hooks/hooks-codex.json +1 -1
  138. package/hooks/hooks-cursor.json +5 -0
  139. package/hooks/hooks-pi.json +5 -0
  140. package/hooks/hooks.json +6 -1
  141. package/hooks/loop-guard.mjs +11 -3
  142. package/hooks/on-session-end.mjs +58 -23
  143. package/hooks/on-session-start.mjs +101 -11
  144. package/hooks/on-stop.mjs +170 -22
  145. package/hooks/operator-steer.mjs +11 -3
  146. package/hooks/post-bash-issue-budget-refund.mjs +18 -8
  147. package/hooks/post-bash-write-verify.mjs +3 -2
  148. package/hooks/post-edit-import-probe.mjs +17 -9
  149. package/hooks/post-edit-validate.mjs +13 -5
  150. package/hooks/post-subagent-discovery-validator.mjs +98 -13
  151. package/hooks/post-tool-batch-wave-signal.mjs +202 -38
  152. package/hooks/post-tool-failure-corrective-context.mjs +11 -5
  153. package/hooks/post-tooluse-frontend-slop.mjs +10 -4
  154. package/hooks/pre-auq-clarity.mjs +15 -2
  155. package/hooks/pre-bash-destructive-guard.mjs +80 -9
  156. package/hooks/pre-bash-issue-budget.mjs +16 -11
  157. package/hooks/pre-bash-memory-propose-audit.mjs +137 -56
  158. package/hooks/pre-bash-sessions-ledger-guard.mjs +419 -22
  159. package/hooks/pre-bash-staging-fence.mjs +335 -31
  160. package/hooks/pre-bash-templates-first.mjs +19 -14
  161. package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
  162. package/hooks/pre-task-scope-disjoint.mjs +792 -74
  163. package/hooks/subagent-telemetry.mjs +111 -38
  164. package/hooks/wave-scope-commit-guard.mjs +197 -100
  165. package/monitors/monitors.json +1 -1
  166. package/output-styles/wave-summary.md +1 -1
  167. package/package.json +3 -3
  168. package/pi/prompts/eval.md +1 -1
  169. package/pi/prompts/navigator.md +12 -0
  170. package/pi/prompts/session.md +1 -1
  171. package/pi/prompts/test-audit.md +12 -0
  172. package/rules/README.md +7 -2
  173. package/rules/_index.md +3 -3
  174. package/rules/always-on/bash-harness-pitfalls.md +7 -1
  175. package/rules/opt-in-domain/prompt-caching.md +6 -2
  176. package/rules/opt-in-stack/backend-data.md +10 -2
  177. package/rules/opt-in-stack/backend.md +10 -4
  178. package/rules/opt-in-stack/frontend.md +7 -2
  179. package/rules/opt-in-stack/security-web.md +10 -4
  180. package/rules/opt-in-stack/swift.md +6 -2
  181. package/scripts/apply-session-learnings.mjs +492 -0
  182. package/scripts/archive-closed-prds.mjs +2 -1
  183. package/scripts/autopilot.mjs +70 -53
  184. package/scripts/backfill-abandoned-sessions.mjs +118 -16
  185. package/scripts/baseline-archetypes.mjs +1 -1
  186. package/scripts/check-package-manager.mjs +62 -5
  187. package/scripts/check-sessions-integrity.mjs +300 -0
  188. package/scripts/cursor-install.mjs +1 -4
  189. package/scripts/dialectic-deriver.mjs +50 -13
  190. package/scripts/emit-session.mjs +371 -31
  191. package/scripts/eval-session.mjs +65 -3
  192. package/scripts/export-hw-learnings.mjs +22 -3
  193. package/scripts/fleet-checkin.mjs +147 -0
  194. package/scripts/generate-agents-skills.mjs +102 -29
  195. package/scripts/generate-cursor-adapter.mjs +61 -16
  196. package/scripts/lib/agent-frontmatter.mjs +3 -3
  197. package/scripts/lib/agent-status.mjs +2 -31
  198. package/scripts/lib/auq/clarity.mjs +10 -2
  199. package/scripts/lib/auq/parse.mjs +12 -31
  200. package/scripts/lib/auq/schema.mjs +56 -41
  201. package/scripts/lib/auto-dialectic.mjs +304 -15
  202. package/scripts/lib/auto-dream.mjs +7 -6
  203. package/scripts/lib/autopilot/flags.mjs +18 -2
  204. package/scripts/lib/autopilot/kill-switches.mjs +6 -3
  205. package/scripts/lib/autopilot/loop.mjs +19 -2
  206. package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
  207. package/scripts/lib/baseline-archetypes.mjs +41 -22
  208. package/scripts/lib/ci-status-banner.mjs +376 -16
  209. package/scripts/lib/command-blocker.mjs +275 -28
  210. package/scripts/lib/config/dialectic.mjs +12 -3
  211. package/scripts/lib/config/gate.mjs +74 -0
  212. package/scripts/lib/config/host-paths.mjs +79 -0
  213. package/scripts/lib/config/persona-gate-wave.mjs +3 -3
  214. package/scripts/lib/config/reaper.mjs +163 -0
  215. package/scripts/lib/config-schema.mjs +13 -0
  216. package/scripts/lib/config.mjs +85 -10
  217. package/scripts/lib/convergence-monitor.mjs +74 -11
  218. package/scripts/lib/ecosystem-health.mjs +11 -0
  219. package/scripts/lib/eval/engine.mjs +421 -53
  220. package/scripts/lib/eval/judge.mjs +463 -40
  221. package/scripts/lib/eval/schema.mjs +10 -1
  222. package/scripts/lib/events-rotation.mjs +221 -25
  223. package/scripts/lib/events-schema.mjs +114 -0
  224. package/scripts/lib/events.mjs +545 -7
  225. package/scripts/lib/fleet-protocol.mjs +270 -0
  226. package/scripts/lib/frontmatter-guard.mjs +21 -10
  227. package/scripts/lib/gates/gate-baseline.mjs +40 -5
  228. package/scripts/lib/gates/gate-full.mjs +35 -5
  229. package/scripts/lib/gates/gate-helpers.mjs +336 -23
  230. package/scripts/lib/gates/gate-incremental.mjs +49 -23
  231. package/scripts/lib/gates/gate-per-file.mjs +42 -8
  232. package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
  233. package/scripts/lib/instruction-budget-guard.mjs +208 -8
  234. package/scripts/lib/io.mjs +42 -8
  235. package/scripts/lib/issue-close-strip-labels.mjs +207 -49
  236. package/scripts/lib/js-mask.mjs +197 -0
  237. package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
  238. package/scripts/lib/learnings/expiry-sweep.mjs +34 -3
  239. package/scripts/lib/learnings/io.mjs +154 -26
  240. package/scripts/lib/learnings/schema.mjs +163 -15
  241. package/scripts/lib/learnings/sizing-subject.mjs +2 -2
  242. package/scripts/lib/maintenance-due-banner.mjs +53 -88
  243. package/scripts/lib/memory-proposals/schema.mjs +11 -5
  244. package/scripts/lib/named-baseline-resolver.mjs +53 -28
  245. package/scripts/lib/named-vault-resolver.mjs +104 -5
  246. package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
  247. package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
  248. package/scripts/lib/orphan-reaper/reaper-audit.mjs +291 -0
  249. package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
  250. package/scripts/lib/orphan-reaper/scan-throttle.mjs +92 -0
  251. package/scripts/lib/orphan-reaper/scan.mjs +506 -0
  252. package/scripts/lib/orphan-reaper.mjs +316 -0
  253. package/scripts/lib/owner-yaml.mjs +104 -6
  254. package/scripts/lib/peer-cards/merger.mjs +48 -10
  255. package/scripts/lib/peer-cards/reader.mjs +78 -2
  256. package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
  257. package/scripts/lib/process-group.mjs +950 -0
  258. package/scripts/lib/project-hygiene.mjs +21 -1
  259. package/scripts/lib/quality-gate.mjs +152 -33
  260. package/scripts/lib/reconcile/backlog.mjs +368 -0
  261. package/scripts/lib/reconcile/engine.mjs +55 -188
  262. package/scripts/lib/reconcile/rule-expiry-sweep.mjs +302 -60
  263. package/scripts/lib/reconcile/sanitize.mjs +69 -3
  264. package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
  265. package/scripts/lib/resource-probe/parsers.mjs +31 -0
  266. package/scripts/lib/resource-probe.mjs +1 -1
  267. package/scripts/lib/rule-loader.mjs +141 -74
  268. package/scripts/lib/rules-sync.mjs +27 -12
  269. package/scripts/lib/scope-baseline.mjs +3 -4
  270. package/scripts/lib/scope-echo.mjs +39 -2
  271. package/scripts/lib/scope-gate.mjs +605 -1
  272. package/scripts/lib/session-close-backfill.mjs +33 -6
  273. package/scripts/lib/session-id.mjs +9 -20
  274. package/scripts/lib/session-invocation.mjs +20 -0
  275. package/scripts/lib/session-record-repair.mjs +183 -26
  276. package/scripts/lib/session-registry.mjs +27 -2
  277. package/scripts/lib/session-schema/constants.mjs +56 -2
  278. package/scripts/lib/session-schema/normalizer.mjs +56 -4
  279. package/scripts/lib/session-schema/validator.mjs +28 -1
  280. package/scripts/lib/session-schema.mjs +8 -3
  281. package/scripts/lib/session-start-probes.mjs +95 -10
  282. package/scripts/lib/session-token-rollup.mjs +245 -52
  283. package/scripts/lib/sessions-canonical.mjs +118 -4
  284. package/scripts/lib/sessions-integrity-banner.mjs +7 -1
  285. package/scripts/lib/sessions-staleness-banner.mjs +193 -51
  286. package/scripts/lib/sh-quote.mjs +29 -0
  287. package/scripts/lib/skill-evidence-window.mjs +891 -0
  288. package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
  289. package/scripts/lib/skill-evolution/engine.mjs +18 -9
  290. package/scripts/lib/skill-judge.mjs +45 -3
  291. package/scripts/lib/subagents-schema.mjs +30 -3
  292. package/scripts/lib/tail-window.mjs +56 -0
  293. package/scripts/lib/telemetry/pricing.mjs +118 -71
  294. package/scripts/lib/telemetry/schema.mjs +30 -0
  295. package/scripts/lib/telemetry/sync.mjs +61 -6
  296. package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
  297. package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
  298. package/scripts/lib/tests-src-ratio.mjs +140 -23
  299. package/scripts/lib/tmux-layout/layouts.mjs +1 -11
  300. package/scripts/lib/tmux-layout/telemetry-stats.mjs +72 -13
  301. package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
  302. package/scripts/lib/user-invocable-skills.mjs +23 -3
  303. package/scripts/lib/ux-grill/reconcile.mjs +48 -22
  304. package/scripts/lib/validate/check-agents-skills.mjs +26 -15
  305. package/scripts/lib/validate/check-agents.mjs +1 -1
  306. package/scripts/lib/validate/check-cursor-adapter.mjs +1 -0
  307. package/scripts/lib/validate/check-entry-guard.mjs +13 -50
  308. package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
  309. package/scripts/lib/validate/check-hooks-symmetry.mjs +5 -0
  310. package/scripts/lib/validate/check-learnings-shell-writes.mjs +214 -0
  311. package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
  312. package/scripts/lib/validate/check-rules.mjs +7 -5
  313. package/scripts/lib/validate/check-skill-links.mjs +9 -1
  314. package/scripts/lib/validate/check-skill-script-paths.mjs +239 -27
  315. package/scripts/lib/validate/check-test-git-config-target.mjs +24 -34
  316. package/scripts/lib/validate/check-test-value-bans.mjs +87 -14
  317. package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
  318. package/scripts/lib/validate/check-untracked-test-deps.mjs +7 -102
  319. package/scripts/lib/validate/check-unwired-features.mjs +130 -27
  320. package/scripts/lib/validate/check-validator-registration.mjs +34 -10
  321. package/scripts/lib/validate/confidential-names.mjs +10 -0
  322. package/scripts/lib/validate-vendored-rules.mjs +210 -84
  323. package/scripts/lib/vault-mirror/namespace.mjs +46 -8
  324. package/scripts/lib/vault-mirror/process.mjs +10 -3
  325. package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
  326. package/scripts/lib/vault-mirror/telemetry.mjs +8 -1
  327. package/scripts/lib/vault-status/board-writer.mjs +48 -11
  328. package/scripts/lib/vault-status/narrative-mirror.mjs +71 -16
  329. package/scripts/lib/vault-yaml.mjs +118 -0
  330. package/scripts/lib/wave-executor/dispatch-common.mjs +10 -18
  331. package/scripts/lib/wave-executor/foreign-dispatch.mjs +1 -1
  332. package/scripts/lib/wave-executor/remote-dispatch.mjs +1 -1
  333. package/scripts/lib/wave-resource-gate.mjs +34 -8
  334. package/scripts/lib/worktree/lifecycle.mjs +153 -1
  335. package/scripts/memory-propose.mjs +18 -5
  336. package/scripts/release-session-lock.mjs +306 -0
  337. package/scripts/release.mjs +40 -8
  338. package/scripts/repair-invalid-sessions.mjs +37 -4
  339. package/scripts/resolve-session-invocation.mjs +59 -0
  340. package/scripts/run-migrate-v2-cross-repo.mjs +158 -30
  341. package/scripts/run-quality-gate.mjs +357 -197
  342. package/scripts/site-numbers.mjs +163 -5
  343. package/scripts/sweep-expired-learnings.mjs +56 -5
  344. package/scripts/sweep-expired-rules.mjs +16 -4
  345. package/scripts/validate-plugin.mjs +19 -0
  346. package/scripts/validate-wave-scope.mjs +32 -105
  347. package/scripts/vault-integration-watcher.mjs +2 -1
  348. package/scripts/vault-mirror.mjs +81 -47
  349. package/scripts/wave-scope-binding.mjs +2 -1
  350. package/skills/_shared/config-reading.md +3 -3
  351. package/skills/_shared/fleet-protocol.md +131 -0
  352. package/skills/_shared/platform-tools.md +23 -11
  353. package/skills/_shared/state-ownership.md +5 -0
  354. package/skills/autopilot/SKILL.md +47 -18
  355. package/skills/bootstrap/SKILL.md +1 -1
  356. package/skills/bootstrap/_shared-template.md +1 -1
  357. package/skills/bootstrap/fast-template.md +1 -1
  358. package/skills/bootstrap/private-contract.md +2 -1
  359. package/skills/bootstrap/public-fallback.md +6 -3
  360. package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
  361. package/skills/claude-md-drift-check/SKILL.md +10 -10
  362. package/skills/claude-md-drift-check/checker.mjs +26 -18
  363. package/skills/convergence-monitoring/README.md +8 -1
  364. package/skills/convergence-monitoring/SIGNALS.md +50 -6
  365. package/skills/convergence-monitoring/SKILL.md +15 -6
  366. package/skills/eval/SKILL.md +39 -24
  367. package/skills/eval/rubric-v1.md +1 -0
  368. package/skills/eval/rubric-v2.md +457 -0
  369. package/skills/evolve/SKILL.md +3 -3
  370. package/skills/evolve/references/evolve-analyze-mode.md +10 -6
  371. package/skills/evolve/references/evolve-dialectic-mode.md +42 -25
  372. package/skills/gitlab-ops/SKILL.md +3 -2
  373. package/skills/memory-cleanup/SKILL.md +1 -1
  374. package/skills/navigator/SKILL.md +193 -0
  375. package/skills/navigator/references/account-check.sh +87 -0
  376. package/skills/navigator/references/fleet-decisions.mjs +231 -0
  377. package/skills/navigator/references/slot-check.sh +114 -0
  378. package/skills/npm-publish/SKILL.md +1 -1
  379. package/skills/persona-panel/SKILL.md +2 -2
  380. package/skills/persona-panel/persona-format.md +2 -2
  381. package/skills/persona-panel/presets/designer-lens.md +1 -1
  382. package/skills/persona-panel/presets/engineer-lens.md +1 -1
  383. package/skills/persona-panel/presets/pm-lens.md +1 -1
  384. package/skills/plan/mode-retro.md +19 -4
  385. package/skills/quality-gates/SKILL.md +6 -4
  386. package/skills/reconcile/SKILL.md +11 -0
  387. package/skills/remote-offload/SKILL.md +4 -2
  388. package/skills/session-end/SKILL.md +13 -16
  389. package/skills/session-end/discovery-scan.md +1 -1
  390. package/skills/session-end/learning-patterns.md +22 -12
  391. package/skills/session-end/metrics-collection.md +5 -6
  392. package/skills/session-end/phase-3-6-tail.md +55 -9
  393. package/skills/session-end/references/phase-3-documentation-updates.md +2 -2
  394. package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
  395. package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
  396. package/skills/session-end/references/session-summary-template.md +1 -1
  397. package/skills/session-end/session-metrics-write.md +32 -60
  398. package/skills/session-plan/SKILL.md +17 -8
  399. package/skills/session-plan/references/session-plan-task-classification.md +2 -2
  400. package/skills/session-start/SKILL.md +6 -0
  401. package/skills/session-start/references/operations-contract.md +4 -0
  402. package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
  403. package/skills/session-start/references/phase-4-ssot-environment-check.md +4 -3
  404. package/skills/test-audit/SKILL.md +152 -0
  405. package/skills/test-audit/references/campaign.md +111 -0
  406. package/skills/test-audit/references/mutate.sh +50 -0
  407. package/skills/ux-grill/SKILL.md +1 -1
  408. package/skills/wave-executor/SKILL.md +9 -5
  409. package/skills/wave-executor/circuit-breaker.md +2 -0
  410. package/skills/wave-executor/references/wave-executor-state-init.md +8 -3
  411. package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
  412. package/skills/wave-executor/references/wave-loop-review.md +3 -5
  413. package/templates/personas/README.md +5 -5
  414. package/templates/personas/accounting-compliance.v1.md +1 -1
  415. package/templates/personas/accounting-tax-advisor.v1.md +1 -1
  416. package/templates/personas/buyer-p1-cto.v1.md +1 -1
  417. package/templates/personas/buyer-p2-kanzlei.v1.md +1 -1
  418. package/templates/personas/buyer-p3-build.v1.md +1 -1
  419. package/templates/personas/buyer-p4-tech-veto.v1.md +1 -1
  420. package/templates/personas/buyer-p5-solo.v1.md +1 -1
  421. package/templates/personas/buyer-p6-ld.v1.md +1 -1
  422. package/templates/personas/klima-ai-expert.v1.md +1 -1
  423. package/templates/personas/klima-physicist.v1.md +1 -1
  424. package/.cursor/commands/convergence-monitoring.md +0 -13
  425. package/.cursor/commands/npm-publish.md +0 -13
  426. package/pi/prompts/convergence-monitoring.md +0 -11
  427. package/pi/prompts/npm-publish.md +0 -11
@@ -47,6 +47,16 @@
47
47
  * tier-gate conditionals `applyGates` already implements (the `context`
48
48
  * param below is the exact mechanism rule-loader exposes for this).
49
49
  *
50
+ * #1449: that membership now matches what Claude Code actually loads. The
51
+ * loader used to skip a leading provenance comment before `---`, so a rule
52
+ * Claude Code loads always-on (it reads frontmatter only from line 1) counted
53
+ * here as path-scoped, and every consumer's always-on total was short by the
54
+ * vendored rules carrying that header. Such a file now counts as always-on —
55
+ * its displaced frontmatter as body bytes, since that is how Claude Code
56
+ * delivers it — and is listed in `defects` and on its own banner line. The
57
+ * list is report-only: it is not an `overBudget` term, so a defect never turns
58
+ * a budget verdict red.
59
+ *
50
60
  * Cross-references:
51
61
  * - "2026-06-20 instruction-budget audit" (#668 / #687; archived in the private Meta-Vault)
52
62
  * - scripts/lib/rule-loader.mjs (always-on classification + tier-gate SSOT)
@@ -78,7 +88,12 @@
78
88
 
79
89
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
80
90
  import { basename, join } from 'node:path';
81
- import { loadApplicableRules, parseGlobsFrontmatter } from './rule-loader.mjs';
91
+ import { scanEventsBackwards } from './events.mjs';
92
+ import {
93
+ FRONTMATTER_NOT_AT_TOP,
94
+ loadApplicableRules,
95
+ parseGlobsFrontmatter,
96
+ } from './rule-loader.mjs';
82
97
 
83
98
  /** Default directive ceiling (operator-chosen growth ratchet just above the ~457 baseline). */
84
99
  export const DEFAULT_CEILING = 480;
@@ -443,6 +458,35 @@ export const DEFAULT_GENERATED_BYTE_CEILING = 95000;
443
458
  */
444
459
  export const DEFAULT_PATH_SCOPED_BYTE_CEILING = 124000;
445
460
 
461
+ /**
462
+ * Share of {@link DEFAULT_PATH_SCOPED_BYTE_CEILING} at which the banner warns
463
+ * BEFORE the writer refuses (#1419).
464
+ *
465
+ * The defect #1419 records is not the ceiling but its TIMING: it becomes
466
+ * visible only inside `writeApprovedRules`, after the operator has already
467
+ * decided. Measured on that case (2026-09-20, /reconcile run): 117,973 of
468
+ * 124,000 B = 95.1 % — ~6 kB of headroom holding back `capped: 91`
469
+ * candidates, and `overPathScopedBudget` (a HARD `bytes > ceiling` test) did
470
+ * not fire, so nothing warned.
471
+ *
472
+ * 90 % is the distance of ~4 materialised rules: 10 % of 124,000 B = 12,400 B
473
+ * against the ~1.5 kB a generated rule file occupied on the #1419 tree — i.e.
474
+ * roughly four more accepted learnings until the hard break. A fixed fraction,
475
+ * deliberately not a per-repo calibration: it cannot know how large THIS
476
+ * repo's next rule is, and a second calibrated number would need its own
477
+ * falsification record.
478
+ *
479
+ * BV-004 ceiling + revisit trigger (HR-105): measure this line's firing rate
480
+ * after ~20 sessions from the probe records in
481
+ * `.orchestrator/metrics/events.jsonl`
482
+ * (`jq -c 'select(.event=="orchestrator.probes.completed")
483
+ * | .probes[] | select(.id=="instruction-budget")' …` — count `ran-warn`
484
+ * against the record total).
485
+ * Above ~10 % the instrument is RE-AIMED per HR-101 — neither lowered nor
486
+ * silenced; below it, leave it alone.
487
+ */
488
+ export const PATH_SCOPED_NEAR_THRESHOLD = 0.9;
489
+
446
490
  /**
447
491
  * Read the `instruction-budget:` nested block from the `## Session Config`
448
492
  * section of CLAUDE.md (or AGENTS.md) at `repoRoot`. Synchronous + never throws.
@@ -1025,12 +1069,18 @@ function measureRuleCorpora(rulesDir) {
1025
1069
  * bySurface: { coordinator: number, wave: number, always: number,
1026
1070
  * generated: {bytes: number, files: number},
1027
1071
  * pathScoped: {bytes: number, files: number} },
1072
+ * defects: string[],
1028
1073
  * }}
1029
- * perFile is sorted DESC by count. On missing/unreadable dir →
1074
+ * perFile is sorted DESC by count. `defects` (#1449) lists, sorted by name,
1075
+ * the rule files whose frontmatter does not start on line 1 — Claude Code
1076
+ * ignores it, so they load always-on and are counted as such. It is
1077
+ * independent of `context` (such a file has no tier to gate on) and never
1078
+ * feeds `overBudget`. On missing/unreadable dir →
1030
1079
  * { totalDirectives: 0, totalBytes: 0, perFile: [], ceiling, byteCeiling,
1031
1080
  * overDirectiveBudget: false, overByteBudget: false, overBudget: false,
1032
1081
  * severity: 'ok', bySurface: { coordinator: 0, wave: 0, always: 0,
1033
- * generated: {bytes:0,files:0}, pathScoped: {bytes:0,files:0} } }.
1082
+ * generated: {bytes:0,files:0}, pathScoped: {bytes:0,files:0} },
1083
+ * defects: [] }.
1034
1084
  *
1035
1085
  * #931a verdict rule — `overBudget` is the OR of the two axes
1036
1086
  * (`overDirectiveBudget || overByteBudget`), NOT a per-axis severity split:
@@ -1135,6 +1185,7 @@ export function computeInstructionBudget(opts = {}) {
1135
1185
  generated: { bytes: 0, files: 0 },
1136
1186
  pathScoped: { bytes: 0, files: 0 },
1137
1187
  },
1188
+ defects: [],
1138
1189
  };
1139
1190
 
1140
1191
  let allEntries;
@@ -1226,6 +1277,13 @@ export function computeInstructionBudget(opts = {}) {
1226
1277
  const overPathScopedBudget = bySurface.pathScoped.bytes > pathScopedByteCeiling;
1227
1278
  const overBudget = overDirectiveBudget || overByteBudget || overGeneratedBudget || overPathScopedBudget;
1228
1279
 
1280
+ // #1449: read off the untiered list — a defective file carries no meta, so
1281
+ // no tier gate drops it and `alwaysOnAll` holds every one of them.
1282
+ const defects = alwaysOnAll
1283
+ .filter((e) => e.defect === FRONTMATTER_NOT_AT_TOP)
1284
+ .map((e) => basename(e.path))
1285
+ .sort((a, b) => a.localeCompare(b));
1286
+
1229
1287
  return {
1230
1288
  totalDirectives,
1231
1289
  totalBytes,
@@ -1241,9 +1299,102 @@ export function computeInstructionBudget(opts = {}) {
1241
1299
  overBudget,
1242
1300
  severity: overBudget ? 'warn' : 'ok',
1243
1301
  bySurface,
1302
+ defects,
1244
1303
  };
1245
1304
  }
1246
1305
 
1306
+ /** Ledger event the backlog number below is read from (`reconcile/engine.mjs`). */
1307
+ const RECONCILE_EVENT = 'orchestrator.reconcile.completed';
1308
+
1309
+ /**
1310
+ * Wall-clock budget for the backwards ledger walk. Deliberately small: this
1311
+ * runs inside a session-start probe whose own budget is 2 s, and the number it
1312
+ * fetches is DECORATION on a line that is already actionable without it.
1313
+ */
1314
+ const RECONCILE_SCAN_BUDGET_MS = 150;
1315
+
1316
+ /**
1317
+ * `capped` from the most recent `orchestrator.reconcile.completed` record —
1318
+ * how many rule-eligible learnings the last /reconcile run held back.
1319
+ *
1320
+ * Three-state on purpose (`feedback_missing_measurement_looks_like_zero`):
1321
+ * `null` means NOT MEASURED (no ledger, no such record, unreadable source, or
1322
+ * the walk ran out of budget), which the caller must render as ABSENCE — never
1323
+ * as `0`, which is a measured "nothing waiting".
1324
+ *
1325
+ * @param {string|undefined} repoRoot
1326
+ * @returns {number|null}
1327
+ */
1328
+ function readLastReconcileCapped(repoRoot) {
1329
+ if (typeof repoRoot !== 'string' || repoRoot === '') return null;
1330
+ const filePath = join(repoRoot, '.orchestrator', 'metrics', 'events.jsonl');
1331
+ let capped = null;
1332
+ let scan;
1333
+ try {
1334
+ scan = scanEventsBackwards({
1335
+ filePath,
1336
+ budgetMs: RECONCILE_SCAN_BUDGET_MS,
1337
+ // Cheap raw-line pre-filter before JSON.parse — same reason as
1338
+ // `maintenance-due-banner.mjs`: parsing every line of a multi-MB ledger
1339
+ // to answer one question is the cost this option exists to avoid.
1340
+ filter: RECONCILE_EVENT,
1341
+ onRecord: (rec) => {
1342
+ if (rec?.event !== RECONCILE_EVENT) return false;
1343
+ capped = Number.isFinite(rec.capped) ? rec.capped : null;
1344
+ return true; // newest record wins; stop the walk
1345
+ },
1346
+ });
1347
+ } catch {
1348
+ return null; // never throw out of the banner wrapper
1349
+ }
1350
+ // A walk that did not finish has not proven anything about the backlog.
1351
+ if (scan.truncated || scan.unreadable.length > 0) return null;
1352
+ return capped;
1353
+ }
1354
+
1355
+ /**
1356
+ * The #1419 early-warning line: the path-scoped ceiling is close, not broken.
1357
+ *
1358
+ * Reports the numbers the RULE judged (HR-106) — the same `pathScoped.bytes`
1359
+ * and ceiling `writeApprovedRules` will compare later — plus, when the ledger
1360
+ * carries one, how many learnings the last /reconcile run already held back.
1361
+ * The percentage is formatted locale-free so the same corpus renders the same
1362
+ * string on every host.
1363
+ *
1364
+ * @param {ReturnType<typeof computeInstructionBudget>} budget
1365
+ * @param {string|undefined} repoRoot
1366
+ * @returns {string}
1367
+ */
1368
+ function formatNearPathScopedLine(budget, repoRoot) {
1369
+ const used = budget.bySurface.pathScoped.bytes;
1370
+ const ceiling = budget.pathScopedByteCeiling;
1371
+ const pct = ((used / ceiling) * 100).toFixed(1);
1372
+ const capped = readLastReconcileCapped(repoRoot);
1373
+ // Only a POSITIVE measured backlog is worth a clause; `0` and `null` both
1374
+ // add nothing the operator can act on, and printing `0` for `null` would be
1375
+ // the false-negative this helper's three-state contract exists to prevent.
1376
+ const backlog = typeof capped === 'number' && capped > 0 ? ` — ${capped} Learnings warten` : '';
1377
+ return (
1378
+ `path-scoped Regel-Decke zu ${pct} % belegt (${used}/${ceiling} B)${backlog} — ` +
1379
+ '/reconcile wird Learnings zurueckhalten'
1380
+ );
1381
+ }
1382
+
1383
+ /**
1384
+ * The #1449 defect line: rule files whose frontmatter Claude Code never reads.
1385
+ * Names every file — the fix is per file (move the text above `---` below the
1386
+ * closing fence), so a count alone would send the operator hunting.
1387
+ *
1388
+ * @param {string[]} defects - basenames from `computeInstructionBudget().defects`
1389
+ * @returns {string}
1390
+ */
1391
+ function formatFrontmatterDefectLine(defects) {
1392
+ return (
1393
+ `⚠ ${defects.length} rule(s) carry frontmatter that Claude Code ignores ` +
1394
+ `(text before the opening ---): ${defects.join(', ')} — they load always-on.`
1395
+ );
1396
+ }
1397
+
1247
1398
  /**
1248
1399
  * Banner wrapper — session-start Phase 4 convention.
1249
1400
  *
@@ -1260,7 +1411,9 @@ export function computeInstructionBudget(opts = {}) {
1260
1411
  *
1261
1412
  * The message names WHICH axis breached (#931a) — a banner that only said
1262
1413
  * "over budget" would leave the operator guessing whether to prune bullets or
1263
- * prose. It stays at three lines because it renders at every session start.
1414
+ * prose. It stays short because it renders at every session start: the breach
1415
+ * block is three lines, plus one line per extra finding (near-threshold,
1416
+ * frontmatter defect).
1264
1417
  *
1265
1418
  * @param {object} [opts] forwarded to computeInstructionBudget.
1266
1419
  * @param {string} [opts.repoRoot] project root for the config read.
@@ -1268,10 +1421,17 @@ export function computeInstructionBudget(opts = {}) {
1268
1421
  * @param {number} [opts.byteCeiling] explicit byte-ceiling override (wins over config).
1269
1422
  * @param {number} [opts.pathScopedByteCeiling] explicit path-scoped-ceiling override.
1270
1423
  * @returns {{ severity: 'warn', message: string } | null}
1271
- * null when disabled / off / every axis at-or-under its ceiling OR on any
1272
- * read failure. Since #1316 each of the four axes — directives, bytes,
1424
+ * null when disabled / off / every axis comfortably under its ceiling OR on
1425
+ * any read failure. Since #1316 each of the four axes — directives, bytes,
1273
1426
  * generated, path-scoped — raises the banner on its own (see
1274
- * DEFAULT_PATH_SCOPED_BYTE_CEILING).
1427
+ * DEFAULT_PATH_SCOPED_BYTE_CEILING). Since #1419 the path-scoped axis ALSO
1428
+ * raises it at {@link PATH_SCOPED_NEAR_THRESHOLD} of its ceiling, before the
1429
+ * breach: that band used to be silent, and the ceiling then surfaced only
1430
+ * inside `writeApprovedRules` — after the operator had approved rules it
1431
+ * refused to write. Since #1449 a rule whose frontmatter is not on line 1
1432
+ * raises it too (one line naming the files), whether or not any axis is
1433
+ * close. The RETURN SHAPE is unchanged (`{severity:'warn', message}` |
1434
+ * null), so `session-start-probes.mjs` needs no new vocabulary.
1275
1435
  */
1276
1436
  export function checkInstructionBudget(opts = {}) {
1277
1437
  let cfg;
@@ -1331,7 +1491,42 @@ export function checkInstructionBudget(opts = {}) {
1331
1491
  return null; // never throw out of the banner wrapper
1332
1492
  }
1333
1493
 
1334
- if (!budget || !budget.overBudget) return null;
1494
+ if (!budget) return null;
1495
+
1496
+ // #1419 near-threshold stage. Strictly BELOW the hard test (a breach is
1497
+ // reported as a breach, never twice), and computed before the early return
1498
+ // so a corpus at 90–100 % of the path-scoped ceiling stops being silent —
1499
+ // that silence is the defect: the ceiling used to surface only inside
1500
+ // `writeApprovedRules`, after the operator had already approved rules.
1501
+ const nearPathScoped =
1502
+ !budget.overPathScopedBudget &&
1503
+ budget.pathScopedByteCeiling > 0 &&
1504
+ budget.bySurface.pathScoped.bytes >= budget.pathScopedByteCeiling * PATH_SCOPED_NEAR_THRESHOLD;
1505
+
1506
+ // #1449 — reported on its own line, never folded into `overBudget`.
1507
+ const defectLine =
1508
+ budget.defects.length > 0 ? formatFrontmatterDefectLine(budget.defects) : null;
1509
+
1510
+ if (!budget.overBudget && !nearPathScoped && !defectLine) return null;
1511
+
1512
+ const nearLine = nearPathScoped ? formatNearPathScopedLine(budget, opts.repoRoot) : null;
1513
+
1514
+ // Nothing breached: one block per finding — the near-threshold line carries
1515
+ // the same remedy as the breach clause below (consolidate — never raise).
1516
+ if (!budget.overBudget) {
1517
+ return {
1518
+ severity: 'warn',
1519
+ message: [
1520
+ ...(nearLine
1521
+ ? [
1522
+ `⚠ ${nearLine}.`,
1523
+ ' Consolidate generated rules per docs/rule-authoring.md § Consolidated rules; never raise the ceiling.',
1524
+ ]
1525
+ : []),
1526
+ ...(defectLine ? [defectLine] : []),
1527
+ ].join('\n'),
1528
+ };
1529
+ }
1335
1530
 
1336
1531
  // Name only the breached axes — listing a healthy axis would pad the line
1337
1532
  // without telling the operator anything they must act on.
@@ -1377,10 +1572,15 @@ export function checkInstructionBudget(opts = {}) {
1377
1572
  .map((f) => `${f.file} (${f.count} dir, ${f.bytes} B)`)
1378
1573
  .join(', ');
1379
1574
 
1575
+ // The near-threshold line rides ALONGSIDE the breach line rather than inside
1576
+ // `axes`: the path-scoped axis is not over, and folding a healthy-but-close
1577
+ // axis into a sentence that says "over" would misreport which ceiling broke.
1380
1578
  const message = [
1381
1579
  `⚠ Instruction budget over — ${axes.join(' · ')} across ${budget.perFile.length} always-on rules.`,
1580
+ ...(nearLine ? [` ${nearLine}.`] : []),
1382
1581
  ` Top files: ${top}`,
1383
1582
  ' See the instruction-budget audit (#687; archived in the private Meta-Vault) for the prune/demote list.',
1583
+ ...(defectLine ? [defectLine] : []),
1384
1584
  ].join('\n');
1385
1585
 
1386
1586
  return { severity: 'warn', message };
@@ -711,12 +711,33 @@ export function emitRewrite(updatedInput) {
711
711
  process.exit(0);
712
712
  };
713
713
 
714
- if (updatedInput === null || typeof updatedInput !== 'object' || Array.isArray(updatedInput)) {
715
- // `updatedInput` is a MAP in the bundle schema, so an array is as wrong as a
716
- // string — and an array would serialize into a shape the harness cannot use.
717
- bail(
718
- `was called with ${Array.isArray(updatedInput) ? 'an array' : String(updatedInput === null ? 'null' : typeof updatedInput)}, not a tool-input object`,
719
- );
714
+ // `updatedInput` is a MAP in the bundle schema, so an array is as wrong as a
715
+ // string — and an array would serialize into a shape the harness cannot use.
716
+ //
717
+ // PLAIN object only, and `typeof === 'object'` is not that test. An object
718
+ // WRAPPER passes it and then serializes to something else entirely:
719
+ // `new Date()` and `new String('x')` become JSON STRINGS (their `toJSON` /
720
+ // primitive form), `new Map([...])` becomes `{}` — which looks valid and,
721
+ // per the doc above, deletes every field of the tool input. Each of the three
722
+ // reaches the harness as a schema-invalid `updatedInput`, and the bundle
723
+ // turns that into `behavior: "deny"`: on the `AskUserQuestion` path the
724
+ // operator's question is destroyed to save a rewrite. A prototype of
725
+ // `Object.prototype` (a literal) or `null` (`Object.create(null)`) is the
726
+ // whole admissible set; everything else takes the same no-op-plus-stderr
727
+ // contract as `null`, an array or a string.
728
+ const proto =
729
+ updatedInput !== null && typeof updatedInput === 'object'
730
+ ? Object.getPrototypeOf(updatedInput)
731
+ : undefined;
732
+ const isPlainObject =
733
+ !Array.isArray(updatedInput) && (proto === Object.prototype || proto === null);
734
+ if (!isPlainObject) {
735
+ let described;
736
+ if (Array.isArray(updatedInput)) described = 'an array';
737
+ else if (updatedInput === null) described = 'null';
738
+ else if (typeof updatedInput !== 'object') described = String(typeof updatedInput);
739
+ else described = `a non-plain object (${proto?.constructor?.name ?? 'unknown prototype'})`;
740
+ bail(`was called with ${described}, not a tool-input object`);
720
741
  }
721
742
 
722
743
  let line;
@@ -731,11 +752,24 @@ export function emitRewrite(updatedInput) {
731
752
  },
732
753
  });
733
754
  } catch (err) {
734
- // A cycle or a BigInt in the caller's object. JSON.stringify can also return
735
- // undefined (a toJSON that yields undefined) — caught by the same guard.
755
+ // A cycle or a BigInt in the caller's object.
736
756
  bail(`could not serialize the tool input (${err?.message ?? String(err)})`);
737
757
  }
738
758
 
759
+ // `JSON.stringify` returns `undefined` only for a TOP-LEVEL value it drops —
760
+ // and `updatedInput` sits NESTED inside the envelope literal above, so at this
761
+ // call site it never can. Measured 2026-09-18 with
762
+ // `{ toJSON() { return undefined; } }` as `updatedInput`: stdout is
763
+ // `{"hookSpecificOutput":{"hookEventName":"PreToolUse"}}` (53 bytes) — the
764
+ // property is silently OMITTED, not undefined, and the guard below does not
765
+ // fire. That degraded state is the safe one (an envelope without
766
+ // `updatedInput` carries no rewrite, so the tool call runs with its original
767
+ // input — exactly what `bail` produces), which is why it is documented rather
768
+ // than checked: re-parsing up to REWRITE_ENVELOPE_MAX_BYTES on every rewrite
769
+ // to recover one stderr line is a poor trade. The guard stays because it is
770
+ // the envelope SHAPE that makes the case unreachable: hoist `updatedInput` to
771
+ // the top level, or hand the literal to a helper that returns it, and
772
+ // `undefined` becomes reachable again.
739
773
  if (typeof line !== 'string') {
740
774
  bail('serialized the tool input to undefined (a toJSON returning undefined?)');
741
775
  }