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
@@ -22,8 +22,11 @@
22
22
  * Here `src` is defined by NEGATION, so a new top-level directory joins the
23
23
  * denominator the moment it is committed, with no rule edit and no re-derivation.
24
24
  *
25
- * universe `git ls-files`, filtered to CODE_EXTENSIONS
26
- * numerator tracked code under `tests/`
25
+ * universe `git ls-files`, filtered to CODE_EXTENSIONS, minus declaration
26
+ * files (`*.d.ts`, `*.d.mts`, `*.d.cts` — types, not executable code)
27
+ * numerator tracked code under a top-level `tests/`, plus co-located tests
28
+ * anywhere: basename `*.test.<ext>` / `*.spec.<ext>`, or any file
29
+ * under a `__tests__/` directory
27
30
  * denominator every OTHER tracked code file
28
31
  *
29
32
  * ## The four questions, answered in code rather than left open
@@ -37,8 +40,8 @@
37
40
  * Only git-tracked? YES — reproducible at a SHA, and it is
38
41
  * what keeps node_modules/, coverage/ and
39
42
  * untracked scratch out without an ignore list.
40
- * Do `tests/fixtures/` count as test LOC? YES when they are code (`.mjs`/`.js`/
41
- * `.cjs`). A code fixture is maintained
43
+ * Do `tests/fixtures/` count as test LOC? YES when they are code (any of
44
+ * CODE_EXTENSIONS). A code fixture is maintained
42
45
  * code that exists only to serve the suite —
43
46
  * that is test-corpus cost. Non-code
44
47
  * fixtures (`.json`, `.jsonl`, `.md`) are
@@ -62,7 +65,9 @@
62
65
  * <repo-root> defaults to process.cwd()
63
66
  * --json emit a single JSON object on stdout, nothing else
