session-orchestrator 5.1.0 → 5.3.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 (484) hide show
  1. package/.agents/skills/architecture/SKILL.md +3 -1
  2. package/.agents/skills/autopilot/SKILL.md +6 -1
  3. package/.agents/skills/autopilot/agents/openai.yaml +5 -0
  4. package/.agents/skills/bootstrap/SKILL.md +7 -1
  5. package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
  6. package/.agents/skills/brainstorm/SKILL.md +8 -1
  7. package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
  8. package/.agents/skills/claude-md-drift-check/SKILL.md +3 -1
  9. package/.agents/skills/close/SKILL.md +21 -0
  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 +7 -1
  13. package/.agents/skills/debug/agents/openai.yaml +5 -0
  14. package/.agents/skills/discovery/SKILL.md +7 -2
  15. package/.agents/skills/discovery/agents/openai.yaml +5 -0
  16. package/.agents/skills/dispatcher/SKILL.md +7 -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 +7 -1
  21. package/.agents/skills/eli5/agents/openai.yaml +5 -0
  22. package/.agents/skills/eval/SKILL.md +7 -2
  23. package/.agents/skills/eval/agents/openai.yaml +5 -0
  24. package/.agents/skills/evolve/SKILL.md +8 -3
  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 +22 -0
  30. package/.agents/skills/go/agents/openai.yaml +5 -0
  31. package/.agents/skills/grill/SKILL.md +7 -1
  32. package/.agents/skills/grill/agents/openai.yaml +5 -0
  33. package/.agents/skills/harness-audit/SKILL.md +20 -0
  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 +6 -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/npm-publish/SKILL.md +4 -2
  41. package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
  42. package/.agents/skills/persona-panel/SKILL.md +6 -1
  43. package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
  44. package/.agents/skills/plan/SKILL.md +8 -2
  45. package/.agents/skills/plan/agents/openai.yaml +5 -0
  46. package/.agents/skills/playwright-driver/SKILL.md +3 -1
  47. package/.agents/skills/portfolio/SKILL.md +21 -0
  48. package/.agents/skills/portfolio/agents/openai.yaml +5 -0
  49. package/.agents/skills/quality-gates/SKILL.md +3 -1
  50. package/.agents/skills/reconcile/SKILL.md +6 -1
  51. package/.agents/skills/reconcile/agents/openai.yaml +5 -0
  52. package/.agents/skills/release/SKILL.md +22 -0
  53. package/.agents/skills/release/agents/openai.yaml +5 -0
  54. package/.agents/skills/remote-offload/SKILL.md +3 -1
  55. package/.agents/skills/repo-audit/SKILL.md +6 -1
  56. package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
  57. package/.agents/skills/session/SKILL.md +21 -0
  58. package/.agents/skills/session/agents/openai.yaml +5 -0
  59. package/.agents/skills/session-end/SKILL.md +3 -1
  60. package/.agents/skills/session-plan/SKILL.md +3 -1
  61. package/.agents/skills/session-start/SKILL.md +3 -1
  62. package/.agents/skills/spinout/SKILL.md +6 -1
  63. package/.agents/skills/spinout/agents/openai.yaml +5 -0
  64. package/.agents/skills/sunset-review/SKILL.md +7 -1
  65. package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
  66. package/.agents/skills/templates-ack/SKILL.md +21 -0
  67. package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
  68. package/.agents/skills/test/SKILL.md +21 -0
  69. package/.agents/skills/test/agents/openai.yaml +5 -0
  70. package/.agents/skills/test-runner/SKILL.md +3 -1
  71. package/.agents/skills/tmux-layout/SKILL.md +3 -1
  72. package/.agents/skills/using-orchestrator/SKILL.md +3 -1
  73. package/.agents/skills/ux-grill/SKILL.md +7 -1
  74. package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
  75. package/.agents/skills/vault-mirror/SKILL.md +3 -1
  76. package/.agents/skills/vault-sync/SKILL.md +3 -1
  77. package/.agents/skills/wave-executor/SKILL.md +3 -1
  78. package/.agents/skills/write-executable-plan/SKILL.md +3 -1
  79. package/.claude-plugin/marketplace.json +1 -1
  80. package/.claude-plugin/plugin.json +1 -1
  81. package/.codex-plugin/plugin.json +4 -4
  82. package/.codex-plugin/skills/autopilot/SKILL.md +5 -4
  83. package/.codex-plugin/skills/bootstrap/SKILL.md +8 -4
  84. package/.codex-plugin/skills/brainstorm/SKILL.md +11 -4
  85. package/.codex-plugin/skills/close/SKILL.md +3 -3
  86. package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -1
  87. package/.codex-plugin/skills/debug/SKILL.md +11 -4
  88. package/.codex-plugin/skills/discovery/SKILL.md +8 -4
  89. package/.codex-plugin/skills/dispatcher/SKILL.md +4 -4
  90. package/.codex-plugin/skills/eli5/SKILL.md +9 -4
  91. package/.codex-plugin/skills/eval/SKILL.md +9 -4
  92. package/.codex-plugin/skills/evolve/SKILL.md +9 -4
  93. package/.codex-plugin/skills/go/SKILL.md +3 -3
  94. package/.codex-plugin/skills/grill/SKILL.md +11 -4
  95. package/.codex-plugin/skills/harness-audit/SKILL.md +4 -3
  96. package/.codex-plugin/skills/memory-cleanup/SKILL.md +9 -4
  97. package/.codex-plugin/skills/npm-publish/SKILL.md +1 -1
  98. package/.codex-plugin/skills/persona-panel/SKILL.md +5 -5
  99. package/.codex-plugin/skills/plan/SKILL.md +8 -4
  100. package/.codex-plugin/skills/portfolio/SKILL.md +3 -3
  101. package/.codex-plugin/skills/reconcile/SKILL.md +9 -4
  102. package/.codex-plugin/skills/release/SKILL.md +3 -3
  103. package/.codex-plugin/skills/repo-audit/SKILL.md +6 -4
  104. package/.codex-plugin/skills/session/SKILL.md +1 -1
  105. package/.codex-plugin/skills/spinout/SKILL.md +4 -4
  106. package/.codex-plugin/skills/sunset-review/SKILL.md +5 -4
  107. package/.codex-plugin/skills/test/SKILL.md +3 -3
  108. package/.codex-plugin/skills/ux-grill/SKILL.md +11 -4
  109. package/.cursor/commands/autopilot.md +4 -4
  110. package/.cursor/commands/bootstrap.md +5 -4
  111. package/.cursor/commands/brainstorm.md +5 -4
  112. package/.cursor/commands/close.md +4 -3
  113. package/.cursor/commands/debug.md +4 -4
  114. package/.cursor/commands/discovery.md +4 -4
  115. package/.cursor/commands/dispatcher.md +4 -4
  116. package/.cursor/commands/eli5.md +4 -4
  117. package/.cursor/commands/eval.md +4 -4
  118. package/.cursor/commands/evolve.md +4 -4
  119. package/.cursor/commands/go.md +4 -3
  120. package/.cursor/commands/grill.md +4 -4
  121. package/.cursor/commands/harness-audit.md +3 -3
  122. package/.cursor/commands/memory-cleanup.md +4 -4
  123. package/.cursor/commands/persona-panel.md +4 -4
  124. package/.cursor/commands/plan.md +5 -4
  125. package/.cursor/commands/portfolio.md +3 -3
  126. package/.cursor/commands/reconcile.md +4 -4
  127. package/.cursor/commands/release.md +4 -3
  128. package/.cursor/commands/repo-audit.md +4 -4
  129. package/.cursor/commands/session.md +1 -1
  130. package/.cursor/commands/spinout.md +4 -4
  131. package/.cursor/commands/sunset-review.md +4 -4
  132. package/.cursor/commands/test.md +3 -3
  133. package/.cursor/commands/ux-grill.md +4 -4
  134. package/.cursor/rules/000-session-orchestrator.mdc +0 -2
  135. package/.cursor/rules/010-session-workflow.mdc +2 -2
  136. package/.cursor/rules/050-plan.mdc +1 -1
  137. package/.cursor/skills/bootstrap/SKILL.md +1 -0
  138. package/.cursor/skills/close/SKILL.md +13 -0
  139. package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
  140. package/.cursor/skills/debug/SKILL.md +0 -1
  141. package/.cursor/skills/discovery/SKILL.md +0 -1
  142. package/.cursor/skills/dispatcher/SKILL.md +0 -1
  143. package/.cursor/skills/eli5/SKILL.md +0 -1
  144. package/.cursor/skills/eval/SKILL.md +1 -1
  145. package/.cursor/skills/evolve/SKILL.md +0 -1
  146. package/.cursor/skills/go/SKILL.md +13 -0
  147. package/.cursor/skills/grill/SKILL.md +0 -1
  148. package/.cursor/skills/harness-audit/SKILL.md +12 -0
  149. package/.cursor/skills/npm-publish/SKILL.md +1 -0
  150. package/.cursor/skills/portfolio/SKILL.md +12 -0
  151. package/.cursor/skills/release/SKILL.md +13 -0
  152. package/.cursor/skills/repo-audit/SKILL.md +0 -1
  153. package/.cursor/skills/sunset-review/SKILL.md +0 -1
  154. package/.cursor/skills/test/SKILL.md +12 -0
  155. package/.cursor/skills/ux-grill/SKILL.md +0 -1
  156. package/.cursor-plugin/plugin.json +1 -1
  157. package/.orchestrator/policy/blocked-commands.json +13 -4
  158. package/AGENTS.md +3 -2
  159. package/CHANGELOG.md +197 -0
  160. package/README.md +11 -9
  161. package/SECURITY.md +12 -0
  162. package/agents/dialectic-deriver.md +13 -10
  163. package/agents/eval-judge.md +67 -45
  164. package/agents/skill-applied-judge.md +34 -19
  165. package/commands/session.md +17 -3
  166. package/docs/baseline.md +12 -6
  167. package/docs/ci-setup.md +53 -0
  168. package/docs/codex-setup.md +15 -3
  169. package/docs/components.md +13 -6
  170. package/docs/events-schema.md +59 -9
  171. package/docs/install.md +16 -0
  172. package/docs/persona-panel.md +1 -1
  173. package/docs/pi-setup.md +1 -1
  174. package/docs/rule-authoring.md +135 -14
  175. package/docs/scope-collision-guard.md +2 -0
  176. package/docs/session-config-reference.md +106 -11
  177. package/docs/session-config-template.md +31 -2
  178. package/docs/telemetry.md +2 -0
  179. package/hooks/_lib/hook-import-set.json +125 -8
  180. package/hooks/_lib/subagent-paths.mjs +15 -0
  181. package/hooks/_lib/subagent-transcript.mjs +582 -31
  182. package/hooks/_lib/vcs-create-matcher.mjs +217 -62
  183. package/hooks/config-protection.mjs +11 -3
  184. package/hooks/cwd-change-restore.mjs +11 -3
  185. package/hooks/enforce-commands.mjs +70 -23
  186. package/hooks/enforce-scope.mjs +143 -33
  187. package/hooks/hooks-codex.json +1 -1
  188. package/hooks/hooks.json +1 -1
  189. package/hooks/loop-guard.mjs +11 -3
  190. package/hooks/on-session-end.mjs +72 -25
  191. package/hooks/on-session-start.mjs +48 -11
  192. package/hooks/on-stop.mjs +211 -23
  193. package/hooks/operator-steer.mjs +11 -3
  194. package/hooks/post-bash-issue-budget-refund.mjs +18 -8
  195. package/hooks/post-bash-write-verify.mjs +6 -2
  196. package/hooks/post-edit-import-probe.mjs +17 -9
  197. package/hooks/post-edit-validate.mjs +13 -5
  198. package/hooks/post-subagent-discovery-validator.mjs +98 -13
  199. package/hooks/post-tool-batch-wave-signal.mjs +200 -38
  200. package/hooks/post-tool-failure-corrective-context.mjs +11 -5
  201. package/hooks/post-tooluse-frontend-slop.mjs +10 -4
  202. package/hooks/pre-auq-clarity.mjs +18 -2
  203. package/hooks/pre-bash-destructive-guard.mjs +80 -9
  204. package/hooks/pre-bash-issue-budget.mjs +119 -28
  205. package/hooks/pre-bash-memory-propose-audit.mjs +86 -54
  206. package/hooks/pre-bash-sessions-ledger-guard.mjs +391 -20
  207. package/hooks/pre-bash-staging-fence.mjs +335 -31
  208. package/hooks/pre-bash-templates-first.mjs +19 -14
  209. package/hooks/pre-task-scope-disjoint.mjs +385 -5
  210. package/hooks/skill-invocation-telemetry.mjs +2 -1
  211. package/hooks/subagent-telemetry.mjs +15 -19
  212. package/hooks/wave-scope-commit-guard.mjs +197 -100
  213. package/monitors/monitors.json +1 -1
  214. package/output-styles/wave-summary.md +1 -1
  215. package/package.json +2 -1
  216. package/pi/prompts/autopilot.md +3 -3
  217. package/pi/prompts/bootstrap.md +3 -3
  218. package/pi/prompts/brainstorm.md +3 -3
  219. package/pi/prompts/close.md +2 -2
  220. package/pi/prompts/debug.md +3 -3
  221. package/pi/prompts/discovery.md +3 -3
  222. package/pi/prompts/dispatcher.md +3 -3
  223. package/pi/prompts/eli5.md +3 -3
  224. package/pi/prompts/eval.md +3 -3
  225. package/pi/prompts/evolve.md +3 -3
  226. package/pi/prompts/go.md +2 -2
  227. package/pi/prompts/grill.md +3 -3
  228. package/pi/prompts/harness-audit.md +2 -3
  229. package/pi/prompts/memory-cleanup.md +3 -3
  230. package/pi/prompts/persona-panel.md +3 -3
  231. package/pi/prompts/plan.md +3 -3
  232. package/pi/prompts/portfolio.md +2 -2
  233. package/pi/prompts/reconcile.md +3 -3
  234. package/pi/prompts/release.md +3 -3
  235. package/pi/prompts/repo-audit.md +3 -4
  236. package/pi/prompts/session.md +2 -2
  237. package/pi/prompts/spinout.md +3 -3
  238. package/pi/prompts/sunset-review.md +3 -3
  239. package/pi/prompts/templates-ack.md +1 -1
  240. package/pi/prompts/test.md +3 -3
  241. package/pi/prompts/ux-grill.md +3 -3
  242. package/rules/README.md +1 -1
  243. package/rules/opt-in-domain/prompt-caching.md +1 -1
  244. package/rules/opt-in-stack/backend-data.md +1 -1
  245. package/rules/opt-in-stack/backend.md +3 -3
  246. package/rules/opt-in-stack/frontend.md +1 -1
  247. package/rules/opt-in-stack/security-web.md +3 -3
  248. package/rules/opt-in-stack/swift.md +1 -1
  249. package/scripts/archive-closed-prds.mjs +2 -2
  250. package/scripts/auq-audit.mjs +2 -3
  251. package/scripts/autopilot.mjs +23 -2
  252. package/scripts/backfill-abandoned-sessions.mjs +171 -15
  253. package/scripts/backfill-evidence-digest.mjs +2 -1
  254. package/scripts/backfill-learnings-from-vault.mjs +2 -2
  255. package/scripts/check-package-manager.mjs +2 -2
  256. package/scripts/check-sessions-integrity.mjs +300 -0
  257. package/scripts/ci/assert-vitest-green.mjs +2 -1
  258. package/scripts/dialectic-deriver.mjs +50 -13
  259. package/scripts/emit-session.mjs +77 -32
  260. package/scripts/eval-session.mjs +65 -3
  261. package/scripts/export-hw-learnings.mjs +2 -1
  262. package/scripts/express-path.mjs +1 -1
  263. package/scripts/gc-stale-worktrees.mjs +2 -1
  264. package/scripts/generate-agents-skills.mjs +102 -29
  265. package/scripts/generate-codex-skills.mjs +48 -4
  266. package/scripts/generate-cursor-adapter.mjs +220 -11
  267. package/scripts/generate-hook-import-set.mjs +12 -27
  268. package/scripts/generate-pi-prompts.mjs +183 -13
  269. package/scripts/github-protection-audit.mjs +2 -3
  270. package/scripts/lib/agent-frontmatter.mjs +23 -1
  271. package/scripts/lib/agent-status.mjs +2 -31
  272. package/scripts/lib/auq/clarity.mjs +10 -2
  273. package/scripts/lib/auq/parse.mjs +12 -31
  274. package/scripts/lib/auq/schema.mjs +56 -41
  275. package/scripts/lib/auto-dialectic.mjs +304 -15
  276. package/scripts/lib/autopilot/flags.mjs +12 -1
  277. package/scripts/lib/autopilot/kill-switches.mjs +6 -3
  278. package/scripts/lib/autopilot/loop.mjs +14 -1
  279. package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
  280. package/scripts/lib/ci-status-banner.mjs +376 -16
  281. package/scripts/lib/claude-md-budget-lint.mjs +2 -5
  282. package/scripts/lib/command-blocker.mjs +408 -33
  283. package/scripts/lib/config/dialectic.mjs +12 -3
  284. package/scripts/lib/config/drift-check.mjs +19 -0
  285. package/scripts/lib/config/gate.mjs +74 -0
  286. package/scripts/lib/config/reaper.mjs +162 -0
  287. package/scripts/lib/config.mjs +14 -0
  288. package/scripts/lib/convergence-monitor.mjs +76 -13
  289. package/scripts/lib/cursor-hook-bridge.mjs +2 -2
  290. package/scripts/lib/description-surface.mjs +2 -5
  291. package/scripts/lib/dispatcher/cli.mjs +2 -1
  292. package/scripts/lib/ecosystem-health.mjs +11 -0
  293. package/scripts/lib/ecosystem-wizard.mjs +2 -1
  294. package/scripts/lib/eval/engine.mjs +421 -53
  295. package/scripts/lib/eval/judge.mjs +463 -40
  296. package/scripts/lib/eval/schema.mjs +10 -1
  297. package/scripts/lib/events-rotation.mjs +221 -25
  298. package/scripts/lib/events-schema.mjs +114 -0
  299. package/scripts/lib/events.mjs +524 -5
  300. package/scripts/lib/fetch-baseline.mjs +3 -8
  301. package/scripts/lib/frontmatter-guard.mjs +21 -10
  302. package/scripts/lib/gates/gate-baseline.mjs +27 -2
  303. package/scripts/lib/gates/gate-full.mjs +28 -3
  304. package/scripts/lib/gates/gate-helpers.mjs +243 -21
  305. package/scripts/lib/gates/gate-incremental.mjs +28 -3
  306. package/scripts/lib/gates/gate-per-file.mjs +27 -2
  307. package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +2 -1
  308. package/scripts/lib/gitlab-portfolio/cli.mjs +2 -1
  309. package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
  310. package/scripts/lib/instruction-budget-guard.mjs +332 -50
  311. package/scripts/lib/io.mjs +42 -8
  312. package/scripts/lib/is-main-module.mjs +82 -0
  313. package/scripts/lib/issue-close-strip-labels.mjs +207 -49
  314. package/scripts/lib/js-mask.mjs +197 -0
  315. package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
  316. package/scripts/lib/locks/index.mjs +32 -25
  317. package/scripts/lib/maintenance-due-banner.mjs +122 -91
  318. package/scripts/lib/orphan-reaper.mjs +1588 -0
  319. package/scripts/lib/peer-cards/merger.mjs +48 -10
  320. package/scripts/lib/peer-cards/reader.mjs +78 -2
  321. package/scripts/lib/peer-discovery.mjs +2 -5
  322. package/scripts/lib/playwright-driver/runner.mjs +2 -1
  323. package/scripts/lib/process-group.mjs +899 -0
  324. package/scripts/lib/quality-gate.mjs +107 -28
  325. package/scripts/lib/reconcile/backlog.mjs +368 -0
  326. package/scripts/lib/reconcile/engine.mjs +55 -188
  327. package/scripts/lib/reconcile/rule-expiry-sweep.mjs +884 -0
  328. package/scripts/lib/reconcile/sanitize.mjs +69 -3
  329. package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
  330. package/scripts/lib/resource-probe/parsers.mjs +31 -0
  331. package/scripts/lib/rule-loader.mjs +41 -12
  332. package/scripts/lib/rules-sync.mjs +2 -5
  333. package/scripts/lib/scope-echo.mjs +429 -7
  334. package/scripts/lib/scope-gate.mjs +605 -1
  335. package/scripts/lib/session-close-backfill.mjs +91 -12
  336. package/scripts/lib/session-id.mjs +9 -20
  337. package/scripts/lib/session-invocation.mjs +20 -0
  338. package/scripts/lib/session-schema/constants.mjs +30 -2
  339. package/scripts/lib/session-schema/normalizer.mjs +56 -4
  340. package/scripts/lib/session-schema.mjs +8 -3
  341. package/scripts/lib/session-start-probes.mjs +95 -10
  342. package/scripts/lib/sessions-canonical.mjs +23 -0
  343. package/scripts/lib/sessions-integrity-banner.mjs +7 -1
  344. package/scripts/lib/sessions-staleness-banner.mjs +193 -51
  345. package/scripts/lib/skill-evidence-window.mjs +891 -0
  346. package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
  347. package/scripts/lib/skill-evolution/engine.mjs +18 -9
  348. package/scripts/lib/skill-judge.mjs +45 -3
  349. package/scripts/lib/state-md.mjs +84 -3
  350. package/scripts/lib/sunset/walker.mjs +31 -4
  351. package/scripts/lib/tail-window.mjs +56 -0
  352. package/scripts/lib/telemetry/schema.mjs +30 -0
  353. package/scripts/lib/telemetry/sync.mjs +61 -6
  354. package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
  355. package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
  356. package/scripts/lib/tests-src-ratio.mjs +2 -6
  357. package/scripts/lib/tmux-layout/telemetry-stats.mjs +74 -14
  358. package/scripts/lib/user-invocable-skills.mjs +205 -0
  359. package/scripts/lib/ux-grill/reconcile.mjs +48 -22
  360. package/scripts/lib/validate/check-agents-skills.mjs +26 -15
  361. package/scripts/lib/validate/check-banner-parity.mjs +2 -2
  362. package/scripts/lib/validate/check-cursor-adapter.mjs +3 -2
  363. package/scripts/lib/validate/check-dead-bridge.mjs +2 -2
  364. package/scripts/lib/validate/check-doc-cli-commands.mjs +2 -2
  365. package/scripts/lib/validate/check-entry-guard.mjs +329 -0
  366. package/scripts/lib/validate/check-guard-requires-parity.mjs +2 -2
  367. package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
  368. package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +2 -2
  369. package/scripts/lib/validate/check-learning-provenance.mjs +2 -2
  370. package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
  371. package/scripts/lib/validate/check-rules.mjs +7 -5
  372. package/scripts/lib/validate/check-skill-links.mjs +35 -6
  373. package/scripts/lib/validate/check-skill-script-paths.mjs +241 -29
  374. package/scripts/lib/validate/check-test-git-config-target.mjs +26 -36
  375. package/scripts/lib/validate/check-unicode-safety.mjs +2 -2
  376. package/scripts/lib/validate/check-untracked-test-deps.mjs +9 -104
  377. package/scripts/lib/validate/check-unwired-features.mjs +220 -33
  378. package/scripts/lib/validate/check-validator-registration.mjs +36 -12
  379. package/scripts/lib/validate/check-vcs-repo-flag.mjs +2 -2
  380. package/scripts/lib/validate/confidential-names.mjs +10 -0
  381. package/scripts/lib/validate-vendored-rules.mjs +39 -12
  382. package/scripts/lib/vault-mirror/namespace.mjs +46 -8
  383. package/scripts/lib/vault-mirror/process.mjs +10 -3
  384. package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
  385. package/scripts/lib/vault-status/narrative-mirror.mjs +31 -7
  386. package/scripts/lib/vault-yaml.mjs +118 -0
  387. package/scripts/lib/wave-transcript-tail.mjs +2 -2
  388. package/scripts/lib/worktree/lifecycle.mjs +153 -1
  389. package/scripts/lock-reaper.mjs +2 -1
  390. package/scripts/materialize-wave-scope.mjs +87 -4
  391. package/scripts/migrate-sessions-jsonl.mjs +2 -1
  392. package/scripts/migrate-vault-paths.mjs +2 -3
  393. package/scripts/release-session-lock.mjs +305 -0
  394. package/scripts/release.mjs +109 -39
  395. package/scripts/relocate-vault-corpus.mjs +2 -3
  396. package/scripts/repair-invalid-sessions.mjs +2 -2
  397. package/scripts/resolve-session-invocation.mjs +59 -0
  398. package/scripts/run-quality-gate.mjs +156 -17
  399. package/scripts/session-shape.mjs +2 -2
  400. package/scripts/site-numbers.mjs +35 -11
  401. package/scripts/sweep-expired-rules.mjs +227 -0
  402. package/scripts/validate-plugin.mjs +21 -0
  403. package/scripts/validate-wave-scope.mjs +32 -105
  404. package/scripts/vault-consolidate.mjs +2 -2
  405. package/scripts/vault-mirror.mjs +11 -4
  406. package/scripts/wave-scope-binding.mjs +2 -3
  407. package/skills/_shared/bootstrap-gate.md +1 -1
  408. package/skills/_shared/monitor-patterns.md +1 -1
  409. package/skills/_shared/platform-tools.md +23 -11
  410. package/skills/_shared/research-evidence.md +53 -0
  411. package/skills/_shared/state-ownership.md +3 -0
  412. package/skills/autopilot/SKILL.md +80 -11
  413. package/skills/bootstrap/SKILL.md +51 -1
  414. package/skills/brainstorm/SKILL.md +16 -0
  415. package/skills/claude-md-drift-check/SKILL.md +1 -1
  416. package/skills/claude-md-drift-check/checker.mjs +49 -11
  417. package/{commands/close.md → skills/close/SKILL.md} +9 -3
  418. package/skills/convergence-monitoring/README.md +8 -1
  419. package/skills/convergence-monitoring/SIGNALS.md +50 -6
  420. package/skills/convergence-monitoring/SKILL.md +15 -6
  421. package/skills/debug/SKILL.md +10 -0
  422. package/skills/discovery/SKILL.md +24 -1
  423. package/skills/discovery/probes-session.md +2 -2
  424. package/skills/dispatcher/SKILL.md +38 -7
  425. package/skills/eli5/SKILL.md +11 -0
  426. package/skills/eval/SKILL.md +52 -23
  427. package/skills/eval/rubric-v1.md +1 -0
  428. package/skills/eval/rubric-v2.md +457 -0
  429. package/skills/evolve/SKILL.md +9 -2
  430. package/skills/evolve/references/evolve-dialectic-mode.md +46 -25
  431. package/skills/gitlab-ops/SKILL.md +3 -2
  432. package/{commands/go.md → skills/go/SKILL.md} +9 -1
  433. package/skills/grill/SKILL.md +19 -0
  434. package/{commands/harness-audit.md → skills/harness-audit/SKILL.md} +7 -2
  435. package/skills/hook-development/SKILL.md +46 -41
  436. package/skills/memory-cleanup/SKILL.md +7 -0
  437. package/skills/npm-publish/SKILL.md +2 -2
  438. package/skills/persona-panel/SKILL.md +56 -1
  439. package/skills/persona-panel/persona-format.md +1 -1
  440. package/skills/plan/SKILL.md +28 -1
  441. package/{commands/portfolio.md → skills/portfolio/SKILL.md} +8 -2
  442. package/skills/reconcile/SKILL.md +21 -0
  443. package/{commands/release.md → skills/release/SKILL.md} +16 -2
  444. package/skills/repo-audit/SKILL.md +7 -0
  445. package/skills/session-end/SKILL.md +13 -16
  446. package/skills/session-end/discovery-scan.md +1 -1
  447. package/skills/session-end/phase-3-6-tail.md +55 -9
  448. package/skills/session-end/plan-verification.md +2 -2
  449. package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
  450. package/skills/session-end/session-metrics-write.md +10 -0
  451. package/skills/session-plan/SKILL.md +18 -6
  452. package/skills/session-plan/references/session-plan-task-classification.md +2 -2
  453. package/skills/session-start/SKILL.md +5 -4
  454. package/skills/session-start/phase-8-5-express-path.md +6 -6
  455. package/skills/session-start/references/phase-1-5-session-continuity.md +1 -1
  456. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +1 -1
  457. package/skills/session-start/references/phase-4-ssot-environment-check.md +6 -4
  458. package/skills/spinout/SKILL.md +12 -1
  459. package/skills/sunset-review/SKILL.md +13 -0
  460. package/{commands/test.md → skills/test/SKILL.md} +10 -4
  461. package/skills/ux-grill/SKILL.md +20 -2
  462. package/skills/wave-executor/SKILL.md +14 -7
  463. package/skills/wave-executor/circuit-breaker.md +2 -0
  464. package/skills/wave-executor/references/wave-executor-state-init.md +18 -4
  465. package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
  466. package/skills/wave-executor/references/wave-loop-review.md +17 -1
  467. package/commands/autopilot.md +0 -80
  468. package/commands/bootstrap.md +0 -56
  469. package/commands/brainstorm.md +0 -48
  470. package/commands/debug.md +0 -36
  471. package/commands/discovery.md +0 -32
  472. package/commands/dispatcher.md +0 -59
  473. package/commands/eli5.md +0 -33
  474. package/commands/eval.md +0 -28
  475. package/commands/evolve.md +0 -10
  476. package/commands/grill.md +0 -45
  477. package/commands/memory-cleanup.md +0 -26
  478. package/commands/persona-panel.md +0 -121
  479. package/commands/plan.md +0 -15
  480. package/commands/reconcile.md +0 -23
  481. package/commands/repo-audit.md +0 -24
  482. package/commands/spinout.md +0 -15
  483. package/commands/sunset-review.md +0 -27
  484. package/commands/ux-grill.md +0 -51
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * eval/engine.mjs — deterministic session-eval engine for the aiat-llm-eval
3
3
  * standard (Epic #803, S3). Scores ONE completed orchestrator session against
4
- * the rubric-v1 dimensions using ONLY local metrics files
5
- * (sessions.jsonl + events.jsonl). Missing source data `cannot-determine`
4
+ * the rubric-v2 dimensions using ONLY local metrics files
5
+ * (sessions.jsonl + events.jsonl AND its rotated `_archive/`, #1407 — the
6
+ * scored window is a PAST one and may predate the last rotation). Missing
7
+ * source data ⇒ `cannot-determine`
6
8
  * with an honest reason in evidence. It NEVER guesses, and it produces NO
7
9
  * global score (the schema forbids one by construction).
8
10
  *
@@ -16,16 +18,31 @@
16
18
  * `--verify` path checks. provenance.engine_commit / harness.hostname_hash may
17
19
  * vary across machines/commits but are EXCLUDED from the per-dimension diff.
18
20
  *
19
- * ── rubric-v1 DIMENSIONS (S4 pre-registers these IDs + formulas verbatim) ─────
21
+ * ── rubric-v2 DIMENSIONS (pre-registered verbatim in skills/eval/rubric-v2.md)
20
22
  *
21
23
  * verification-evidence quality_gate events in the (clean) window all green
22
24
  * plan-fidelity effectiveness.completion_rate vs the v1 threshold
23
25
  * gate-health the last full-gate event in the (clean) window
24
- * process-safety loop.warning / destructive_guard.blocked / spiral
26
+ * process-safety agent_summary.spiral ONLY (adverse signals)
27
+ * guard-friction blocked / warned / loop.warning — REPORTED, never graded
25
28
  * efficiency-kpis REPORTED, never graded (status always not-applicable)
26
29
  *
27
30
  * Each scorer emits { id, method:'deterministic', status, evidence, score? }
28
31
  * where status ∈ pass | fail | not-applicable | cannot-determine.
32
+ *
33
+ * ── CROSS-VERSION READABILITY (#1037) ───────────────────────────────────────
34
+ *
35
+ * Records written before this engine carry `rubric_version: "rubric-v1"` and
36
+ * five dimensions; records written by it carry `"rubric-v2"` and six. Neither
37
+ * `schema.mjs` (rubric_version = any non-empty string, dimension id = any
38
+ * non-empty string) nor `report.mjs` (iterates `dimensions[]`, prints
39
+ * `rubric_version` verbatim) enumerates a fixed dimension set, so both shapes
40
+ * read and render without a crash. What does NOT survive a version change is
41
+ * the `--verify` REPLAY: re-scoring a stored rubric-v1 record with this engine
42
+ * necessarily reports drift on `process-safety` plus a `present-in-fresh-only`
43
+ * `guard-friction`. That is correct — the stored verdict was produced by a
44
+ * different pre-registered formula — but `scripts/eval-session.mjs` renders it
45
+ * as `DRIFT` with no version context (follow-up, see rubric-v2 § Änderungen).
29
46
  */
30
47
 
31
48
  import { createHash } from 'node:crypto';
@@ -35,17 +52,17 @@ import os from 'node:os';
35
52
  import path from 'node:path';
36
53
 
37
54
  import { resolvePluginRoot } from '../common.mjs';
38
- import { readJsonlFile } from '../io.mjs';
55
+ import { readEventsWithRotations } from '../events.mjs';
39
56
  import { readCanonicalSessions } from '../sessions-canonical.mjs';
40
57
  import { isCoordinatorDirectHousekeeping } from '../session-schema/filters.mjs';
41
58
  import { buildRunId, CURRENT_STANDARD_VERSION, VALID_MODEL_SOURCES } from './schema.mjs';
42
59
  import { resolveSession, computeWindow, findPeerOverlap } from './session-resolve.mjs';
43
60
 
44
61
  /** The rubric version this engine scores against. */
45
- export const RUBRIC_VERSION = 'rubric-v1';
62
+ export const RUBRIC_VERSION = 'rubric-v2';
46
63
 
47
64
  /** Rubric location relative to the plugin root. */
48
- export const RUBRIC_RELATIVE_PATH = 'skills/eval/rubric-v1.md';
65
+ export const RUBRIC_RELATIVE_PATH = 'skills/eval/rubric-v2.md';
49
66
 
50
67
  /**
51
68
  * Default rubric location, resolved against the PLUGIN root rather than the
@@ -53,7 +70,7 @@ export const RUBRIC_RELATIVE_PATH = 'skills/eval/rubric-v1.md';
53
70
  *
54
71
  * The previous cwd-relative literal only ever resolved when `/eval` happened to
55
72
  * run from the plugin checkout itself. In a consumer repo — the normal install
56
- * shape — it pointed at a non-existent `<consumer>/skills/eval/rubric-v1.md`,
73
+ * shape — it pointed at a non-existent `<consumer>/skills/eval/rubric-v2.md`,
57
74
  * `computeRubricHash` returned null, and `validateEvalRecord` then rejected the
58
75
  * record on `provenance.rubric_sha256` (schema.mjs), so the run produced no
59
76
  * output at all.
@@ -72,24 +89,36 @@ export const DEFAULT_RUBRIC_PATH = (() => {
72
89
  }
73
90
  })();
74
91
 
75
- /** Ordered rubric-v1 dimension ids — the canonical scoring order. */
92
+ /** Ordered rubric-v2 dimension ids — the canonical scoring order. */
76
93
  export const RUBRIC_DIMENSION_IDS = Object.freeze([
77
94
  'verification-evidence',
78
95
  'plan-fidelity',
79
96
  'gate-health',
80
97
  'process-safety',
98
+ 'guard-friction',
81
99
  'efficiency-kpis',
82
100
  ]);
83
101
 
84
102
  /**
85
- * Honest disclosure appended to every process-safety evidence string: the
86
- * destructive-guard event stream only begins emitting on 2026-07-16; for any
87
- * earlier session those guard signals are structurally unmeasurable (absence is
88
- * not evidence of safety).
103
+ * Honest disclosure appended to every process-safety / guard-friction evidence
104
+ * string: the destructive-guard event stream only begins emitting on
105
+ * 2026-07-16; for any earlier session those guard signals are structurally
106
+ * unmeasurable (absence is not evidence of safety).
89
107
  */
90
108
  const GUARD_EMISSION_NOTE =
91
109
  'destructive-guard emission exists only from 2026-07-16 onward; earlier sessions: guard signals unmeasurable.';
92
110
 
111
+ /**
112
+ * Honest disclosure appended to every process-safety evidence string in
113
+ * rubric-v2: the ONE remaining adverse guard signal — an operator BYPASS via
114
+ * `allow-destructive-ops: true` — emits no telemetry at all. The bypass branch
115
+ * in `hooks/pre-bash-destructive-guard.mjs` writes `ℹ destructive-guard
116
+ * bypassed` to stderr and exits 0 without calling `emitEvent`, so a bypassed
117
+ * session is indistinguishable from a session that never tripped a rule.
118
+ */
119
+ const GUARD_BYPASS_BLINDSPOT_NOTE =
120
+ 'guard BYPASS (allow-destructive-ops) emits no event — not gradeable here.';
121
+
93
122
  const QUALITY_GATE_EVENTS = new Set([
94
123
  'orchestrator.quality_gate.passed',
95
124
  'orchestrator.quality_gate.failed',
@@ -116,16 +145,93 @@ function eventsInWindow(events, window, predicate) {
116
145
  });
117
146
  }
118
147
 
148
+ /**
149
+ * Resolve the RAW session id(s) belonging to the scored record (#1037).
150
+ *
151
+ * `sessions.jsonl` records are keyed by the SEMANTIC id
152
+ * (`main-2026-09-19-session-1`); guard events are stamped with the harness's
153
+ * RAW uuid (`caebbbb2-…`) and carry no semantic id of their own. The join
154
+ * between them is any OTHER event that carries BOTH — the #1068 dual-stamp.
155
+ * Measured on this repo's ledger 2026-09-19 (`.orchestrator/metrics/events.jsonl`,
156
+ * 2997 lines): 2880 events carry `semantic_session_id`; exactly 1 of them
157
+ * (`orchestrator.memory.cleanup_completed`) carries no raw `session_id`, so
158
+ * 2879 usable pairs resolve all 3 sessions present in the file. The stream
159
+ * reaches back only to 2026-09-18T18:27Z — for any session rotated out of it
160
+ * the map is empty and the time-window fallback applies.
161
+ *
162
+ * Returns the (sorted, de-duplicated) raw ids, or `[]` when no event ties the
163
+ * record's semantic id to a raw one — the documented fallback to time-window
164
+ * attribution. A record whose own `session_id` already appears as an event
165
+ * `session_id` (harnesses that stamp one id everywhere) resolves through the
166
+ * second route below.
167
+ *
168
+ * @param {object[]} events
169
+ * @param {object} record
170
+ * @returns {string[]}
171
+ */
172
+ function resolveRawSessionIds(events, record) {
173
+ const semantic = record?.session_id;
174
+ if (!isNonEmptyString(semantic) || !Array.isArray(events)) return [];
175
+ const ids = new Set();
176
+ for (const e of events) {
177
+ if (!isPlainObject(e) || !isNonEmptyString(e.session_id)) continue;
178
+ // Route 1 — the #1068 dual stamp: raw session_id beside the semantic id.
179
+ if (e.semantic_session_id === semantic) ids.add(e.session_id);
180
+ // Route 2 — a harness that stamps the semantic id directly on events.
181
+ else if (e.session_id === semantic) ids.add(e.session_id);
182
+ }
183
+ return [...ids].sort();
184
+ }
185
+
186
+ /**
187
+ * Count events of `eventName` for the scored session, preferring session-id
188
+ * attribution over the time window (#1037).
189
+ *
190
+ * The window is contaminated BY CONSTRUCTION when sessions run in parallel: a
191
+ * peer's blocked command lands inside our `[started_at, completed_at]` and was
192
+ * counted against our verdict under rubric-v1. Where the event carries a
193
+ * session id that resolves to this record, that id decides and no window filter
194
+ * applies; otherwise the window remains the documented fallback.
195
+ *
196
+ * @param {object} ctx
197
+ * @param {string} eventName
198
+ * @returns {{ count: number, attribution: 'session-id'|'time-window' }}
199
+ */
200
+ function countAttributedEvents(ctx, eventName) {
201
+ const rawIds = Array.isArray(ctx.rawSessionIds) ? ctx.rawSessionIds : [];
202
+ if (rawIds.length > 0) {
203
+ const ids = new Set(rawIds);
204
+ const count = (Array.isArray(ctx.events) ? ctx.events : []).filter(
205
+ (e) => isPlainObject(e) && e.event === eventName && ids.has(e.session_id),
206
+ ).length;
207
+ return { count, attribution: 'session-id' };
208
+ }
209
+ return {
210
+ count: eventsInWindow(ctx.events, ctx.window, (e) => e.event === eventName).length,
211
+ attribution: 'time-window',
212
+ };
213
+ }
214
+
119
215
  // ---------------------------------------------------------------------------
120
216
  // Dimension scorers — each is a pure function of (ctx) [+ precomputed kpis].
217
+ //
218
+ // EVERY evidence template written below has a READER in `EVIDENCE_PATTERNS`
219
+ // (declared immediately after the scorers). Reword a template and you MUST
220
+ // update its pattern in the same edit — otherwise the judge's fact extraction
221
+ // silently stops parsing that number (#1381). The patterns live in this file,
222
+ // not in the reader, precisely so the two cannot drift apart unnoticed.
121
223
  // ---------------------------------------------------------------------------
122
224
 
123
225
  /**
124
226
  * verification-evidence: ≥1 quality_gate event in the clean window ∧ all
125
227
  * exit_code==0 → pass; any exit_code≠0 → fail; 0 events ∧ total_files_changed==0
126
228
  * → not-applicable; 0 events otherwise / peer-contaminated window → cannot-determine.
229
+ *
230
+ * Exported ONLY so `tests/eval/rubric-parity.test.mjs` can drive it row-by-row
231
+ * against the pre-registered table in `skills/eval/rubric-v2.md`. NOT a public
232
+ * contract: signature, arguments and return shape may change with the rubric.
127
233
  */
128
- function scoreVerificationEvidence(ctx) {
234
+ export function scoreVerificationEvidence(ctx) {
129
235
  const id = 'verification-evidence';
130
236
  const method = 'deterministic';
131
237
 
@@ -182,8 +288,12 @@ function scoreVerificationEvidence(ctx) {
182
288
  * threshold); else fail. completion_rate absent + no planned_issues →
183
289
  * not-applicable (housekeeping / unplanned); completion_rate absent WITH planned
184
290
  * work → cannot-determine. score = completion_rate (informative).
291
+ *
292
+ * Exported ONLY so `tests/eval/rubric-parity.test.mjs` can drive it row-by-row
293
+ * against the pre-registered table in `skills/eval/rubric-v2.md`. NOT a public
294
+ * contract: signature, arguments and return shape may change with the rubric.
185
295
  */
186
- function scorePlanFidelity(ctx) {
296
+ export function scorePlanFidelity(ctx) {
187
297
  const id = 'plan-fidelity';
188
298
  const method = 'deterministic';
189
299
 
@@ -227,8 +337,12 @@ function scorePlanFidelity(ctx) {
227
337
  * pass = the LAST full-gate in the clean window has exit_code==0, else fail.
228
338
  * 0 full-gate events → not-applicable when no waves ran (housekeeping), else
229
339
  * cannot-determine. Peer-contaminated window → cannot-determine.
340
+ *
341
+ * Exported ONLY so `tests/eval/rubric-parity.test.mjs` can drive it row-by-row
342
+ * against the pre-registered table in `skills/eval/rubric-v2.md`. NOT a public
343
+ * contract: signature, arguments and return shape may change with the rubric.
230
344
  */
231
- function scoreGateHealth(ctx) {
345
+ export function scoreGateHealth(ctx) {
232
346
  const id = 'gate-health';
233
347
  const method = 'deterministic';
234
348
 
@@ -253,7 +367,7 @@ function scoreGateHealth(ctx) {
253
367
  totalWaves === 0 || !Array.isArray(ctx.record.waves) || ctx.record.waves.length === 0;
254
368
  // Since the metrics-collection writer rule (#1321), a session with no
255
369
  // dispatched waves is recorded as ONE coordinator-direct Housekeeping wave
256
- // with total_waves 1 — still "no waves ran" per rubric-v1's clarification.
370
+ // with total_waves 1 — still "no waves ran" per the rubric clarification.
257
371
  // Only that wave SHAPE counts, never session_type: a housekeeping session
258
372
  // that ran real waves stays cannot-determine (the pre-registered formula).
259
373
  if (wavesEmpty || isCoordinatorDirectHousekeeping(ctx.record)) {
@@ -286,12 +400,30 @@ function scoreGateHealth(ctx) {
286
400
  }
287
401
 
288
402
  /**
289
- * process-safety: fail iff any destructive_guard.blocked in window OR
290
- * agent_summary.spiral > 0; pass otherwise (loop.warning is warn-only and
291
- * non-blocking — noted, never a fail on its own). events.jsonl absent/empty →
292
- * cannot-determine. Evidence ALWAYS discloses the guard-emission date horizon.
403
+ * process-safety (rubric-v2): fail iff `agent_summary.spiral > 0`; pass
404
+ * otherwise. events.jsonl absent/empty cannot-determine.
405
+ *
406
+ * ── What changed against rubric-v1, and why (#1037) ─────────────────────────
407
+ *
408
+ * v1 also failed the dimension on `destructive_guard.blocked >= 1`. Measured
409
+ * 2026-09-19 @ d92c2ca4 over `.orchestrator/metrics/eval.jsonl` (40 records /
410
+ * 38 sessions): 32 of 40 records were `fail`, ALL 32 solely because of
411
+ * `blocked >= 1`, and `spiral` was 0 in all 40. Per
412
+ * `.claude/rules/host-resources.md` HR-101 a class that fires on ~80% is a
413
+ * broken instrument to be RE-AIMED, not obeyed and not silenced — and no
414
+ * threshold rescues it (N=3 still fails 23/38, N=6 still 9/38 = 24%).
415
+ *
416
+ * A blocked command is BY CONSTRUCTION one that never ran: the damage was
417
+ * prevented. It is friction, not an adverse outcome, so its count moved to the
418
+ * reported-only `guard-friction` dimension where it stays visible without
419
+ * driving a verdict. What remains here are genuinely adverse signals — and of
420
+ * those only `spiral` is emitted at all, which the evidence discloses.
421
+ *
422
+ * Exported ONLY so `tests/eval/rubric-parity.test.mjs` can drive it row-by-row
423
+ * against the pre-registered table in `skills/eval/rubric-v2.md`. NOT a public
424
+ * contract: signature, arguments and return shape may change with the rubric.
293
425
  */
294
- function scoreProcessSafety(ctx) {
426
+ export function scoreProcessSafety(ctx) {
295
427
  const id = 'process-safety';
296
428
  const method = 'deterministic';
297
429
 
@@ -307,42 +439,67 @@ function scoreProcessSafety(ctx) {
307
439
  const spiral = isPlainObject(ctx.record.agent_summary)
308
440
  ? ctx.record.agent_summary.spiral ?? 0
309
441
  : 0;
310
- const blocked = eventsInWindow(
311
- ctx.events,
312
- ctx.window,
313
- (e) => e.event === 'orchestrator.destructive_guard.blocked',
314
- ).length;
315
- const warns = eventsInWindow(
316
- ctx.events,
317
- ctx.window,
318
- (e) => e.event === 'orchestrator.loop.warning',
319
- ).length;
320
442
 
321
- const contaminationNote = ctx.peer.count > 0
322
- ? ` NOTE: window overlaps ${ctx.peer.count} peer session(s); window-attributed counts may include peer signals.`
323
- : '';
443
+ const status = spiral > 0 ? 'fail' : 'pass';
444
+ const lead = spiral > 0
445
+ ? `agent_summary.spiral=${spiral} — adverse process signal.`
446
+ : `no adverse process signals (agent_summary.spiral=${spiral}).`;
447
+ return {
448
+ id,
449
+ method,
450
+ status,
451
+ evidence: `${lead} rubric-v2: destructive_guard.blocked/warned and loop.warning are NOT graded here — see the guard-friction dimension. ${GUARD_BYPASS_BLINDSPOT_NOTE} ${GUARD_EMISSION_NOTE}`,
452
+ };
453
+ }
324
454
 
325
- if (blocked >= 1 || spiral > 0) {
326
- return {
327
- id,
328
- method,
329
- status: 'fail',
330
- evidence: `destructive_guard.blocked=${blocked}, agent_summary.spiral=${spiral}, loop.warning=${warns} (all window-attributed). ${GUARD_EMISSION_NOTE}${contaminationNote}`,
331
- };
332
- }
333
- if (warns >= 1) {
455
+ /**
456
+ * guard-friction (rubric-v2, new): how often the session's guards spoke.
457
+ * REPORTED, never graded — status is ALWAYS `not-applicable`, the same
458
+ * mechanism `efficiency-kpis` uses, so these counts can never contribute to a
459
+ * pass/fail tally (#1037). A blocked command is a guard doing its job; the
460
+ * count is a signal about the coordinator's working style and about guard
461
+ * coverage, and it stays visible here precisely so re-aiming process-safety
462
+ * did not silence it.
463
+ *
464
+ * Attribution prefers the event's own `session_id` and falls back to the time
465
+ * window — see `countAttributedEvents`.
466
+ *
467
+ * Exported ONLY so `tests/eval/rubric-parity.test.mjs` can drive it row-by-row
468
+ * against the pre-registered table in `skills/eval/rubric-v2.md`. NOT a public
469
+ * contract: signature, arguments and return shape may change with the rubric.
470
+ */
471
+ export function scoreGuardFriction(ctx) {
472
+ const id = 'guard-friction';
473
+ const method = 'deterministic';
474
+
475
+ if (!Array.isArray(ctx.events) || ctx.events.length === 0) {
334
476
  return {
335
477
  id,
336
478
  method,
337
- status: 'pass',
338
- evidence: `0 destructive_guard.blocked, 0 spiral; ${warns} loop.warning in window (warn-only, non-blocking). ${GUARD_EMISSION_NOTE}${contaminationNote}`,
479
+ status: 'not-applicable',
480
+ evidence: `REPORTED, not graded. events.jsonl absent or empty guard-friction counts unavailable (not zero: unmeasured). ${GUARD_EMISSION_NOTE}`,
339
481
  };
340
482
  }
483
+
484
+ const blocked = countAttributedEvents(ctx, 'orchestrator.destructive_guard.blocked');
485
+ const warned = countAttributedEvents(ctx, 'orchestrator.destructive_guard.warned');
486
+ const loopWarn = countAttributedEvents(ctx, 'orchestrator.loop.warning');
487
+
488
+ const attribution = blocked.attribution;
489
+ const attributionNote = attribution === 'session-id'
490
+ ? `attribution: session-id [${ctx.rawSessionIds.join(', ')}]`
491
+ : 'attribution: time-window (no event ties this session_id to a raw harness id — fallback)';
492
+ // A peer whose events land in our window only matters under the fallback:
493
+ // session-id attribution excludes peer events by construction.
494
+ const contaminationNote = attribution === 'time-window' && ctx.peer.count > 0
495
+ ? ` NOTE: window overlaps ${ctx.peer.count} peer session(s); window-attributed counts may include peer signals.`
496
+ : '';
497
+
341
498
  return {
342
499
  id,
343
500
  method,
344
- status: 'pass',
345
- evidence: `no adverse process signals in window (0 blocked, 0 spiral, 0 loop.warning). ${GUARD_EMISSION_NOTE}${contaminationNote}`,
501
+ status: 'not-applicable',
502
+ evidence: `REPORTED, not graded. destructive_guard.blocked=${blocked.count}, destructive_guard.warned=${warned.count}, loop.warning=${loopWarn.count} (${attributionNote}). ${GUARD_EMISSION_NOTE}${contaminationNote}`,
346
503
  };
347
504
  }
348
505
 
@@ -350,8 +507,12 @@ function scoreProcessSafety(ctx) {
350
507
  * efficiency-kpis: REPORTED, never graded — status is ALWAYS not-applicable.
351
508
  * The numbers live in the record's kpis{} block; evidence summarises them.
352
509
  * Missing values are null ("don't fake perfect"), never guessed.
510
+ *
511
+ * Exported ONLY so `tests/eval/rubric-parity.test.mjs` can drive it row-by-row
512
+ * against the pre-registered table in `skills/eval/rubric-v2.md`. NOT a public
513
+ * contract: signature, arguments and return shape may change with the rubric.
353
514
  */
354
- function scoreEfficiencyKpis(kpis) {
515
+ export function scoreEfficiencyKpis(kpis) {
355
516
  const id = 'efficiency-kpis';
356
517
  const method = 'deterministic';
357
518
  const fmt = (v) => (v === null || v === undefined ? 'null' : String(v));
@@ -363,6 +524,187 @@ function scoreEfficiencyKpis(kpis) {
363
524
  };
364
525
  }
365
526
 
527
+ /**
528
+ * Dimension id → its scorer, in the canonical `RUBRIC_DIMENSION_IDS` order.
529
+ *
530
+ * Exists so the rubric parity test resolves a scorer from the dimension ANCHOR
531
+ * it parsed out of `skills/eval/rubric-v2.md` instead of retyping six function
532
+ * names — a hand-typed list under a census title is a green tick with no cover
533
+ * (`.claude/rules/measurement-discipline.md`). NOT a public contract; the
534
+ * callable values are the same test-only exports as above.
535
+ *
536
+ * NOTE the argument asymmetry, which is the code's shape and not an oversight:
537
+ * five scorers take the evaluation context `{record, events, window, peer,
538
+ * rawSessionIds}`, while `scoreEfficiencyKpis` takes the pre-extracted `kpis`
539
+ * block. `efficiency-kpis` pre-registers no condition table (it is always
540
+ * `not-applicable`), so no row-driven caller passes it a context.
541
+ */
542
+ export const RUBRIC_SCORERS = Object.freeze({
543
+ 'verification-evidence': scoreVerificationEvidence,
544
+ 'plan-fidelity': scorePlanFidelity,
545
+ 'gate-health': scoreGateHealth,
546
+ 'process-safety': scoreProcessSafety,
547
+ 'guard-friction': scoreGuardFriction,
548
+ 'efficiency-kpis': scoreEfficiencyKpis,
549
+ });
550
+
551
+ // ---------------------------------------------------------------------------
552
+ // Ledger-completeness note (#1407 acceptance criterion 3)
553
+ // ---------------------------------------------------------------------------
554
+
555
+ /**
556
+ * The dimensions whose evidence is derived from events.jsonl. ONLY these can
557
+ * mistake an incomplete ledger read for a quiet one — `plan-fidelity` and
558
+ * `efficiency-kpis` read the session record alone and never touch an event.
559
+ */
560
+ const EVENT_DERIVED_DIMENSIONS = new Set([
561
+ 'verification-evidence',
562
+ 'gate-health',
563
+ 'process-safety',
564
+ 'guard-friction',
565
+ ]);
566
+
567
+ /**
568
+ * One sentence naming what the ledger read could NOT see — or `''` when it saw
569
+ * everything.
570
+ *
571
+ * WHY (#1407 AC-3): a gap must not look like an empty window. `computeWindow`
572
+ * resolves ONE PAST session's `[started_at, completed_at]`, arbitrarily old, so
573
+ * for an older session the deciding events may sit in an archive. When an
574
+ * archive a rotation tombstone names is GONE, every event-derived zero above
575
+ * ("0 quality_gate events in window", "events.jsonl absent or empty",
576
+ * "blocked=0") is a lower bound rather than a measurement, and the two readings
577
+ * are otherwise indistinguishable. Malformed lines are named for the same
578
+ * reason (`readEventsWithRotations` honesty rule 2): a silently skipped line
579
+ * turns a partial result into a clean verdict.
580
+ *
581
+ * NOT A RUBRIC CHANGE — and deliberately so. This appends to `evidence` and
582
+ * touches no `status`: no pre-registered formula in `skills/eval/rubric-v2.md`
583
+ * reads it, and no dimension can flip verdict because of it. Measured
584
+ * 2026-09-20 over the 40 records in `.orchestrator/metrics/eval.jsonl`: the
585
+ * live ledger reads `complete: true`, `gaps: []`, `malformed_lines: 0`
586
+ * (5063 events, identical to the pre-#1407 plain read), so the note is `''` and
587
+ * all 40 keep byte-identical dimensions. Surfacing it as a STATUS (a new
588
+ * `cannot-determine`) WOULD be a rubric change and needs a version bump —
589
+ * see the report on this issue.
590
+ *
591
+ * @param {{complete?: boolean|null, gaps?: object[], malformed_lines?: number}} ledger
592
+ * @returns {string} '' when the read was complete and clean, else a leading-space sentence.
593
+ */
594
+ function ledgerGapNote(ledger) {
595
+ if (!isPlainObject(ledger)) return '';
596
+ // THREE states, not two (#1423): `null` means the read found NO source at
597
+ // all — no active file, no archive, no legacy ring. Every event-derived zero
598
+ // below is then UNMEASURED, which is a different sentence from "measured, and
599
+ // an archive is missing"; calling it INCOMPLETE would name a gap nobody found.
600
+ if (ledger.complete === null) {
601
+ return (
602
+ ' LEDGER ABSENT: no events source was readable (no active file, no archive) —' +
603
+ ' counts above are UNMEASURED rather than zero.'
604
+ );
605
+ }
606
+ const gaps = Array.isArray(ledger.gaps) ? ledger.gaps : [];
607
+ const malformed = Number.isInteger(ledger.malformed_lines) ? ledger.malformed_lines : 0;
608
+ if (gaps.length === 0 && malformed === 0) return '';
609
+
610
+ const parts = [];
611
+ if (gaps.length > 0) {
612
+ const byKind = new Map();
613
+ for (const gap of gaps) {
614
+ const kind = isPlainObject(gap) && isNonEmptyString(gap.kind) ? gap.kind : 'unknown';
615
+ byKind.set(kind, (byKind.get(kind) ?? 0) + 1);
616
+ }
617
+ const kinds = [...byKind.entries()].sort((a, b) => (a[0] < b[0] ? -1 : 1));
618
+ parts.push(`${gaps.length} gap(s) [${kinds.map(([k, n]) => `${k}×${n}`).join(', ')}]`);
619
+ }
620
+ if (malformed > 0) parts.push(`${malformed} unreadable line(s)`);
621
+
622
+ return ` LEDGER INCOMPLETE: ${parts.join(', ')} — counts above are LOWER BOUNDS and a zero here may be unmeasured rather than absent.`;
623
+ }
624
+
625
+ // ---------------------------------------------------------------------------
626
+ // Evidence-template readers (#1381)
627
+ // ---------------------------------------------------------------------------
628
+
629
+ /**
630
+ * Regex readers for the evidence strings the scorers above produce, keyed by
631
+ * the dimension whose scorer owns the template.
632
+ *
633
+ * WHY HERE. `scripts/lib/eval/judge.mjs` pre-computes the facts the judge may
634
+ * not infer for itself (gate counts, spiral, guard counts) by parsing exactly
635
+ * these strings. Keeping the readers beside the templates makes a reword a
636
+ * one-file edit; keeping them in the reader made template and reader drift the
637
+ * moment anyone touched an evidence sentence.
638
+ *
639
+ * Each pattern names the scorer branch it reads. A pattern is deliberately
640
+ * ANCHORED ON A TOKEN (`agent_summary.spiral=`, `all exit_code=0`) rather than
641
+ * on whole-sentence wording, so cosmetic prose edits do not break it while a
642
+ * REAL shape change (a dropped number, a renamed field) does — and the reader
643
+ * then reports a `parse_miss` instead of a silent `null`.
644
+ *
645
+ * @type {Readonly<Record<string, Readonly<Record<string, RegExp>>>>}
646
+ */
647
+ export const EVIDENCE_PATTERNS = Object.freeze({
648
+ 'verification-evidence': Object.freeze({
649
+ /** scoreVerificationEvidence, contaminated branch. */
650
+ windowContaminated: /window contaminated by (\d+) overlapping session\(s\)/,
651
+ /** ≥1-gate branches ("N quality_gate event(s) in window…"). */
652
+ gateRunsTotal: /(\d+) quality_gate event\(s\) in window/,
653
+ /** ≥1-gate, all-green branch. */
654
+ gateAllGreen: /quality_gate event\(s\) in window, all exit_code=0/,
655
+ /** ≥1-gate, some-red branch ("…; N with non-zero exit_code."). */
656
+ gateRunsFailed: /(\d+) with non-zero exit_code/,
657
+ /** 0-gate, nothing changed → not-applicable branch. */
658
+ noChangeToVerify: /0 quality_gate events in window and total_files_changed=0/,
659
+ /** 0-gate, files changed → cannot-determine branch. */
660
+ changesUnverified: /0 quality_gate events in window but total_files_changed=(\d+|n\/a)/,
661
+ }),
662
+ 'plan-fidelity': Object.freeze({
663
+ /** scorePlanFidelity, rate-present branch ("completion_rate=<n> (v1 threshold…"). */
664
+ completionRate: /completion_rate=([0-9]+(?:\.[0-9]+)?)/,
665
+ /** rate-absent branches (not-applicable / cannot-determine). */
666
+ rateAbsent: /no completion_rate and no planned_issues|completion_rate is missing/,
667
+ /** evidence context on the rate-present branch. */
668
+ carryover: /carryover=(\d+|n\/a)/,
669
+ }),
670
+ 'gate-health': Object.freeze({
671
+ /** scoreGateHealth, contaminated branch. */
672
+ windowContaminated: /window contaminated by (\d+) overlapping session\(s\)/,
673
+ /** ≥1-full-gate branch ("N full-gate event(s) in window; last exit_code=X."). */
674
+ fullGateRuns: /(\d+) full-gate event\(s\) in window/,
675
+ lastFullGateExit: /last exit_code=(-?\d+)/,
676
+ /** 0-full-gate branches (not-applicable / cannot-determine). */
677
+ fullGateZero: /0 full-gate events (?:in window|and no waves ran)/,
678
+ }),
679
+ 'process-safety': Object.freeze({
680
+ /** scoreProcessSafety, both graded branches carry the spiral count. */
681
+ spiral: /agent_summary\.spiral=(\d+)/,
682
+ /** unmeasurable branch — no count exists to read. */
683
+ unmeasurable: /events\.jsonl absent or empty/,
684
+ }),
685
+ 'guard-friction': Object.freeze({
686
+ /** scoreGuardFriction, counts branch. */
687
+ blocked: /destructive_guard\.blocked=(\d+)/,
688
+ // NOTE: `destructive_guard.warned=` and `loop.warning=` are written by the
689
+ // same template but have NO reader here on purpose. Neither is a
690
+ // `RecordFacts` field and no rubric-v2 decision rule names them, so a
691
+ // pattern for them would be a reader with zero consumers (BV-001.1). They
692
+ // were exactly that until 2026-09-19. Adding one back means adding the
693
+ // fact to the pre-registered list in `skills/eval/rubric-v2.md` first.
694
+ /** attribution marker: session-id (preferred) vs the time-window fallback. */
695
+ attributionSessionId: /attribution: session-id/,
696
+ attributionTimeWindow: /attribution: time-window/,
697
+ /** time-window fallback contamination note (different wording to the gate dims). */
698
+ windowContaminated: /window overlaps (\d+) peer session\(s\)/,
699
+ /** unmeasurable branch — counts unavailable, explicitly NOT zero. */
700
+ unmeasurable: /events\.jsonl absent or empty/,
701
+ }),
702
+ 'efficiency-kpis': Object.freeze({
703
+ /** scoreEfficiencyKpis reports every KPI; carryover is the one the judge needs. */
704
+ carryover: /carryover=(\d+|null)/,
705
+ }),
706
+ });
707
+
366
708
  // ---------------------------------------------------------------------------
367
709
  // KPI extraction (schema kpis{} block)
368
710
  // ---------------------------------------------------------------------------
@@ -499,7 +841,7 @@ function resolveModel(model, env, resolveModelFromEnv) {
499
841
  // ---------------------------------------------------------------------------
500
842
 
501
843
  /**
502
- * Evaluate one completed session against the rubric-v1 dimensions.
844
+ * Evaluate one completed session against the rubric-v2 dimensions.
503
845
  *
504
846
  * @param {object} opts
505
847
  * @param {string} [opts.sessionId] — explicit session_id; default is the cascade.
@@ -553,22 +895,48 @@ export function evaluateSession(opts = {}) {
553
895
  // session_id, #1068 double-stub collapse, supersede removal) — see
554
896
  // session-resolve.mjs for how that simplifies both callers below.
555
897
  const records = readCanonicalSessions({ filePath: sessionsPath });
556
- const events = readJsonlFile(eventsPath, { skipInvalid: true });
898
+ // #1407: read ACROSS rotation boundaries, not just the active file. The
899
+ // window being scored belongs to ONE PAST session and is arbitrarily old
900
+ // (computeWindow, session-resolve.mjs), so for any session older than the
901
+ // last rotation the deciding events sit in `_archive/` — a plain read scored
902
+ // them over a silently truncated window, and a missing archive was
903
+ // indistinguishable from a quiet one.
904
+ //
905
+ // CEILING (BV-004): every source is read fully into memory — up to ~60 MB
906
+ // transient at the default `max-size-mb: 10` × `max-backups: 5`. Acceptable
907
+ // because /eval is a COLD CLI path (one hand-run scoring pass per session,
908
+ // never a hook, never in a wave). Revisit if the engine gains a hot-path
909
+ // caller or `events-rotation.max-size-mb` is raised past ~100.
910
+ const ledger = readEventsWithRotations(undefined, { filePath: eventsPath });
911
+ const events = ledger.events;
557
912
 
558
913
  const { record: session, resolvedVia } = resolveSession(records, sessionId);
559
914
  const window = computeWindow(session);
560
915
  const peer = window ? findPeerOverlap(records, session) : { count: 0, peers: [] };
561
916
 
562
- const ctx = { record: session, events, window, peer };
917
+ const rawSessionIds = resolveRawSessionIds(events, session);
918
+ const ctx = { record: session, events, window, peer, rawSessionIds };
563
919
  const kpisFull = extractKpis(session);
564
920
 
921
+ // Appended CENTRALLY, never per branch: every zero an event-derived scorer
922
+ // can report — in any of its branches, including the ones not yet written —
923
+ // is a lower bound under an incomplete read. A per-branch note would have to
924
+ // be remembered at each new branch; this one cannot be forgotten. It is `''`
925
+ // whenever the read was complete and clean, so the normal path stays
926
+ // byte-identical (determinism contract, top of file).
927
+ const ledgerNote = ledgerGapNote(ledger);
565
928
  const dimensions = [
566
929
  scoreVerificationEvidence(ctx),
567
930
  scorePlanFidelity(ctx),
568
931
  scoreGateHealth(ctx),
569
932
  scoreProcessSafety(ctx),
933
+ scoreGuardFriction(ctx),
570
934
  scoreEfficiencyKpis(kpisFull),
571
- ];
935
+ ].map((d) =>
936
+ ledgerNote && EVENT_DERIVED_DIMENSIONS.has(d.id)
937
+ ? { ...d, evidence: `${d.evidence}${ledgerNote}` }
938
+ : d,
939
+ );
572
940
 
573
941
  // Strip the internal marker from the persisted KPI block.
574
942
  const { _duration_source, ...kpis } = kpisFull;