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
@@ -173,6 +173,53 @@
173
173
  * confused" as a bypass strategy. This is the one place the guard is closed
174
174
  * rather than open, and it is closed because the alternative is a silent skip.
175
175
  *
176
+ * ## Interpreter eval payloads (GitLab #1408)
177
+ *
178
+ * The blind spot this file used to NAME became the habitual shape. On
179
+ * 2026-09-19 a coordinator appended its own `sessions.jsonl` record with
180
+ * `node --input-type=module -e "… appendFileSync('.orchestrator/metrics/
181
+ * sessions.jsonl', JSON.stringify(rec) + '\n')"`; `emit-session.mjs` never ran
182
+ * that session, and the record landed with `ended_at` instead of `completed_at`
183
+ * and four required fields missing. Measured over 58 transcripts of this repo:
184
+ * 438 Bash commands mention the ledger, the SHELL write shapes above occurred
185
+ * **0 times**, and 46 are interpreter one-liners. Fleet-wide: 40 interpreter
186
+ * write calls, 38 with the ledger path as the FIRST argument. The guard was
187
+ * aimed at a shape nobody uses.
188
+ *
189
+ * What is covered now: ONE predicate (`findInterpreterLedgerWrite`), hung off
190
+ * the SAME per-segment loop as every other matcher here, so scoping is per
191
+ * STATEMENT — `node -e '<read>' ; echo ok` allows, `cmd && node -e '<append>'`
192
+ * denies on the second statement alone. It takes the eval-flag operand
193
+ * (`-e`/`--eval`/`-p`/`--print`, `python -c`, `perl -e`, `ruby -e`,
194
+ * `deno eval`, in separated / attached / `=`-joined spelling) and denies iff a
195
+ * write call from a CLOSED list (`appendFile(Sync)`, `writeFile(Sync)`,
196
+ * `createWriteStream`; Python `open(…, 'a'|'w'|'x'|…)`; Ruby `File.write` /
197
+ * `IO.write` / `File.open`; Perl's two `open` forms) has the literal basename
198
+ * `sessions.jsonl` in its FIRST ARGUMENT TEXT.
199
+ *
200
+ * The first-argument scoping is load-bearing, not an optimisation: keying on
201
+ * mere co-occurrence denies all three of the ledger READS measured in this
202
+ * repo's transcripts (each reads the ledger and writes
203
+ * `.orchestrator/tmp/session-entry.json` before piping into
204
+ * `emit-session.mjs`), and denies `readFileSync('…/sessions.jsonl')` +
205
+ * `writeFileSync('/tmp/x')` in one one-liner.
206
+ *
207
+ * KNOWN LIMITS — these stay limits, deliberately (BV-004):
208
+ * - The path held in a VARIABLE: `const p = '…/sessions.jsonl';
209
+ * appendFileSync(p, …)`. No constant propagation here; the first-argument
210
+ * text carries no basename.
211
+ * - A here-doc-fed interpreter (`node <<'EOF' … EOF`) — the body is skipped
212
+ * upstream by `scanCommand`, so no payload reaches this predicate.
213
+ * - An invoked SCRIPT FILE (`node tools/append.mjs`) — the ledger name is not
214
+ * in the command string at all.
215
+ * - A write API outside the closed list (`fs.open` + `write`, a stream from a
216
+ * helper, `Deno.writeTextFile`), and Python kwargs-only `open(file=…,
217
+ * mode='a')`.
218
+ * REVISIT TRIGGER: re-run the transcript census (`rg` for interpreter
219
+ * one-liners naming the ledger). If a shape from the four bullets above
220
+ * appears in ANY real transcript, widen the predicate then — not before. A
221
+ * guard widened against imagined shapes buys false positives at full price.
222
+ *
176
223
  * ## What this does NOT catch (stated plainly — the bounds are the contract)
177
224
  *
178
225
  * - Indirection through a variable: `>> "$LEDGER"`, `tee "$LEDGER"`,
@@ -193,19 +240,37 @@
193
240
  * buried in a quoted substitution is not the accident shape this guard is
194
241
  * for — it is the "determined circumvention" line below.
195
242
  * - In-place editors: `sed -i`, `perl -i`, `ed`, an interactive editor.