64
67
  * --check exit 1 when the ratio exceeds the ceiling (consolidation
65
- * wave required); exit 0 when inside the corridor
68
+ * wave required); exit 3 when the ratio is not measurable
69
+ * (`reason: 'no-tests-found'`, #1451); exit 0 when inside
70
+ * the corridor
66
71
  * --ceiling <n> override the TV-003 ceiling (default 1.60)
67
72
  * --stdin take newline-separated paths from stdin instead of
68
73
  * enumerating via `git ls-files` (test seam / staged-only mode)
@@ -71,6 +76,12 @@
71
76
  * 0 — measurement completed (and, under --check, ratio is within the corridor)
72
77
  * 1 — --check only: ratio exceeds the ceiling
73
78
  * 2 — tool error (missing/unreadable root, bad argv)
79
+ * 3 — --check only: ratio not measurable — 0 test files counted while test-like
80
+ * files ARE tracked (`reason: 'no-tests-found'`, e.g. a Python repo whose
81
+ * `tests/test_*.py` sit outside CODE_EXTENSIONS). Fail-closed since #1451:
82
+ * before, such a repo read `ratio 0, withinCorridor true` — "not found"
83
+ * reported as "in corridor". TypeScript tests count since the 2026-10-01
84
+ * owner decision on #1451; other stacks still get "not measurable".
74
85
  */
75
86
 
76
87
  import { readFileSync, existsSync, statSync } from 'node:fs';
@@ -90,23 +101,65 @@ import { isMainModule } from './is-main-module.mjs';
90
101
  * Applied symmetrically to numerator and denominator by construction — a file
91
102
  * type can never inflate one side while being invisible to the other.
92
103
  */
93
- export const CODE_EXTENSIONS = Object.freeze(['.mjs', '.js', '.cjs']);
104
+ export const CODE_EXTENSIONS = Object.freeze([
105
+ '.mjs',
106
+ '.js',
107
+ '.cjs',
108
+ '.jsx',
109
+ '.ts',
110
+ '.tsx',
111
+ '.mts',
112
+ '.cts',
113
+ ]);
94
114
 
95
- /** Path prefix that makes a tracked code file part of the TEST corpus. */
115
+ /**
116
+ * TypeScript declaration files end in a CODE_EXTENSION but carry only types —
117
+ * no executable line a test could exercise. Excluded from BOTH sides, so a
118
+ * generated `.d.ts` can never move the ratio.
119
+ */
120
+ export const DECLARATION_SUFFIXES = Object.freeze(['.d.ts', '.d.mts', '.d.cts']);
121
+
122
+ /** Top-level path prefix that makes a tracked code file part of the TEST corpus. */
96
123
  export const TEST_PREFIX = 'tests/';
97
124
 
125
+ /**
126
+ * Co-located test files, wherever they sit: a `*.test.<ext>` / `*.spec.<ext>`
127
+ * basename (the vitest/jest default include shape) or a `__tests__/` segment.
128
+ */
129
+ const COLOCATED_TEST_RE = /\.(test|spec)\.[^./]+$|(^|\/)__tests__\//;
130
+
131
+ /**
132
+ * A tracked path that LOOKS like a test, counted or not: a `*.test.*` / `*.spec.*`
133
+ * JS/TS file, anything under a `__tests__/` segment, or any file under `tests/`
134
+ * regardless of extension. Used only to tell "this repo has no tests" apart from
135
+ * "this repo has tests the recipe cannot see" (#1451) — e.g. `tests/test_x.py`.
136
+ */
137
+ const TEST_LIKE_RE = /\.(test|spec)\.[cm]?[jt]sx?$/;
138
+
139
+ /**
140
+ * @param {string} relPath repo-relative path
141
+ * @returns {boolean}
142
+ */
143
+ export function isTestLikePath(relPath) {
144
+ const p = String(relPath).replace(/\\/g, '/').replace(/^\.\//, '');
145
+ return TEST_LIKE_RE.test(p) || p.startsWith(TEST_PREFIX) || /(^|\/)__tests__\//.test(p);
146
+ }
147
+
98
148
  /** The TV-003 ceiling. Exceeding it switches the consolidation rule on. */
99
149
  export const DEFAULT_CEILING = 1.6;
100
150
 
101
151
  /** Machine-readable schema tag for the --json envelope. */
102
- export const SCHEMA = 'tests-src-ratio/1';
152
+ export const SCHEMA = 'tests-src-ratio/2';
103
153
 
104
154
  /**
105
155
  * Which bucket a repo-relative path belongs to.
106
156
  *
107
- * `src` is defined by NEGATION — every tracked code file that is not under
108
- * `tests/`. That is what makes the two buckets a partition rather than two
109
- * globs that can overlap or leave a gap.
157
+ * `src` is defined by NEGATION — every tracked code file that is not a test
158
+ * (under `tests/`, or co-located per COLOCATED_TEST_RE). That is what makes the
159
+ * two buckets a partition rather than two globs that can overlap or leave a gap.
160
+ *
161
+ * Known blind spot: a nested `pkg/tests/helper.ts` that is not itself named
162
+ * `*.test.*` counts as src — only the TOP-LEVEL `tests/` is a test directory.
110
163
  *
111
164
  * @param {string} relPath repo-relative path, `/`-separated
112
165
  * @returns {'test'|'src'|null} null = outside the metric entirely
@@ -114,7 +167,8 @@ export const SCHEMA = 'tests-src-ratio/1';
114
167
  export function classifyPath(relPath) {
115
168
  const p = String(relPath).replace(/\\/g, '/').replace(/^\.\//, '');
116
169
  if (!CODE_EXTENSIONS.some((ext) => p.endsWith(ext))) return null;
117
- return p === TEST_PREFIX.slice(0, -1) || p.startsWith(TEST_PREFIX) ? 'test' : 'src';
170
+ if (DECLARATION_SUFFIXES.some((ext) => p.endsWith(ext))) return null;
171
+ return p.startsWith(TEST_PREFIX) || COLOCATED_TEST_RE.test(p) ? 'test' : 'src';
118
172
  }
119
173
 
120
174
  /**
@@ -154,7 +208,8 @@ export function countPhysicalLines(content) {
154
208
  * @param {(relPath: string) => string|null} opts.readFile returns content, or null when unreadable
155
209
  * @param {number} [opts.ceiling]
156
210
  * @returns {{testFiles:number,testLoc:number,srcFiles:number,srcLoc:number,ratio:number|null,
157
- * ceiling:number,withinCorridor:boolean,consolidationWaveRequired:boolean,skipped:number}}
211
+ * ceiling:number,withinCorridor:boolean|null,consolidationWaveRequired:boolean|null,
212
+ * skipped:number,testLikeFilesSeen:number,reason:'no-tests-found'|null}}
158
213
  */
159
214
  export function measure({ files, readFile, ceiling = DEFAULT_CEILING }) {
160
215
  let testFiles = 0;
@@ -162,8 +217,10 @@ export function measure({ files, readFile, ceiling = DEFAULT_CEILING }) {
162
217
  let srcFiles = 0;
163
218
  let srcLoc = 0;
164
219
  let skipped = 0;
220
+ let testLikeFilesSeen = 0;
165
221
 
166
222
  for (const rel of files) {
223
+ if (isTestLikePath(rel)) testLikeFilesSeen++;
167
224
  const bucket = classifyPath(rel);
168
225
  if (bucket === null) continue;
169
226
  const content = readFile(rel);
@@ -183,6 +240,26 @@ export function measure({ files, readFile, ceiling = DEFAULT_CEILING }) {
183
240
 
184
241
  // A repo with no src code has an undefined ratio, not an infinite one. Saying
185
242
  // `null` keeps the consumer from reading Infinity as a corridor breach.
243
+ // Fail closed (#1451): a zero numerator next to tracked test-like files means
244
+ // the recipe cannot SEE this repo's tests (e.g. `tests/test_x.py`), not that
245
+ // there are none. Reporting ratio 0 / withinCorridor true there turned "not found"
246
+ // into "green". The verdict is unknown, so every verdict field is null.
247
+ if (testFiles === 0 && testLikeFilesSeen > 0) {
248
+ return {
249
+ testFiles,
250
+ testLoc,
251
+ srcFiles,
252
+ srcLoc,
253
+ ratio: null,
254
+ ceiling,
255
+ withinCorridor: null,
256
+ consolidationWaveRequired: null,
257
+ skipped,
258
+ testLikeFilesSeen,
259
+ reason: 'no-tests-found',
260
+ };
261
+ }
262
+
186
263
  const ratio = srcLoc === 0 ? null : Number((testLoc / srcLoc).toFixed(4));
187
264
  const withinCorridor = ratio === null ? true : ratio <= ceiling;
188
265
 
@@ -196,6 +273,8 @@ export function measure({ files, readFile, ceiling = DEFAULT_CEILING }) {
196
273
  withinCorridor,
197
274
  consolidationWaveRequired: !withinCorridor,
198
275
  skipped,
276
+ testLikeFilesSeen,
277
+ reason: null,
199
278
  };
200
279
  }
201
280
 
@@ -208,11 +287,16 @@ export function definitionOf(source) {
208
287
  return {
209
288
  source: source === 'stdin' ? 'stdin path list' : 'git ls-files (tracked files only)',
210
289
  codeExtensions: [...CODE_EXTENSIONS],
211
- numerator: `tracked code files under ${TEST_PREFIX}`,
290
+ declarationsExcluded: [...DECLARATION_SUFFIXES],
291
+ numerator:
292
+ `tracked code files under a top-level ${TEST_PREFIX}, plus co-located tests anywhere: ` +
293
+ 'basename *.test.<ext> / *.spec.<ext>, or any file under a __tests__/ directory',
212
294
  denominator: `every OTHER tracked code file (src defined by negation, not by a directory list)`,
213
295
  lineRule: 'physical lines; blank + comment lines counted; EOF-newline-insensitive',
214
296
  excluded:
215
- 'non-code extensions (.md, .json, .jsonl, .yml) on BOTH sides; untracked files (node_modules/, coverage/, scratch)',
297
+ `declaration files (${DECLARATION_SUFFIXES.map((x) => `*${x}`).join(', ')}: types, not ` +
298
+ 'executable code) and non-code extensions (.md, .json, .jsonl, .yml) on BOTH sides; ' +
299
+ 'untracked files (node_modules/, coverage/, scratch)',
216
300
  };
217
301
  }
218
302
 
@@ -322,7 +406,11 @@ const USAGE =
322
406
  * because a measurement problem must not block a session start.
323
407
  *
324
408
  * @param {{ repoRoot?: string, ceiling?: number }} [opts]
325
- * @returns {{ severity: 'warn', message: string, ratio: number, ceiling: number } | null}
409
+ * The one non-null path besides a breach is `reason: 'no-tests-found'` (#1451):
410
+ * a repo whose tests the recipe cannot see gets a WARN, never silence — silence
411
+ * is what made the unmeasurable ratio read as green fleet-wide.
412
+ *
413
+ * @returns {{ severity: 'warn', message: string, ratio: number|null, ceiling: number, reason?: string } | null}
326
414
  */
327
415
  export function checkTestsSrcRatio({ repoRoot, ceiling = DEFAULT_CEILING } = {}) {
328
416
  try {
@@ -342,6 +430,18 @@ export function checkTestsSrcRatio({ repoRoot, ceiling = DEFAULT_CEILING } = {})
342
430
  };
343
431
 
344
432
  const result = measure({ files, readFile, ceiling });
433
+ if (result.reason === 'no-tests-found') {
434
+ return {
435
+ severity: 'warn',
436
+ ratio: null,
437
+ ceiling: result.ceiling,
438
+ reason: result.reason,
439
+ message:
440
+ `⚠ tests:src ratio nicht messbar — 0 Testdateien gezählt, ` +
441
+ `${result.testLikeFilesSeen} test-artige Dateien getrackt (#1451). ` +
442
+ `Detail: node scripts/lib/tests-src-ratio.mjs --json`,
443
+ };
444
+ }
345
445
  if (result.ratio === null || result.withinCorridor) return null;
346
446
 
347
447
  const dirty = isDirty(root);
@@ -402,17 +502,20 @@ function main() {
402
502
  console.log(USAGE);
403
503
  console.log('');
404
504
  console.log('The canonical tests:src LOC measurement for TV-003 (test-value.md).');
405
- console.log(' numerator tracked code under tests/');
505
+ console.log(' numerator tracked code under tests/, plus *.test.* / *.spec.* / __tests__/ anywhere');
406
506
  console.log(' denominator every other tracked code file (src by negation)');
407
507
  console.log(` code exts ${CODE_EXTENSIONS.join(' ')} (.md / .json never counted)`);
508
+ console.log(` excluded declaration files ${DECLARATION_SUFFIXES.join(' ')} on both sides`);
408
509
  console.log(' lines physical; blanks + comments counted; EOF-newline-insensitive');
409
510
  console.log('');
410
511
  console.log(' --json machine-readable envelope on stdout');
411
- console.log(' --check exit 1 when the ratio exceeds the ceiling');
512
+ console.log(' --check exit 1 when the ratio exceeds the ceiling, 3 when not measurable');
412
513
  console.log(` --ceiling <n> override the TV-003 ceiling (default ${DEFAULT_CEILING})`);
413
514
  console.log(' --stdin read newline-separated paths instead of git ls-files');
414
515
  console.log('');
415
- console.log('Exit: 0 ok / within corridor · 1 (--check) ceiling exceeded · 2 tool error');
516
+ console.log(
517
+ 'Exit: 0 ok / within corridor · 1 (--check) ceiling exceeded · 2 tool error · 3 (--check) not measurable',
518
+ );
416
519
  process.exit(0);
417
520
  }
418
521
 
@@ -460,10 +563,17 @@ function main() {
460
563
  // fail-open class is not worth re-litigating per payload size.
461
564
  writeStdoutLineSync(JSON.stringify(envelope, null, 2));
462
565
  } else {
463
- const r = result.ratio === null ? 'n/a (no src code)' : result.ratio.toFixed(4);
464
- const verdict = result.withinCorridor
465
- ? 'within corridor'
466
- : `ABOVE ceiling ${result.ceiling} — TV-003 consolidation wave required`;
566
+ const notMeasurable = result.reason === 'no-tests-found';
567
+ const r = notMeasurable
568
+ ? 'n/a (no tests found)'
569
+ : result.ratio === null
570
+ ? 'n/a (no src code)'
571
+ : result.ratio.toFixed(4);
572
+ const verdict = notMeasurable
573
+ ? `NOT MEASURABLE — ${result.testLikeFilesSeen} test-like files tracked but 0 counted (#1451)`
574
+ : result.withinCorridor
575
+ ? 'within corridor'
576
+ : `ABOVE ceiling ${result.ceiling} — TV-003 consolidation wave required`;
467
577
  const at = envelope.ref ? ` @ ${envelope.ref}${dirty ? '+dirty' : ''}` : '';
468
578
  writeStdoutLineSync(
469
579
  `tests:src = ${r} (${result.testLoc} test LOC / ${result.srcLoc} src LOC` +
@@ -472,6 +582,13 @@ function main() {
472
582
  );
473
583
  }
474
584
 
585
+ if (args.check && result.reason === 'no-tests-found') {
586
+ console.error(
587
+ `tests:src ratio not measurable — 0 test files counted but ${result.testLikeFilesSeen} ` +
588
+ `test-like files tracked (no-tests-found); see #1451`,
589
+ );
590
+ process.exit(3);
591
+ }
475
592
  process.exit(args.check && !result.withinCorridor ? 1 : 0);
476
593
  }
477
594
 
@@ -25,6 +25,7 @@
25
25
 
26
26
  import path from 'node:path';
27
27
  import { resolveStateDir } from '../platform.mjs';
28
+ import { shellQuote } from '../sh-quote.mjs';
28
29
  import { detectVcsCommand } from './vcs-detector.mjs';
29
30
  import { isSessionCollision } from './tmux-shell.mjs';
30
31
 
@@ -48,17 +49,6 @@ try {
48
49
  // Internal helpers
49
50
  // ---------------------------------------------------------------------------
50
51
 
51
- /**
52
- * Single-quote a shell command string for use as a tmux send-keys argument.
53
- * Escapes embedded single-quotes using the POSIX `'...' '\'' '...'` pattern.
54
- *
55
- * @param {string} s
56
- * @returns {string} e.g. 'tail -F /path/to/STATE.md'
57
- */
58
- function shellQuote(s) {
59
- return "'" + String(s).replace(/'/g, "'\\''") + "'";
60
- }
61
-
62
52
  /** Poll interval (seconds) of the agent-status pane loop. */
63
53
  const STATUS_PANE_POLL_SECONDS = 2;
64
54
 
@@ -2,7 +2,8 @@
2
2
  * scripts/lib/tmux-layout/telemetry-stats.mjs
3
3
  *
4
4
  * Aggregator stub for /tmux-layout telemetry promotion gate (#563).
5
- * Reads .orchestrator/metrics/events.jsonl and computes:
5
+ * Reads the events ledger — active file AND every rotated archive (#1407) —
6
+ * and computes:
6
7
  * - invocation count (total + per layout)
7
8
  * - completion rate (completed / invoked) — promotion gate threshold 80%
8
9
  * - top-K degradation reasons
@@ -13,26 +14,68 @@
13
14
  * - CLI: `node scripts/lib/tmux-layout/telemetry-stats.mjs` (emits JSON to stdout)
14
15
  */
15
16
 
16
- import { readFileSync, existsSync } from 'node:fs';
17
+ import path from 'node:path';
18
+ import { readEventsWithRotations } from '../events.mjs';
17
19
  import { isMainModule } from '../is-main-module.mjs';
18
20
 
19
21
  const EVENTS_PATH = '.orchestrator/metrics/events.jsonl';
20
22
 
23
+ /** Every event this module reports on carries this prefix. */
24
+ const TMUX_EVENT_PREFIX = 'tmux-layout.';
25
+
26
+ /**
27
+ * Read the tmux-layout events ACROSS rotation boundaries, together with the
28
+ * honesty verdict of that read.
29
+ *
30
+ * WHY THE ENVELOPE (#1407 acceptance criterion 3): the stats below are an
31
+ * ALL-TIME rate. Reading only the active file silently redefines "all time" as
32
+ * "since the last rotation", and a genuinely missing archive would then look
33
+ * identical to a quiet week. `complete === false` says which of the two it is;
34
+ * the CLI prints it and never computes over a partial set in silence.
35
+ *
36
+ * THREE states (#1423): `complete === null` is a THIRD reading — no source
37
+ * existed at all, so `invocations: 0` is UNMEASURED, not a measured zero, and
38
+ * `meetsPromotionGate: false` below rests on nothing. The CLI names that case
39
+ * separately; folding it into `false` would report a gap nobody found, folding
40
+ * it into `true` would report a measurement nobody took.
41
+ *
42
+ * CEILING (BV-004): `readEventsWithRotations` loads the active file and every
43
+ * archive fully into memory — up to ~60 MB transient at the default
44
+ * `max-size-mb: 10` / `max-backups: 5`. Acceptable here because this is a COLD
45
+ * ops path: a hand-run CLI / promotion-gate check, never a hook and never on
46
+ * the session path. Revisit if this module gains a hot-path caller or if
47
+ * `max-size-mb` is raised past ~100.
48
+ *
49
+ * @param {string} [eventsPath=.orchestrator/metrics/events.jsonl]
50
+ * @returns {{events: Array<object>, complete: boolean|null, gaps: Array<object>,
51
+ * notices: Array<object>}}
52
+ */
53
+ export function readTmuxEventsEnvelope(eventsPath = EVENTS_PATH) {
54
+ const { events, gaps, complete, notices } = readEventsWithRotations(undefined, {
55
+ filePath: eventsPath,
56
+ });
57
+ return {
58
+ events: events.filter(
59
+ (rec) => rec && typeof rec.event === 'string' && rec.event.startsWith(TMUX_EVENT_PREFIX),
60
+ ),
61
+ complete,
62
+ gaps,
63
+ notices: notices ?? [],
64
+ };
65
+ }
66
+
21
67
  /**
22
- * Read events.jsonl and return tmux-layout-related events.
68
+ * Read the events ledger and return tmux-layout-related events.
69
+ *
70
+ * Thin array-returning view of {@link readTmuxEventsEnvelope} — the shape every
71
+ * existing caller expects. Use the envelope when the completeness of the read
72
+ * matters to the answer.
23
73
  *
24
74
  * @param {string} [eventsPath=.orchestrator/metrics/events.jsonl]
25
75
  * @returns {Array<object>} parsed event records (filtered to tmux-layout.* events)
26
76
  */
27
77
  export function readTmuxEvents(eventsPath = EVENTS_PATH) {
28
- if (!existsSync(eventsPath)) return [];
29
- const raw = readFileSync(eventsPath, 'utf-8');
30
- const lines = raw.split('\n').filter((l) => l.trim().length > 0);
31
- return lines
32
- .map((l) => {
33
- try { return JSON.parse(l); } catch { return null; }
34
- })
35
- .filter((rec) => rec && typeof rec.event === 'string' && rec.event.startsWith('tmux-layout.'));
78
+ return readTmuxEventsEnvelope(eventsPath).events;
36
79
  }
37
80
 
38
81
  /**
@@ -95,7 +138,23 @@ export function computeStats(events) {
95
138
 
96
139
  // CLI entry-point — emit stats as JSON to stdout when run directly
97
140
  if (isMainModule(import.meta.url)) {
98
- const events = readTmuxEvents();
141
+ const { events, complete, gaps } = readTmuxEventsEnvelope();
99
142
  const stats = computeStats(events);
100
- console.log(JSON.stringify(stats, null, 2));
143
+ if (complete === null) {
144
+ // NOT MEASURED — a different sentence from "incomplete" (#1423): no active
145
+ // file and no archive means the zeros below were never observed.
146
+ console.error(
147
+ 'WARN: no events source was readable (no active file, no archive) — the stats below are UNMEASURED, not zero',
148
+ );
149
+ } else if (!complete) {
150
+ // A gap, never an empty window: diagnostics on stderr (cli-design.md),
151
+ // the machine-readable verdict in the JSON below.
152
+ const detail = gaps
153
+ .map((g) => `${g.kind}:${path.basename(String(g.archived_as ?? 'unknown'))}`)
154
+ .join(', ');
155
+ console.error(
156
+ `WARN: events ledger incomplete — the all-time rate below is computed over a PARTIAL set (${gaps.length} gap(s): ${detail})`,
157
+ );
158
+ }
159
+ console.log(JSON.stringify({ ...stats, ledgerComplete: complete, ledgerGaps: gaps }, null, 2));
101
160
  }
@@ -6,6 +6,8 @@
6
6
 
7
7
  import { execSync } from 'node:child_process';
8
8
 
9
+ import { shellQuote } from '../sh-quote.mjs';
10
+
9
11
  const MIN_MAJOR = 3;
10
12
  const MIN_MINOR = 0;
11
13
 
@@ -69,14 +71,3 @@ export function isSessionCollision(sessionName) {
69
71
  return { exists: false, error: `tmux has-session failed: ${err.message}` };
70
72
  }
71
73
  }
72
-
73
- /**
74
- * Shell-quote a string for safe interpolation into a shell command.
75
- * Uses single-quote wrapping with embedded single-quote escaping.
76
- *
77
- * @param {string} s
78
- * @returns {string}
79
- */
80
- function shellQuote(s) {
81
- return "'" + String(s).replace(/'/g, "'\\''") + "'";
82
- }
@@ -102,11 +102,31 @@ function normaliseScalar(text) {
102
102
  * than inventing a fifth dialect. Because the demotion is the surprising half,
103
103
  * each one emits exactly one stderr WARN naming the file.
104
104
  *
105
+ * The same normalisation governs the SIBLING boolean marker
106
+ * `disable-model-invocation` (the Cursor and Codex adapters read it with this
107
+ * predicate). The WARN therefore names the key it was asked about rather than a
108
+ * hard-coded `user-invocable`: measured 2026-09-18, a `disable-model-invocation:
109
+ * yes` demotion reported `user-invocable: "yes"`, sending the operator to a
110
+ * line that does not exist in that file.
111
+ *
112
+ * The NOUN follows the key too (#1388 P10). Naming the key while hard-coding
113
+ * "is NOT a slash-command marker" told a `disable-model-invocation: yes` author
114
+ * that his value failed to be something it never was — a correct key with a
115
+ * wrong subject reads as a broken diagnostic. The default phrase is unchanged
116
+ * for `user-invocable`, so the four existing call sites see identical text.
117
+ *
105
118
  * @param {unknown} value the raw frontmatter value
106
119
  * @param {string} [file] path named in the WARN when a truthy-looking value is demoted
120
+ * @param {string} [key] frontmatter key named in the WARN (default `user-invocable`)
107
121
  * @returns {boolean}
108
122
  */
109
- export function isUserInvocableValue(value, file) {
123
+ const MARKER_NOUN = Object.freeze({
124
+ 'user-invocable': 'a slash-command marker',
125
+ 'disable-model-invocation': 'a model-invocation opt-out marker',
126
+ });
127
+ const DEFAULT_MARKER_NOUN = 'a boolean marker';
128
+
129
+ export function isUserInvocableValue(value, file, key = 'user-invocable') {
110
130
  if (value === true) return true;
111
131
  if (typeof value !== 'string') return false;
112
132
  const token = normaliseScalar(value);
@@ -114,8 +134,8 @@ export function isUserInvocableValue(value, file) {
114
134
  if (TRUTHY_LOOKALIKE.test(token)) {
115
135
  const where = file ? `${file}: ` : '';
116
136
  process.stderr.write(
117
- `WARN ${where}user-invocable: ${JSON.stringify(value)} is NOT a slash-command marker — `
118
- + 'only `true` is (YAML 1.2 reads yes/on/1 as strings). Write `user-invocable: true`.\n',
137
+ `WARN ${where}${key}: ${JSON.stringify(value)} is NOT ${MARKER_NOUN[key] ?? DEFAULT_MARKER_NOUN} — `
138
+ + `only \`true\` is (YAML 1.2 reads yes/on/1 as strings). Write \`${key}: true\`.\n`,
119
139
  );
120
140
  }
121
141
  return false;
@@ -37,12 +37,16 @@
37
37
  * mistaken import away from filing issues nobody asked for.
38
38
  *
39
39
  * Exports:
40
- * UX_GRILL_LABELS, buildIssueTitle, buildIssueBody, reconcileFindings
40
+ * UX_GRILL_LABELS, buildIssueTitle, buildUxGrillIssueBody, reconcileFindings
41
+ * buildIssueBody — @deprecated alias of buildUxGrillIssueBody (5.2.0 name),
42
+ * removed in 6.0.0
41
43
  */
42
44
 
43
45
  import {
44
46
  createFinding as defaultCreateFinding,
47
+ fingerprintSentinelLine,
45
48
  listExistingFindings as defaultListExisting,
49
+ neutralizeFingerprintSentinel,
46
50
  triageDecision,
47
51
  updateFinding as defaultUpdateFinding,
48
52
  } from '../test-runner/issue-reconcile.mjs';
@@ -69,31 +73,22 @@ const TITLE_MAX_LENGTH = 100;
69
73
  const AUTO_SEVERITIES = Object.freeze(['critical', 'high']);
70
74
 
71
75
  /**
72
- * Neutralise a fingerprint-sentinel literal in free text (#388 SEC-IR-MED-1).
76
+ * Neutralise fingerprint-sentinel literals (#388 SEC-IR-MED-1) and strip the
77
+ * characters `createFinding` rejects at the argv boundary, so a multi-line page
78
+ * title in a finding message cannot turn into a VALIDATION failure for the whole
79
+ * reconcile pass.
73
80
  *
74
- * `issue-reconcile.mjs` applies this to `recommendation` only, inside
75
- * `reconcileFinding` — a body built HERE and handed to `createFinding` is not
76
- * sanitised by anything, so a finding `message` echoing page text could forge
77
- * the authoritative `**Fingerprint:**` line and make the next run's
81
+ * The neutralisation is the SHARED one from `issue-reconcile.mjs` (#1339 P6): a
82
+ * body built HERE and handed to `createFinding` is not sanitised by anything
83
+ * else, so a finding `message` echoing page text could otherwise forge the
84
+ * authoritative `**Fingerprint:**` line and make the next run's
78
85
  * `triageDecision` dedup against the wrong issue.
79
86
  *
80
87
  * @param {unknown} text
81
88
  * @returns {string}
82
89
  */
83
- function sanitizeSentinel(text) {
84
- return String(text ?? '').replace(/\*\*Fingerprint:\*\*/gi, '__Fingerprint__');
85
- }
86
-
87
- /**
88
- * Strip the characters `createFinding` rejects at the argv boundary, so a
89
- * multi-line page title in a finding message cannot turn into a VALIDATION
90
- * failure for the whole reconcile pass.
91
- *
92
- * @param {unknown} text
93
- * @returns {string}
94
- */
95
90
  function oneLine(text) {
96
- return sanitizeSentinel(text).replace(/[\n\r\0]+/g, ' ').trim();
91
+ return neutralizeFingerprintSentinel(text).replace(/[\n\r\0]+/g, ' ').trim();
97
92
  }
98
93
 
99
94
  /**
@@ -144,7 +139,7 @@ export function buildIssueTitle(finding) {
144
139
  * @param {string} [context.rubricHash]
145
140
  * @returns {string}
146
141
  */
147
- export function buildIssueBody(finding, { runId, rubricHash } = {}) {
142
+ export function buildUxGrillIssueBody(finding, { runId, rubricHash } = {}) {
148
143
  const evidence = finding?.evidence ?? {};
149
144
  const evidenceLines = Object.entries(evidence)
150
145
  .filter(([, value]) => typeof value === 'string' || Array.isArray(value))
@@ -153,7 +148,7 @@ export function buildIssueBody(finding, { runId, rubricHash } = {}) {
153
148
  return [
154
149
  oneLine(finding?.message) || `${oneLine(finding?.checkId)} at ${oneLine(finding?.locator)}`,
155
150
  '',
156
- `**Fingerprint:** \`${oneLine(finding?.fingerprint)}\``,
151
+ fingerprintSentinelLine(oneLine(finding?.fingerprint)),
157
152
  `**Severity:** ${oneLine(finding?.severity)}`,
158
153
  `**Check:** ${oneLine(finding?.checkId)}`,
159
154
  `**Locator:** \`${oneLine(finding?.locator)}\``,
@@ -168,6 +163,37 @@ export function buildIssueBody(finding, { runId, rubricHash } = {}) {
168
163
  .join('\n');
169
164
  }
170
165
 
166
+ /** One stderr line per process for the deprecated alias below, not one per call. */
167
+ let buildIssueBodyDeprecationWarned = false;
168
+
169
+ /**
170
+ * Deprecated alias of {@link buildUxGrillIssueBody}, shipped under this name in
171
+ * 5.2.0.
172
+ *
173
+ * @deprecated since 5.3.0 — use buildUxGrillIssueBody; removed in 6.0.0.
174
+ * Kept one minor cycle (`.claude/rules/development.md` § Package Lifecycle &
175
+ * Versioning) because `package.json` carries no `exports` map: every packed
176
+ * `scripts/lib/` export is a public deep-import, and a consumer calling this
177
+ * name would otherwise get a runtime TypeError, not a compile error. It
178
+ * delegates to the LOCAL ux-grill body builder — never to the unrelated
179
+ * `buildIssueBody(finding, fp)` of `test-runner/issue-reconcile.mjs`, whose
180
+ * finding shape and second argument differ.
181
+ *
182
+ * @param {object} finding - a `makeFinding` record
183
+ * @param {object} [context] - same as {@link buildUxGrillIssueBody}
184
+ * @returns {string}
185
+ */
186
+ export function buildIssueBody(finding, context) {
187
+ if (!buildIssueBodyDeprecationWarned) {
188
+ buildIssueBodyDeprecationWarned = true;
189
+ console.warn(
190
+ '[deprecated] buildIssueBody from scripts/lib/ux-grill/reconcile.mjs is deprecated since the 5.3 line ' +
191
+ 'and will be removed in 6.0.0 — use buildUxGrillIssueBody instead.',
192
+ );
193
+ }
194
+ return buildUxGrillIssueBody(finding, context);
195
+ }
196
+
171
197
  /**
172
198
  * Collect every string in a finding's `evidence` that looks like an absolute
173
199
  * host path (POSIX `/…` or Windows `C:\…`).
@@ -296,7 +322,7 @@ export async function reconcileFindings({
296
322
  }
297
323
 
298
324
  const title = buildIssueTitle(finding);
299
- const body = buildIssueBody(finding, { runId, rubricHash });
325
+ const body = buildUxGrillIssueBody(finding, { runId, rubricHash });
300
326
  const decision = triageDecision({ fingerprint: finding?.fingerprint, title }, candidates);
301
327
 
302
328
  if (decision.action === 'ignore') {