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
@@ -18,7 +18,11 @@
18
18
  * over the target (atomic), and the result is then re-verified against BOTH
19
19
  * `validateSession` and `checkSessionsIntegrity`. A failed verification restores
20
20
  * the backup byte-identically and exits 3 — the ledger is never left in a state
21
- * worse than the one this CLI found.
21
+ * worse than the one this CLI found. Records with no defensible repair (a
22
+ * timestamp that is not ISO-8601 at all, a waves entry that is not an object)
23
+ * are kept byte-identical and do NOT trigger that rollback (#1442): the repair
24
+ * of every other line is kept, and the run exits 4 so a caller can tell "clean"
25
+ * from "clean except N lines nobody can repair".
22
26
  *
23
27
  * TARGET CONSTRAINT (MED-3): `--file` MUST resolve inside
24
28
  * `<repo-root>/.orchestrator/metrics/`. Any target outside it — including a
@@ -32,6 +36,10 @@
32
36
  * 1 — user/input error (bad flag, unknown argument)
33
37
  * 2 — system error (unreadable/unwritable ledger, I/O failure)
34
38
  * 3 — post-verification failed; the backup was restored and nothing was kept
39
+ * 4 — completed, but N unrepairable lines remain (apply: every repairable
40
+ * line was fixed and verified, the N were kept byte-identical; dry-run:
41
+ * the N lines that WOULD remain are listed). Checked after 3: a
42
+ * rolled-back run is always 3, never 4.
35
43
  */
36
44
 
37
45
  import fs from 'node:fs';
@@ -53,7 +61,9 @@ const USAGE =
53
61
  ');\n' +
54
62
  ' must resolve inside <repo-root>/.orchestrator/metrics/\n' +
55
63
  ' --repo-root project root (default: resolved project dir)\n' +
56
- 'Exit codes: 0 completed, 1 arg error, 2 system error, 3 post-verification failed\n';
64
+ 'Exit codes: 0 completed, 1 arg error, 2 system error, 3 post-verification failed\n' +
65
+ ' (backup restored), 4 unrepairable remain (N lines kept byte-identical;\n' +
66
+ ' every repairable line fixed — dry-run: would remain)\n';
57
67
 
58
68
  /**
59
69
  * Resolve `--file` and REFUSE any target outside
@@ -127,7 +137,10 @@ export function renderHuman(s) {
127
137
  }
128
138
  if (s.backup_path) lines.push(` backup: ${s.backup_path}`);
129
139
  if (s.errors.length > 0) {
130
- lines.push(` errors: ${s.errors.length} (original line kept)`);
140
+ // #1442: name every line that stays invalid — in dry-run BEFORE anything is
141
+ // written, so the operator knows the remainder --apply will leave behind.
142
+ const verb = s.mode === 'apply' ? 'remain' : 'would remain';
143
+ lines.push(` unrepairable: ${s.errors.length} (${verb} invalid; original line kept byte-identical)`);
131
144
  for (const e of s.errors) lines.push(` line ${e.line} ${e.session_id ?? '<no session_id>'}: ${e.error}`);
132
145
  }
133
146
  if (s.post_verify && s.post_verify.integrity !== 'clean' && s.post_verify.integrity !== 'skipped-not-canonical-path') {
@@ -135,10 +148,30 @@ export function renderHuman(s) {
135
148
  }
136
149
  if (s.ok === false) {
137
150
  lines.push(' POST-VERIFICATION FAILED — backup restored, ledger unchanged.');
151
+ } else if (s.errors.length > 0) {
152
+ lines.push(
153
+ s.mode === 'apply'
154
+ ? ` Repairable lines fixed and verified; ${s.errors.length} unrepairable line(s) left as they were (exit 4).`
155
+ : ` --apply would fix every repairable line and leave ${s.errors.length} unrepairable line(s) as they are (exit 4).`
156
+ );
138
157
  }
139
158
  return lines.join('\n') + '\n';
140
159
  }
141
160
 
161
+ /**
162
+ * Map a `repairLedger` summary to the exit-code contract in the header.
163
+ * Rollback (3) wins over an unrepairable remainder (4): a restored ledger kept
164
+ * nothing, so "N remain" would describe a write that did not happen.
165
+ *
166
+ * @param {object} summary
167
+ * @returns {0|3|4}
168
+ */
169
+ export function exitCodeFor(summary) {
170
+ if (summary.ok === false) return 3;
171
+ if (summary.unrepairable > 0) return 4;
172
+ return 0;
173
+ }
174
+
142
175
  async function main() {
143
176
  let parsed;
144
177
  try {
@@ -197,7 +230,7 @@ async function main() {
197
230
  } else {
198
231
  process.stdout.write(renderHuman(summary));
199
232
  }
200
- process.exit(summary.ok === false ? 3 : 0);
233
+ process.exit(exitCodeFor(summary));
201
234
  }
202
235
 
203
236
  const isDirectRun =isMainModule(import.meta.url);
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ /** Stdin-only boundary for commands/session.md; never executes task context. */
3
+ import { readFileSync } from 'node:fs';
4
+ import { parseArgs } from 'node:util';
5
+ import { resolveSessionInvocation } from './lib/session-invocation.mjs';
6
+
7
+ const HELP = `Usage: node scripts/resolve-session-invocation.mjs [--json] < arguments.txt
8
+
9
+ Read the complete /session argument text from UTF-8 stdin.
10
+ Resolve the leading housekeeping, feature, deep or ultradeep token; preserve
11
+ the rest as task context. Empty input defaults to deep. An invalid mode emits
12
+ a warning and resolves to deep, as the session command has always specified.
13
+
14
+ --json Emit one JSON object instead of a human-readable summary
15
+ --help Show this help
16
+ --version Show the plugin version
17
+
18
+ Example: node scripts/resolve-session-invocation.mjs --json < arguments.txt
19
+ Write arguments.txt with a file tool; do not interpolate user text into a shell.
20
+ Exit codes: 0 resolved (including fallback), 1 bad CLI arguments, 2 stdin I/O error
21
+ `;
22
+
23
+ let options;
24
+ try {
25
+ options = parseArgs({
26
+ options: { json: { type: 'boolean' }, help: { type: 'boolean' }, version: { type: 'boolean' } },
27
+ allowPositionals: false,
28
+ }).values;
29
+ } catch (error) {
30
+ process.stderr.write(`session-invocation: ${error.message}\n`);
31
+ process.exit(1);
32
+ }
33
+
34
+ if (options.help) {
35
+ process.stdout.write(HELP);
36
+ } else if (options.version) {
37
+ process.stdout.write(
38
+ `${JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version}\n`,
39
+ );
40
+ } else {
41
+ let input;
42
+ try {
43
+ input = readFileSync(0, 'utf8');
44
+ } catch {
45
+ process.stderr.write('session-invocation: could not read UTF-8 stdin\n');
46
+ process.exit(2);
47
+ }
48
+ const resolved = resolveSessionInvocation(input);
49
+ if (resolved.invalidMode !== undefined) {
50
+ process.stderr.write(
51
+ `Invalid session type '${resolved.invalidMode}'. Valid types: housekeeping, feature, deep (alias: ultradeep). Falling back to deep.\n`,
52
+ );
53
+ }
54
+ process.stdout.write(
55
+ options.json
56
+ ? `${JSON.stringify(resolved)}\n`
57
+ : `Session type: ${resolved.sessionType}${resolved.profile ? ` (profile: ${resolved.profile})` : ''}\nTask context: ${resolved.context}\n`,
58
+ );
59
+ }
@@ -3,7 +3,12 @@
3
3
  * run-migrate-v2-cross-repo.mjs — cross-repo Migrate-CLI v2 runner.
4
4
  *
5
5
  * Walks a list of repos, applies the v2 migration to each repo's learnings.jsonl,
6
- * and reports pre/post invalid-rate per repo.
6
+ * and reports pre/post invalid-rate per repo — plus, per repo and in the
7
+ * aggregate, the count of invalid records per validation error class before and
8
+ * after migration (`errorClassesPre` / `errorClassesPost`, GitHub #69 / GitLab
9
+ * #1446). An error class is the validateLearning message up to `, got:`
10
+ * (e.g. `scope must be one of local|private|public`), so a dry run shows which
11
+ * failure kinds the migration fixes and which remain.
7
12
  *
8
13
  * Usage:
9
14
  * node scripts/run-migrate-v2-cross-repo.mjs [--repos <comma-list>] [--apply] [--json] [--out <path>]
@@ -11,15 +16,20 @@
11
16
  * Flags:
12
17
  * --repos <comma-list> Comma-separated repo paths (absolute or ~-prefixed).
13
18
  * When omitted, uses the hardcoded ROLLOUT_REPOS list.
14
- * --apply Write migrated records back to each file (atomic).
15
- * DEFAULT is dry-run (no writes).
19
+ * --apply Write migrated records back to each file (atomic),
20
+ * under that store's lock (`withLearningsLock`, the
21
+ * `<store>.lock` every learnings writer takes), so a
22
+ * live session appending in that repo is never lost.
23
+ * DEFAULT is dry-run (no writes, no lock).
16
24
  * --json Output machine-readable JSON instead of Markdown table.
17
25
  * --out <path> Write output to file instead of stdout.
18
26
  *
19
27
  * Exit codes:
20
28
  * 0 Success (including repos with no learnings.jsonl — gracefully skipped)
21
29
  * 1 Input/argument error
22
- * 2 I/O error
30
+ * 2 I/O error — incl. a store lock not acquired under --apply: that repo
31
+ * is left unwritten and reported `status: 'error'`, the run continues
32
+ * with the next repo, and exits 2 at the end
23
33
  */
24
34
 
25
35
  import { existsSync, readFileSync, writeFileSync, copyFileSync, renameSync } from 'node:fs';
@@ -30,6 +40,7 @@ import {
30
40
  migrateLegacyLearning,
31
41
  validateLearning,
32
42
  } from './lib/learnings.mjs';
43
+ import { LearningsLockError, withLearningsLock } from './lib/learnings/io.mjs';
33
44
  import { getCrossRepoProjects, getConfinementRoot } from './lib/config/cross-repo.mjs';
34
45
  import { validatePathInsideProject } from './lib/path-utils.mjs';
35
46
 
@@ -135,47 +146,102 @@ if (reposArg) {
135
146
  * @property {number} invalidPost - invalid records after migration
136
147
  * @property {number} fixedByV2 - records that became valid after migration
137
148
  * @property {number} malformed - JSON-parse failures (preserved, not counted in invalid)
149
+ * @property {Record<string, number>} errorClassesPre - invalid-pre count per error class
150
+ * @property {Record<string, number>} errorClassesPost - invalid-post count per error class
138
151
  * @property {string|null} error - error message if status=error
139
152
  */
140
153
 
154
+ /**
155
+ * Reduce a validateLearning error message to its class: the text before the
156
+ * record-specific `, got: <value>` tail. Messages without that tail (e.g.
157
+ * `learning missing required field: subject`) are their own class.
158
+ *
159
+ * @param {unknown} err
160
+ * @returns {string}
161
+ */
162
+ function errorClassOf(err) {
163
+ const message = err instanceof Error ? err.message : String(err);
164
+ const idx = message.indexOf(', got:');
165
+ return idx === -1 ? message : message.slice(0, idx);
166
+ }
167
+
168
+ /**
169
+ * @param {Record<string, number>} counts
170
+ * @param {string} cls
171
+ */
172
+ function bump(counts, cls) {
173
+ counts[cls] = (counts[cls] ?? 0) + 1;
174
+ }
175
+
176
+ /**
177
+ * A RepoResult that counted nothing.
178
+ *
179
+ * @param {string} repoPath
180
+ * @param {string} status
181
+ * @param {string|null} error
182
+ * @returns {RepoResult}
183
+ */
184
+ function emptyResult(repoPath, status, error) {
185
+ return {
186
+ repo: repoPath,
187
+ status,
188
+ total: 0,
189
+ invalidPre: 0,
190
+ invalidPost: 0,
191
+ fixedByV2: 0,
192
+ malformed: 0,
193
+ errorClassesPre: {},
194
+ errorClassesPost: {},
195
+ error,
196
+ };
197
+ }
198
+
199
+ /** Repos whose store lock was not acquired under --apply (nothing written there). */
200
+ const lockFailedRepos = [];
201
+
141
202
  /**
142
203
  * Process a single repo. Returns a RepoResult.
143
204
  *
205
+ * Under --apply the WHOLE read → backup → rename runs inside the store lock:
206
+ * these are other repos, where a live session may append between this read
207
+ * and the rename — outside the lock that record would be silently replaced.
208
+ * A dry run writes nothing and takes no lock.
209
+ *
144
210
  * @param {string} repoPath - resolved absolute path to the repo
145
211
  * @param {boolean} apply - whether to write changes back
146
- * @returns {RepoResult}
212
+ * @returns {Promise<RepoResult>}
147
213
  */
148
- function processRepo(repoPath, apply) {
214
+ async function processRepo(repoPath, apply) {
149
215
  const learningsPath = join(repoPath, LEARNINGS_REL);
150
216
 
151
- if (!existsSync(learningsPath)) {
152
- return {
153
- repo: repoPath,
154
- status: 'skipped',
155
- total: 0,
156
- invalidPre: 0,
157
- invalidPost: 0,
158
- fixedByV2: 0,
159
- malformed: 0,
160
- error: null,
161
- };
217
+ if (!existsSync(learningsPath)) return emptyResult(repoPath, 'skipped', null);
218
+ if (!apply) return migrateStore(repoPath, learningsPath, false);
219
+
220
+ try {
221
+ return await withLearningsLock(learningsPath, () => migrateStore(repoPath, learningsPath, true));
222
+ } catch (err) {
223
+ if (!(err instanceof LearningsLockError)) throw err;
224
+ lockFailedRepos.push(repoPath);
225
+ return emptyResult(repoPath, 'error', err.message);
162
226
  }
227
+ }
163
228
 
229
+ /**
230
+ * Read, migrate and (when `apply`) atomically rewrite one store. Under --apply
231
+ * the caller holds the store lock.
232
+ *
233
+ * @param {string} repoPath
234
+ * @param {string} learningsPath
235
+ * @param {boolean} apply
236
+ * @returns {RepoResult}
237
+ */
238
+ function migrateStore(repoPath, learningsPath, apply) {
164
239
  // Read
165
240
  let raw;
166
241
  try {
167
242
  raw = readFileSync(learningsPath, 'utf8');
168
243
  } catch (err) {
169
- return {
170
- repo: repoPath,
171
- status: 'error',
172
- total: 0,
173
- invalidPre: 0,
174
- invalidPost: 0,
175
- fixedByV2: 0,
176
- malformed: 0,
177
- error: `read failed: ${err.message}`,
178
- };
244
+ return emptyResult(repoPath, 'error', `read failed: ${err.message}`);
179
245
  }
180
246
 
181
247
  const lines = raw.split('\n').filter((l) => l.trim().length > 0);
@@ -184,6 +250,10 @@ function processRepo(repoPath, apply) {
184
250
  let invalidPre = 0;
185
251
  let invalidPost = 0;
186
252
  let fixedByV2 = 0;
253
+ /** @type {Record<string, number>} */
254
+ const errorClassesPre = {};
255
+ /** @type {Record<string, number>} */
256
+ const errorClassesPost = {};
187
257
  const outputLines = [];
188
258
 
189
259
  for (const line of lines) {
@@ -201,9 +271,10 @@ function processRepo(repoPath, apply) {
201
271
  let wasValidPre = true;
202
272
  try {
203
273
  validateLearning({ ...parsed, schema_version: parsed.schema_version ?? 1 });
204
- } catch {
274
+ } catch (err) {
205
275
  wasValidPre = false;
206
276
  invalidPre++;
277
+ bump(errorClassesPre, errorClassOf(err));
207
278
  }
208
279
 
209
280
  // Migrate
@@ -217,9 +288,10 @@ function processRepo(repoPath, apply) {
217
288
  ...migrated,
218
289
  schema_version: migrated.schema_version ?? 1,
219
290
  });
220
- } catch {
291
+ } catch (err) {
221
292
  isValidPost = false;
222
293
  invalidPost++;
294
+ bump(errorClassesPost, errorClassOf(err));
223
295
  }
224
296
 
225
297
  if (!wasValidPre && isValidPost) {
@@ -259,6 +331,8 @@ function processRepo(repoPath, apply) {
259
331
  invalidPost,
260
332
  fixedByV2,
261
333
  malformed,
334
+ errorClassesPre,
335
+ errorClassesPost,
262
336
  error: `write failed: ${err.message}`,
263
337
  };
264
338
  }
@@ -272,6 +346,8 @@ function processRepo(repoPath, apply) {
272
346
  invalidPost,
273
347
  fixedByV2,
274
348
  malformed,
349
+ errorClassesPre,
350
+ errorClassesPost,
275
351
  error: null,
276
352
  };
277
353
  }
@@ -280,7 +356,10 @@ function processRepo(repoPath, apply) {
280
356
  // Run across all repos
281
357
  // ---------------------------------------------------------------------------
282
358
 
283
- const results = repos.map((r) => processRepo(r, applyFlag));
359
+ // Sequential: one store lock at a time, and a lock failure in one repo never
360
+ // stops the next.
361
+ const results = [];
362
+ for (const r of repos) results.push(await processRepo(r, applyFlag));
284
363
 
285
364
  // ---------------------------------------------------------------------------
286
365
  // Aggregate
@@ -294,6 +373,12 @@ const aggregate = results.reduce(
294
373
  acc.totalMalformed += r.malformed;
295
374
  acc.totalReposProcessed += r.status !== 'skipped' ? 1 : 0;
296
375
  acc.totalReposSkipped += r.status === 'skipped' ? 1 : 0;
376
+ for (const [cls, n] of Object.entries(r.errorClassesPre)) {
377
+ acc.errorClassesPre[cls] = (acc.errorClassesPre[cls] ?? 0) + n;
378
+ }
379
+ for (const [cls, n] of Object.entries(r.errorClassesPost)) {
380
+ acc.errorClassesPost[cls] = (acc.errorClassesPost[cls] ?? 0) + n;
381
+ }
297
382
  return acc;
298
383
  },
299
384
  {
@@ -303,6 +388,10 @@ const aggregate = results.reduce(
303
388
  totalMalformed: 0,
304
389
  totalReposProcessed: 0,
305
390
  totalReposSkipped: 0,
391
+ /** @type {Record<string, number>} */
392
+ errorClassesPre: {},
393
+ /** @type {Record<string, number>} */
394
+ errorClassesPost: {},
306
395
  }
307
396
  );
308
397
 
@@ -319,6 +408,26 @@ function fmtPct(count, total) {
319
408
  return `${count} (${((count / total) * 100).toFixed(1)}%)`;
320
409
  }
321
410
 
411
+ /**
412
+ * Markdown table of invalid-record counts per error class, pre vs post
413
+ * migration. Classes are the union of both sides, sorted by pre count desc.
414
+ *
415
+ * @param {Record<string, number>} pre
416
+ * @param {Record<string, number>} post
417
+ * @returns {string[]}
418
+ */
419
+ function errorClassTable(pre, post) {
420
+ const classes = [...new Set([...Object.keys(pre), ...Object.keys(post)])].sort(
421
+ (a, b) => (pre[b] ?? 0) - (pre[a] ?? 0) || a.localeCompare(b)
422
+ );
423
+ if (classes.length === 0) return ['_No validation errors._'];
424
+ const rows = ['| Error class | Invalid pre | Invalid post |', '|-------------|-------------|--------------|'];
425
+ for (const cls of classes) {
426
+ rows.push(`| ${cls.replaceAll('|', '\\|')} | ${pre[cls] ?? 0} | ${post[cls] ?? 0} |`);
427
+ }
428
+ return rows;
429
+ }
430
+
322
431
  function buildMarkdown(results, aggregate, mode) {
323
432
  const lines = [];
324
433
  lines.push(`# Cross-Repo Migrate-CLI v2 — ${mode} report`);
@@ -345,6 +454,17 @@ function buildMarkdown(results, aggregate, mode) {
345
454
  lines.push(`- Repos processed: **${aggregate.totalReposProcessed}**`);
346
455
  lines.push(`- Repos skipped (no learnings.jsonl): **${aggregate.totalReposSkipped}**`);
347
456
  lines.push('');
457
+ lines.push('## Error classes (all repos)');
458
+ lines.push('');
459
+ lines.push(...errorClassTable(aggregate.errorClassesPre, aggregate.errorClassesPost));
460
+ lines.push('');
461
+ for (const r of results) {
462
+ if (r.status === 'skipped') continue;
463
+ lines.push(`### ${repoName(r.repo)}`);
464
+ lines.push('');
465
+ lines.push(...errorClassTable(r.errorClassesPre, r.errorClassesPost));
466
+ lines.push('');
467
+ }
348
468
  return lines.join('\n');
349
469
  }
350
470
 
@@ -382,4 +502,12 @@ process.stderr.write(
382
502
  `${aggregate.totalStillInvalidPost} still-invalid\n`
383
503
  );
384
504
 
505
+ if (lockFailedRepos.length > 0) {
506
+ process.stderr.write(
507
+ `run-migrate-v2: ERROR learnings store lock not acquired, nothing written in ${lockFailedRepos.length} repo(s): ` +
508
+ `${lockFailedRepos.join(', ')}\n`
509
+ );
510
+ process.exit(2);
511
+ }
512
+
385
513
  process.exit(0);