196
- * - A write performed inside an interpreter: `node -e`, `python -c`,
197
- * `bash -c '… >> …/sessions.jsonl'`, or any script the command invokes that
198
- * appends the ledger itself. The one exception is a WRAPPER payload —
199
- * `env -S 'tee -a …/sessions.jsonl'` — which `resolveSegmentVerb` reports
200
- * as a payload and which this matcher re-enters (to MAX_PAYLOAD_DEPTH).
201
- * Interpreter `-c` payloads are a larger surface and stay out of scope.
243
+ * - A write performed by a SCRIPT FILE the command invokes
244
+ * (`node tools/append.mjs`) — the ledger name never appears in the command
245
+ * string at all, so nothing here can see it. Interpreter EVAL payloads
246
+ * (`node -e`, `python3 -c`) used to sit in this bullet too; since #1408 they
247
+ * are covered — see "Interpreter eval payloads" below for the exact bound.
248
+ * - A here-doc-fed interpreter (`node <<'EOF' … EOF`): here-doc BODIES are
249
+ * skipped as the data they are, so the payload never reaches the matcher.
250
+ * - A shell `-c` payload the segment does not NAME a shell for. Since
251
+ * 2026-09-20 a `-c` operand following a `bash`/`sh`/`zsh`/`dash`/`ksh`/`su`
252
+ * TOKEN is re-entered by the same matcher (`bash -c '… >> …/sessions.jsonl'`
253
+ * and `xargs -I{} sh -c '…'` both deny), but three shapes stay out:
254
+ * `ssh host '… >> …/sessions.jsonl'` (a REMOTE ledger — not this repo's
255
+ * file, so out of scope by nature, not by omission); a payload whose shell
256
+ * is only reachable through expansion (`"$SHELL" -c …`, `eval "$CMD"`);
257
+ * and a payload nested past MAX_PAYLOAD_DEPTH (2) — that cut is marked, not
258
+ * silent, when the dropped payload mentions the ledger (#998 item 2).
259
+ * This bullet REPLACES the one #1408 deleted (it named
260
+ * `bash -c '… >> …/sessions.jsonl'` as uncovered, which was true, and the
261
+ * replacement bullet narrowed the claim to script files and here-docs while
262
+ * the hole stayed open — a guard may have limits, it may not misstate them).
263
+ * REVISIT TRIGGER: re-run the transcript census for interpreter one-liners
264
+ * naming the ledger; widen only if one of these three shapes appears in a
265
+ * REAL transcript.
202
266
  * - Obfuscation: `eval`, `base64 -d | sh`, a here-doc-fed shell. Here-doc
203
267
  * BODIES are skipped as the data they are — `bash <<EOF … EOF` therefore
204
268
  * hides its payload from this matcher by construction.
205
269
  * - `sponge`, `install`, `rsync`, `awk > file` and other less common write
206
270
  * verbs (a redirect inside an `awk` program string is quoted data here).
207
- * - The Write/Edit tools — a different PreToolUse matcher entirely
208
- * (`hooks/enforce-scope.mjs` territory), not this hook's surface.
271
+ * - The Write/Edit/MultiEdit tools — a different PreToolUse matcher entirely,
272
+ * guarded by `hooks/pre-edit-sessions-ledger-guard.mjs` (#1443), not this
273
+ * hook's surface.
209
274
  * - The #385 repair-apply subshell/backtick deny (`unwrapSubshellLayers`)
210
275
  * only unwraps a command that IS, in its entirety, one wrapped run —
211
276
  * `x=$(node scripts/repair-invalid-sessions.mjs --apply)` (the wrapper is
@@ -249,8 +314,7 @@ import path from 'node:path';
249
314
  import { pathToFileURL } from 'node:url';
250
315
 
251
316
  import { shouldRunHook } from './_lib/profile-gate.mjs';
252
- // #211: exit 0 immediately (silent allow) when this hook is disabled via profile/env
253
- if (!shouldRunHook('pre-bash-sessions-ledger-guard')) process.exit(0);
317
+ import { isMainModule } from '../scripts/lib/is-main-module.mjs';
254
318
 
255
319
  // ---------------------------------------------------------------------------
256
320
  // #993 — late-bound repo dependencies
@@ -324,8 +388,9 @@ const GUARD_CONSEQUENCE = {
324
388
  ' COMMITTED (HEAD) command lexer — any uncommitted change to that file is NOT in effect.',
325
389
  ],
326
390
  inactive: [
327
- ' Consequence: a direct shell write into .orchestrator/metrics/sessions.jsonl',
328
- ' (>, >>, tee, dd of=, cp/mv destination) is NOT being blocked. This hook is a',
391
+ ' Consequence: a direct write into .orchestrator/metrics/sessions.jsonl',
392
+ ' (>, >>, tee, dd of=, cp/mv destination, or an interpreter payload such as',
393
+ ' node -e / python -c — #1408) is NOT being blocked. This hook is a',
329
394
  ' fail-open nudge, not a security boundary — but repair it so the #958 corruption',
330
395
  ' class stays caught.',
331
396
  ],
@@ -859,6 +924,293 @@ function scanCommand(command) {
859
924
  return { targets, sanitized: out, balanced: true };
860
925
  }
861
926
 
927
+ // ---------------------------------------------------------------------------
928
+ // Interpreter eval payloads (#1408)
929
+ //
930
+ // ONE predicate, hung off the EXISTING per-segment loop in findWriteVerbTarget.
931
+ // Deliberately NOT a Bash parser and deliberately NOT in command-blocker.mjs:
932
+ // that module has five other consumers, and a JS payload re-entered by its SHELL
933
+ // matcher would be misread as shell.
934
+ // ---------------------------------------------------------------------------
935
+
936
+ /**
937
+ * Interpreter verbs whose eval payload is read, mapped to the DIALECT its code
938
+ * is judged in. `node`/`bun`/`deno` share the `fs` API surface, so one dialect
939
+ * covers all three.
940
+ */
941
+ const INTERPRETER_FAMILY = new Map([
942
+ ['node', 'js'],
943
+ ['nodejs', 'js'],
944
+ ['bun', 'js'],
945
+ ['deno', 'js'],
946
+ ['python', 'python'],
947
+ ['python2', 'python'],
948
+ ['python3', 'python'],
949
+ ['perl', 'perl'],
950
+ ['ruby', 'ruby'],
951
+ ]);
952
+
953
+ /** Flags whose operand is CODE, not a script path — per dialect. */
954
+ const INTERPRETER_EVAL_FLAGS = {
955
+ js: new Set(['-e', '--eval', '-p', '--print']),
956
+ python: new Set(['-c']),
957
+ perl: new Set(['-e', '-E']),
958
+ ruby: new Set(['-e']),
959
+ };
960
+
961
+ /**
962
+ * CLOSED list of write calls per dialect. Nothing outside it is matched — a
963
+ * matcher keyed on "the payload mentions the ledger AND writes something" was
964
+ * measured to false-positive on all three real read one-liners in this repo's
965
+ * transcripts (they read the ledger and write `.orchestrator/tmp/…`).
966
+ */
967
+ const INTERPRETER_WRITE_CALLS = {
968
+ js: ['appendFileSync', 'appendFile', 'writeFileSync', 'writeFile', 'createWriteStream'],
969
+ python: ['open'],
970
+ perl: ['open'],
971
+ ruby: ['File.write', 'IO.write', 'File.open'],
972
+ };
973
+
974
+ /**
975
+ * Collect the eval-payload operands of one interpreter invocation.
976
+ *
977
+ * `deno` is the odd one: its eval mode is a SUBCOMMAND (`deno eval '<code>'`),
978
+ * not a flag. Everything else takes `-e`/`-c`-style flags in three spellings —
979
+ * separated (`-e '<code>'`), attached (`-e'<code>'`) and `=`-joined
980
+ * (`--eval='<code>'`).
981
+ *
982
+ * A QUOTED `-e` token is still treated as the flag: bash passes `'-e'` and `-e`
983
+ * identically, so skipping the quoted spelling would be a one-character bypass.
984
+ *
985
+ * @param {string} verb
986
+ * @param {string} family
987
+ * @param {Array<{ text: string, quoted: boolean }>} args - tokens after the verb
988
+ * @returns {string[]}
989
+ */
990
+ function collectCodeOperands(verb, family, args) {
991
+ const operands = [];
992
+ if (verb === 'deno') {
993
+ const at = args.findIndex((a) => a.text === 'eval');
994
+ if (at === -1) return operands;
995
+ for (const a of args.slice(at + 1)) {
996
+ if (!a.quoted && a.text.startsWith('-')) continue;
997
+ operands.push(a.text);
998
+ break;
999
+ }
1000
+ return operands;
1001
+ }
1002
+
1003
+ const flags = INTERPRETER_EVAL_FLAGS[family];
1004
+ if (!flags) return operands;
1005
+ for (let i = 0; i < args.length; i++) {
1006
+ const text = args[i].text;
1007
+ const eq = text.indexOf('=');
1008
+ if (eq > 0 && flags.has(text.slice(0, eq))) { operands.push(text.slice(eq + 1)); continue; }
1009
+ if (flags.has(text)) {
1010
+ if (i + 1 < args.length) { operands.push(args[i + 1].text); i++; }
1011
+ continue;
1012
+ }
1013
+ for (const flag of flags) {
1014
+ if (flag.length === 2 && !text.startsWith('--') && text.length > 2 && text.startsWith(flag)) {
1015
+ operands.push(text.slice(2));
1016
+ break;
1017
+ }
1018
+ }
1019
+ }
1020
+ return operands;
1021
+ }
1022
+
1023
+ /**
1024
+ * Indices of the `(` that OPENS a call to `name` in `code`.
1025
+ *
1026
+ * Identifier-boundary aware on the left, so `appendFile` does not match inside
1027
+ * `appendFileSync` (which is a separate list entry) and `myWriteFile` is not a
1028
+ * `writeFile`. A receiver prefix is fine — `.` is not an identifier char, so
1029
+ * `fs.appendFileSync` and `require('fs').appendFileSync` both resolve.
1030
+ *
1031
+ * @param {string} code
1032
+ * @param {string} name
1033
+ * @returns {number[]}
1034
+ */
1035
+ function findCallOpenings(code, name) {
1036
+ const out = [];
1037
+ for (let from = 0; ;) {
1038
+ const idx = code.indexOf(name, from);
1039
+ if (idx === -1) return out;
1040
+ from = idx + name.length;
1041
+ const before = idx > 0 ? code[idx - 1] : '';
1042
+ if (before && /[A-Za-z0-9_$]/.test(before)) continue;
1043
+ let j = from;
1044
+ while (j < code.length && (code[j] === ' ' || code[j] === '\t' || code[j] === '\n')) j++;
1045
+ if (code[j] === '(') out.push(j);
1046
+ }
1047
+ }
1048
+
1049
+ /**
1050
+ * Split the argument list opening at `open` on TOP-LEVEL commas.
1051
+ *
1052
+ * Quote- and nesting-aware, single pass, no backtracking regex — this runs on
1053
+ * every Bash call of every session, and a path that can throw or hang reads as
1054
+ * NO decision under the exit-0 protocol, i.e. fail-OPEN in the wrong direction.
1055
+ * Returns `null` when the list never closes (judged as "no hit", never a throw).
1056
+ *
1057
+ * @param {string} code
1058
+ * @param {number} open - index of the opening `(`
1059
+ * @returns {string[]|null}
1060
+ */
1061
+ function splitCallArgs(code, open) {
1062
+ const args = [];
1063
+ let cur = '';
1064
+ let depth = 0;
1065
+ let quote = null;
1066
+ for (let i = open; i < code.length; i++) {
1067
+ const ch = code[i];
1068
+ if (quote !== null) {
1069
+ if (ch === '\\' && i + 1 < code.length) { cur += ch + code[i + 1]; i++; continue; }
1070
+ if (ch === quote) quote = null;
1071
+ cur += ch;
1072
+ continue;
1073
+ }
1074
+ if (ch === '"' || ch === "'" || ch === '`') { quote = ch; cur += ch; continue; }
1075
+ if (ch === '(' || ch === '[' || ch === '{') {
1076
+ depth++;
1077
+ if (depth > 1) cur += ch;
1078
+ continue;
1079
+ }
1080
+ if (ch === ')' || ch === ']' || ch === '}') {
1081
+ depth--;
1082
+ if (depth === 0) { args.push(cur); return args; }
1083
+ cur += ch;
1084
+ continue;
1085
+ }
1086
+ if (ch === ',' && depth === 1) { args.push(cur); cur = ''; continue; }
1087
+ cur += ch;
1088
+ }
1089
+ return null;
1090
+ }
1091
+
1092
+ /**
1093
+ * Does any operand after the first name a WRITE mode?
1094
+ *
1095
+ * `open(p, 'a')` / `open(p, mode='w')` / `File.open(p, 'a')` write; `open(p)`
1096
+ * and `open(p, 'r')` read. `+` counts (`'r+'` writes).
1097
+ *
1098
+ * @param {string[]} args
1099
+ * @returns {boolean}
1100
+ */
1101
+ function hasWriteModeOperand(args) {
1102
+ for (let i = 1; i < args.length; i++) {
1103
+ const m = /^\s*(?:mode\s*=\s*)?(['"])([^'"]*)\1\s*$/.exec(args[i]);
1104
+ if (m && /[awx+]/.test(m[2])) return true;
1105
+ }
1106
+ return false;
1107
+ }
1108
+
1109
+ /**
1110
+ * Judge ONE resolved write call. The FIRST-ARGUMENT scoping is load-bearing:
1111
+ * keying on mere co-occurrence of a write verb and the ledger basename anywhere
1112
+ * in the payload denies `readFileSync('…/sessions.jsonl')` +
1113
+ * `writeFileSync('/tmp/x')` in one one-liner — the shape all three measured
1114
+ * ledger READS in this repo's transcripts actually have.
1115
+ *
1116
+ * Perl is the exception, by its own grammar: `open(FH, '>>', $p)` puts the path
1117
+ * THIRD and `open(FH, ">>$p")` glues mode to path. A read open never carries a
1118
+ * `>`, so "the ledger is an operand AND some operand carries `>`" is exact
1119
+ * rather than a guess.
1120
+ *
1121
+ * @param {string} family
1122
+ * @param {string} name
1123
+ * @param {string[]} args
1124
+ * @returns {string|null} the offending argument text, or null
1125
+ */
1126
+ function judgeInterpreterCall(family, name, args) {
1127
+ if (args.length === 0) return null;
1128
+ if (family === 'perl') {
1129
+ const hit = args.find((a) => a.includes(LEDGER_BASENAME));
1130
+ if (!hit) return null;
1131
+ return args.some((a) => a.includes('>')) ? hit : null;
1132
+ }
1133
+ if (!args[0].includes(LEDGER_BASENAME)) return null;
1134
+ if (family === 'python' || name === 'File.open') {
1135
+ return hasWriteModeOperand(args) ? args[0] : null;
1136
+ }
1137
+ return args[0];
1138
+ }
1139
+
1140
+ /**
1141
+ * The #1408 predicate: does this interpreter invocation write the ledger from
1142
+ * INSIDE its eval payload?
1143
+ *
1144
+ * Called from the per-segment loop of {@link findWriteVerbTarget}, so scoping is
1145
+ * per STATEMENT: `node -e '<read>' ; echo ok` is judged on the statement that
1146
+ * hit, and `cmd && node -e '<append>'` denies on the second one alone.
1147
+ *
1148
+ * @param {string} verb - resolved segment verb (basename, wrappers already peeled)
1149
+ * @param {Array<{ text: string, quoted: boolean }>} args - tokens after the verb
1150
+ * @returns {string|null} the offending target, or null
1151
+ */
1152
+ function findInterpreterLedgerWrite(verb, args) {
1153
+ const family = INTERPRETER_FAMILY.get(verb);
1154
+ if (!family) return null;
1155
+ for (const code of collectCodeOperands(verb, family, args)) {
1156
+ if (typeof code !== 'string' || !code.includes(LEDGER_BASENAME)) continue;
1157
+ for (const name of INTERPRETER_WRITE_CALLS[family]) {
1158
+ for (const open of findCallOpenings(code, name)) {
1159
+ const callArgs = splitCallArgs(code, open);
1160
+ if (!callArgs) continue;
1161
+ const hit = judgeInterpreterCall(family, name, callArgs);
1162
+ if (hit) return `${hit.trim()} (${name}(…) inside a ${verb} eval payload)`;
1163
+ }
1164
+ }
1165
+ }
1166
+ return null;
1167
+ }
1168
+
1169
+ /**
1170
+ * Shells whose `-c <payload>` operand is a command line the shell EXECUTES.
1171
+ *
1172
+ * DELIBERATE DUPLICATE of `DASH_C_SHELLS` in scripts/lib/command-blocker.mjs,
1173
+ * which does not export it (nor its `dashCPayloads`). Change one, change both.
1174
+ */
1175
+ const DASH_C_SHELLS = new Set(['bash', 'sh', 'zsh', 'dash', 'ksh', 'su']);
1176
+
1177
+ /**
1178
+ * Collect the command lines a shell nested in this segment will EXECUTE, so
1179
+ * {@link findWriteVerbTarget} can re-enter the SAME matcher on them.
1180
+ *
1181
+ * `resolveSegmentVerb` reports WRAPPER payloads (`env -S '…'`) and this hook
1182
+ * has recursed on those since #982 — but a `bash -c` payload is not a wrapper
1183
+ * payload, and command-blocker's own `dedupedSegmentPayloads` (the piece that
1184
+ * adds them) is internal to that module. So the guard saw `bash -c` as the
1185
+ * verb `bash` with an inert quoted operand and ALLOWED, while the identical
1186
+ * unwrapped command DENIED — one word of bypass, measured 2026-09-20.
1187
+ *
1188
+ * Keyed on a shell TOKEN anywhere in the segment rather than on the resolved
1189
+ * verb: that is what reaches `xargs -I{} sh -c "…"` (verb `xargs`) and
1190
+ * `find … -exec sh -c "…" \;`, and it keeps `grep -c <pattern>` out, because
1191
+ * `grep` is not a shell name. Recursion is bounded by MAX_PAYLOAD_DEPTH, and a
1192
+ * payload that carries no write intent still ALLOWs — the false-positive floor
1193
+ * (`bash -c "jq . <ledger>"`, `bash -c "tail -1 <ledger> | jq ."`) is judged by
1194
+ * the same matcher as its unwrapped form, not by a second, weaker one.
1195
+ *
1196
+ * @param {Array<{ text: string, quoted: boolean }>} segment
1197
+ * @returns {string[]} distinct payload strings
1198
+ */
1199
+ function dashCShellPayloads(segment) {
1200
+ const out = new Set();
1201
+ for (let i = 0; i < segment.length - 1; i++) {
1202
+ const tok = segment[i];
1203
+ if (tok.quoted || !DASH_C_SHELLS.has(path.basename(tok.text))) continue;
1204
+ for (let j = i + 1; j < segment.length - 1; j++) {
1205
+ if (/^-[A-Za-z]*c$/.test(segment[j].text)) {
1206
+ out.add(segment[j + 1].text);
1207
+ break;
1208
+ }
1209
+ }
1210
+ }
1211
+ return [...out];
1212
+ }
1213
+
862
1214
  /**
863
1215
  * Find a non-redirect write verb (`tee`, `dd of=`, `cp`/`mv` destination)
864
1216
  * whose target is the ledger.
@@ -923,6 +1275,12 @@ function findWriteVerbTarget(command, depth = 0, marks = []) {
923
1275
  }
924
1276
  }
925
1277
 
1278
+ // A shell `-c` payload is a command line too, and until 2026-09-20 it was
1279
+ // the guard's one-word bypass: `bash -c "echo x >> <ledger>"` ALLOWED while
1280
+ // `echo x >> <ledger>` DENIED. See dashCShellPayloads for why this is keyed
1281
+ // on a shell token rather than the resolved verb.
1282
+ for (const p of dashCShellPayloads(segment)) payloadSet.add(p);
1283
+
926
1284
  // `env -S 'tee -a <ledger>'` hides a whole command line in one operand.
927
1285
  // Recurse on the payload with the SAME matcher rather than a second,
928
1286
  // weaker one — bounded by MAX_PAYLOAD_DEPTH. Kept BEFORE the verb dispatch
@@ -951,6 +1309,12 @@ function findWriteVerbTarget(command, depth = 0, marks = []) {
951
1309
  // other's (the lib docblock's positional-walk caveat).
952
1310
  const args = segment.slice(index + 1);
953
1311
 
1312
+ // #1408 — the write happens INSIDE an interpreter payload (`node -e`,
1313
+ // `python3 -c`, …), where there is no shell write intent to resolve. One
1314
+ // predicate, judged on THIS statement; see findInterpreterLedgerWrite.
1315
+ const interpreterTarget = findInterpreterLedgerWrite(verb, args);
1316
+ if (interpreterTarget) return interpreterTarget;
1317
+
954
1318
  // The repair CLI mutates the ledger from inside Node, so there is no
955
1319
  // redirect target for the structural matcher to see. Match only its
956
1320
  // effective apply contract at the resolved token seam: `node`, the
@@ -1158,13 +1522,39 @@ async function main() {
1158
1522
  ? `${target.slice(0, TARGET_ECHO_MAX)}… (${target.length} chars)`
1159
1523
  : target;
1160
1524
 
1525
+ // #1442 — a repair-apply VARIANT (extra flag, wrapper, env prefix) reached
1526
+ // here because it is not the exact sanctioned string. Text only, no matching
1527
+ // change: name the one command that IS allowed, and say why the most common
1528
+ // re-spelling cannot work — this hook reads SO_DISABLED_HOOKS from its OWN
1529
+ // process env (hooks/_lib/profile-gate.mjs), never from the command text, and
1530
+ // a leading `VAR=val` token also breaks the exact match above.
1531
+ if (target.startsWith(REPAIR_APPLY_MARKER)) {
1532
+ emitDeny(
1533
+ [
1534
+ `Direct write to the sessions ledger blocked: '${shown}'`,
1535
+ `The sanctioned repair invocation is allowed ONLY as the exact bare command:`,
1536
+ ` ${REPAIR_APPLY_CANONICAL_COMMAND}`,
1537
+ `Any variant — an extra flag, a wrapper, an env prefix — is denied.`,
1538
+ `A SO_DISABLED_HOOKS=… prefix INSIDE the command has no effect: this hook reads`,
1539
+ `it from the environment of the whole session, never from the command text.`,
1540
+ `Override (intentional maintenance only, operator-side — e.g. started with \`!\`):`,
1541
+ `SO_DISABLED_HOOKS=pre-bash-sessions-ledger-guard as the environment of the session`,
1542
+ `See: GitLab #958, #1442, skills/session-end/session-metrics-write.md`,
1543
+ ].join('\n'),
1544
+ );
1545
+ return;
1546
+ }
1547
+
1161
1548
  emitDeny(
1162
1549
  [
1163
1550
  `Direct write to the sessions ledger blocked: '${shown}'`,
1164
1551
  `The ledger is append-only through its validating writer:`,
1165
1552
  ` node scripts/emit-session.mjs --entry '<json>' (or pipe the JSON on stdin)`,
1166
- `Hand-composing a record and appending it with a shell redirect skips schema`,
1167
- `validation — that is exactly how the malformed record in GitLab #958 landed.`,
1553
+ `After the writer succeeds, verify the record (read-only; does not repair):`,
1554
+ ` node scripts/check-sessions-integrity.mjs --session-id '<semantic-id>'`,
1555
+ `Hand-composing a record and appending it — with a shell redirect, or from`,
1556
+ `inside an interpreter (node -e / python -c) — skips schema validation. That is`,
1557
+ `exactly how the malformed records in GitLab #958 and #1408 landed.`,
1168
1558
  `Override (intentional maintenance only): run the session with`,
1169
1559
  `SO_DISABLED_HOOKS=pre-bash-sessions-ledger-guard`,
1170
1560
  `See: GitLab #958, skills/session-end/session-metrics-write.md`,
@@ -1203,11 +1593,18 @@ try {
1203
1593
  process.exit(0); // fail-open, but no longer fail-silent
1204
1594
  }
1205
1595
 
1206
- // Top-level error handler — fail-OPEN (see the module docblock). Never let a
1207
- // non-zero exit leak: on this protocol exit 0 + empty stdout is "no decision".
1208
- main().catch((e) => {
1209
- process.stderr.write(
1210
- `⚠ pre-bash-sessions-ledger-guard: internal error — ${e?.message || e}\n`,
1211
- );
1212
- process.exit(0);
1213
- });
1596
+ // Entry guard (#1393): run only as the node script the harness execs — a bare
1597
+ // `import()` must run no handler and must not exit the importing process.
1598
+ if (isMainModule(import.meta.url)) {
1599
+ // #211: exit 0 immediately (silent allow) when this hook is disabled via profile/env
1600
+ if (!shouldRunHook('pre-bash-sessions-ledger-guard')) process.exit(0);
1601
+
1602
+ // Top-level error handler — fail-OPEN (see the module docblock). Never let a
1603
+ // non-zero exit leak: on this protocol exit 0 + empty stdout is "no decision".
1604
+ main().catch((e) => {
1605
+ process.stderr.write(
1606
+ `⚠ pre-bash-sessions-ledger-guard: internal error — ${e?.message || e}\n`,
1607
+ );
1608
+ process.exit(0);
1609
+ });
1610
+ }