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
@@ -51,15 +51,21 @@
51
51
  * skill body) and nowhere in the three RUN surfaces above still reports
52
52
  * UNREGISTERED — being documented is not being run. REVISIT if a fourth run
53
53
  * surface (a new CI job file, a different git hook) is ever added: extend
54
- * `RUN_SURFACES`, do not special-case it here. The comment stripper's own
55
- * quote-tracking is a single flat state — an escaped quote (`\"`) inside a
56
- * double-quoted string is not honoured, and a template-literal's `${...}`
57
- * interpolation is not walked separately. Both failure directions lean
58
- * toward treating MORE text as "inside a string" than a real parser would,
59
- * which can only make the stripper MISS a comment (false "still
60
- * registered"), never manufacture a false UNREGISTERED — the direction this
61
- * checker's own false-positive history (the paragraph above) already
62
- * measured as the live hazard.
54
+ * `RUN_SURFACES`, do not special-case it here.
55
+ *
56
+ * THE "NEVER A FALSE UNREGISTERED" CLAIM WAS FALSE (#1388, measured
57
+ * 2026-09-18 @ 20a4cbff). This header used to argue that the hand-rolled
58
+ * stripper could only ever MISS a comment, never manufacture a false
59
+ * UNREGISTERED. It had no regex-literal branch, so
60
+ * `const re = /\/*$/;` in `scripts/validate-plugin.mjs` opened a BLOCK
61
+ * COMMENT at the regex's `/*` and swallowed every `runCheck(...)` line below
62
+ * it — precisely a false UNREGISTERED, for every checker registered after
63
+ * that point. The `.mjs` surface is therefore lexed by the shared
64
+ * `maskSource` (`scripts/lib/js-mask.mjs`), which recognises regex literals;
65
+ * only the `#`-comment shell/YAML surfaces (`.husky/pre-commit`,
66
+ * `.gitlab-ci.yml`) still use the small local walker, whose flat
67
+ * quote-tracking ceiling (no `\"` escape handling, no `${...}` walk) is
68
+ * real but bounded to those two files.
63
69
  *
64
70
  * Usage: check-validator-registration.mjs <repo-root>
65
71
  * Output: ` PASS: …` / ` FAIL: …` lines (two leading spaces), then
@@ -74,6 +80,7 @@ import { existsSync, readFileSync } from 'node:fs';
74
80
  import path from 'node:path';
75
81
  import { enumerateRepoFiles } from './enumerate-repo-files.mjs';
76
82
  import { isMainModule } from '../is-main-module.mjs';
83
+ import { maskSource } from '../js-mask.mjs';
77
84
 
78
85
  /** Marker line inside a checker's own header — declares deliberate CLI-only status. */
79
86
  export const STANDALONE_MARKER = /^\s*\/\/\s*registration:\s*standalone\b(?:\s+(.*))?$/m;
@@ -113,11 +120,28 @@ function commentStyleForSurface(rel) {
113
120
  * truncating the line early. See the header NAMED CEILING for what this
114
121
  * quote-tracking deliberately does not attempt.
115
122
  *
123
+ * The JS style (`//` + `/* *\/`) is delegated to the shared lexer
124
+ * {@link maskSource} in `keepLiterals` mode — it is the only one of the three
125
+ * RUN_SURFACES that can contain a REGEX LITERAL, and a stripper without a
126
+ * regex branch reads `/\/*$/` as a block-comment opener and swallows every
127
+ * registration below it (#1388, see the header). `maskSource` blanks comment
128
+ * bytes to SPACES rather than deleting them, which is strictly safer for the
129
+ * substring match below: deletion could splice two fragments into a basename
130
+ * that was never written. The `#` style stays local — `maskSource` models
131
+ * JavaScript, not sh/YAML.
132
+ *
116
133
  * @param {string} text
117
134
  * @param {{lineComment: string, blockComment: boolean, quoteChars: string[]}} style
118
- * @returns {string}
135
+ * @returns {string} TWO comment models behind one signature, and they differ in
136
+ * shape: the JS style (`//` + `/* *\/`, delegated to {@link maskSource})
137
+ * returns a string of the SAME LENGTH as `text`, with comment bytes blanked to
138
+ * spaces; the `#` style (sh/YAML, the local walker) returns a SHORTER string,
139
+ * with the comment text deleted to end-of-line (the newline itself is kept).
140
+ * Only the substring match below is common to both — do not rely on offsets
141
+ * surviving the `#` style.
119
142
  */
120
143
  export function stripComments(text, { lineComment, blockComment, quoteChars }) {
144
+ if (lineComment === '//' && blockComment) return maskSource(text, { keepLiterals: true });
121
145
  let out = '';
122
146
  let i = 0;
123
147
  let inQuote = null;
@@ -177,6 +177,16 @@ export function inspectConfidentialNames({ namesPath, deps = {} } = {}) {
177
177
  // Unconfigured → no list, no noise. This is the normal case for public repos
178
178
  // and for any host that has not opted into confidential-name scanning.
179
179
  if (typeof namesPath !== 'string' || namesPath.trim() === '') {
180
+ // UNREACHABLE from the one production caller today (#1273 P1):
181
+ // `check-owner-leakage.mjs` resolves `namesPath` via `resolveHostPath()`
182
+ // and returns its own early `disabledReason` on an empty/non-string path
183
+ // BEFORE ever calling `inspectConfidentialNames` — so in production this
184
+ // branch never runs. It is reachable only for a direct caller (or a test)
185
+ // that skips that pre-filter. Kept regardless: `status` is part of the
186
+ // documented public contract every deep importer may switch on, and
187
+ // 'unconfigured' is the correct answer for exactly this input shape.
188
+ // REVISIT TRIGGER (BV-004): the production caller stops pre-filtering an
189
+ // empty/non-string `namesPath` before calling `inspectConfidentialNames`.
180
190
  return { status: 'unconfigured', names: [] };
181
191
  }
182
192
 
@@ -6,17 +6,21 @@
6
6
  * Catches vendoring bugs that are invisible at the source-of-truth layer but
7
7
  * become live footguns once synced into a target repo:
8
8
  *
9
- * - `paths:` frontmatter in a `rules/` LIBRARY SOURCE — a vendoring-
10
- * CONVENTION gate, not a loader-compatibility one. Since #795
11
- * `rule-loader.mjs` accepts `paths:` as an alias for `globs:`, so such a
12
- * rule IS glob-scoped; `globs:` stays the canonical form for a rule
13
- * vendored out through the library (#742). See § Scope below — this probe
14
- * never judges a repo's own consolidated `.claude/rules/` files.
9
+ * - `frontmatter-not-at-top` — a `---` block preceded by anything (the
10
+ * provenance comment, a blank line). Claude Code reads frontmatter only
11
+ * from line 1 and reads only its `paths:` key, so such a rule silently
12
+ * loads ALWAYS-ON in every consumer repo (#1449).
13
+ * - `globs-paths-mismatch` — a library rule's frontmatter must carry
14
+ * `globs:` AND `paths:` with the identical list (order-insensitive), or
15
+ * neither. `paths:` is what Claude Code reads; `globs:` stays because
16
+ * `rule-loader.mjs` prefers it (#742/#795). One key alone, or two
17
+ * different lists, scopes the rule differently per loader.
15
18
  * - Missing provenance header — `rules-sync.mjs` detects "plugin-owned vs.
16
- * local override" purely by checking whether the first line starts with
17
- * `PLUGIN_HEADER_PREFIX`. A source file missing that header gets
18
- * mis-detected as a local override on the NEXT re-sync (it looks
19
- * hand-authored), so the plugin can never update it again.
19
+ * local override" via `isPluginOwnedContent()`: the header line starting
20
+ * with `PLUGIN_HEADER_PREFIX` on line 1 (a rule without frontmatter) or as
21
+ * the first non-blank line after a line-1 frontmatter block. A source file
22
+ * missing it gets mis-detected as a local override on the NEXT re-sync (it
23
+ * looks hand-authored), so the plugin can never update it again.
20
24
  * - Unfilled placeholder tokens (`{{PROJECT_NAME}}`-style, `## TODO:
21
25
  * Customize` headings, `<!-- TODO:` comments) leaking from an
22
26
  * un-filled-in skeleton into a synced rule.
@@ -30,9 +34,10 @@
30
34
  * Stdlib-only ESM, with the same picomatch-with-fallback resolution pattern
31
35
  * as `rule-loader.mjs` (duplicated here rather than imported because those
32
36
  * helpers are module-private there — the *approach* is reused, not
33
- * reinvented). Frontmatter `globs:` extraction is NOT duplicated — this
34
- * module imports the already-exported `parseGlobsFrontmatter` from
35
- * `rule-loader.mjs` directly.
37
+ * reinvented). The globs-derived probes use the already-exported
38
+ * `parseGlobsFrontmatter` from `rule-loader.mjs` directly; only
39
+ * `globs-paths-mismatch` reads the two keys itself (`readScopeKeys`), because
40
+ * the loader merges them and cannot say which key carried which list.
36
41
  *
37
42
  * ## Scope: the `rules/` fleet library, never a repo's own `.claude/rules/`
38
43
  *
@@ -43,15 +48,14 @@
43
48
  * written target is never read back through the gate. The CLI's `--dir` is
44
49
  * operator-supplied and means that same library.
45
50
  *
46
- * This is load-bearing for `paths-frontmatter`: a repo's own CONSOLIDATED rules
47
- * under `.claude/rules/` are `paths:`-canonical by design, because Claude
48
- * Code's native loader reads ONLY `paths:` and treats a rule without it as
49
- * always-on (`validate/check-rules.mjs` #1108; `docs/rule-authoring.md`
50
- * § Consolidated rules point 3). Measured 2026-09-16 in this repo: 10
51
- * path-scoped rule files there, 9 of them `paths:`-only. Pointing `--dir` at
52
- * such a tree would emit findings that, if obeyed, UNDO that consolidation —
53
- * which is also why `check-rules.mjs` deliberately does not duplicate this
54
- * probe for `.claude/rules/`.
51
+ * This matters for `globs-paths-mismatch` and the provenance probe: they encode
52
+ * the LIBRARY convention (both scope keys, a provenance header). A repo's own
53
+ * CONSOLIDATED rules under `.claude/rules/` are `paths:`-only and header-free
54
+ * by design, because Claude Code's native loader reads ONLY `paths:`
55
+ * (`validate/check-rules.mjs` #1108; `docs/rule-authoring.md` § Consolidated
56
+ * rules point 3). Measured 2026-09-16 in this repo: 10 path-scoped rule files
57
+ * there, 9 of them `paths:`-only. Pointing `--dir` at such a tree reports
58
+ * findings about a convention those files were never meant to follow.
55
59
  *
56
60
  * @module validate-vendored-rules
57
61
  */
@@ -61,7 +65,7 @@ import { join, resolve, relative, basename } from 'node:path';
61
65
  import { fileURLToPath } from 'node:url';
62
66
  import { execFileSync } from 'node:child_process';
63
67
  import { createRequire } from 'node:module';
64
- import { parseGlobsFrontmatter } from './rule-loader.mjs';
68
+ import { parseGlobsFrontmatter, FRONTMATTER_NOT_AT_TOP } from './rule-loader.mjs';
65
69
  import { isMainModule } from './is-main-module.mjs';
66
70
 
67
71
  // Mirrors rules-sync.mjs's exported PLUGIN_HEADER_PREFIX (rules-sync.mjs
@@ -137,21 +141,26 @@ function matchGlob(filePath, globPattern) {
137
141
  // Frontmatter helpers
138
142
  // ---------------------------------------------------------------------------
139
143
 
144
+ // A line that may precede a MISPLACED frontmatter opener: blank, or one
145
+ // complete single-line HTML comment (the provenance header's shape).
140
146
  const HEADER_LINE_RE = /^[ \t]*(?:<!--.*-->)?[ \t]*$/;
141
147
 
142
148
  /**
143
- * Finds a YAML frontmatter block delimited by `---` lines.
149
+ * Locates a YAML frontmatter block delimited by `---` lines.
144
150
  *
145
- * Mirrors rule-loader.mjs's header-tolerant frontmatter shape for the `paths:`
146
- * authoring guard: a leading run of blank lines and/or single-line HTML
147
- * comments may precede the opening `---`.
151
+ * Deliberately tolerant of a leading run of blank lines and/or single-line
152
+ * HTML comments before the opener — NOT because such a block is valid (Claude
153
+ * Code reads frontmatter only when `---` is line 1, and `rule-loader.mjs`
154
+ * no longer tolerates the prefix either), but so `frontmatter-not-at-top` can
155
+ * SEE the misplaced block and report it. Callers that judge what a loader
156
+ * reads must check `startLine === 0` first.
148
157
  *
149
- * @param {string} content
150
- * @returns {{ body: string, startLine: number } | null} startLine is the
151
- * 0-based file-line index of the opening `---`.
158
+ * @param {string[]} lines - file content split on `\r?\n`
159
+ * @returns {{ bodyLines: string[], startLine: number, endLine: number } | null}
160
+ * startLine / endLine are the 0-based file-line indices of the opening and
161
+ * closing `---`.
152
162
  */
153
- function extractFrontmatter(content) {
154
- const lines = content.split(/\r?\n/);
163
+ function locateFrontmatter(lines) {
155
164
  let startLine = 0;
156
165
  while (startLine < lines.length && HEADER_LINE_RE.test(lines[startLine])) {
157
166
  startLine++;
@@ -159,22 +168,93 @@ function extractFrontmatter(content) {
159
168
  if (lines[startLine] !== '---') return null;
160
169
  for (let i = startLine + 1; i < lines.length; i++) {
161
170
  if (lines[i] === '---') {
162
- return { body: lines.slice(startLine + 1, i).join('\n'), startLine };
171
+ return { bodyLines: lines.slice(startLine + 1, i), startLine, endLine: i };
163
172
  }
164
173
  }
165
174
  return null;
166
175
  }
167
176
 
168
177
  /**
169
- * Line number (1-based, absolute within the file) of a match at `bodyIndex`
170
- * inside the frontmatter body.
171
- * @param {number} startLine - 0-based file-line index of the opening `---`
172
- * @param {string} body - frontmatter body text
173
- * @param {number} bodyIndex - character offset within body
174
- * @returns {number}
178
+ * True when `content` carries the plugin provenance header in one of the two
179
+ * positions the vendoring convention allows (#1449):
180
+ *
181
+ * (a) line 1 — a rule WITHOUT frontmatter, and also every vendored copy
182
+ * written before #1449 (header on line 1, frontmatter after it);
183
+ * (b) the first non-blank line after the closing `---` of a frontmatter
184
+ * block that opens on line 1 — the current format for a rule WITH
185
+ * frontmatter, because Claude Code reads `paths:` only from a line-1
186
+ * block.
187
+ *
188
+ * This is the ownership predicate `rules-sync.mjs` uses to tell a
189
+ * plugin-owned vendored copy (rewrite it) from a local rule (preserve it), and
190
+ * the `provenance-header` probe's acceptance test. Accepting (a) with a
191
+ * frontmatter after it keeps old-format consumer copies plugin-owned, so the
192
+ * next sync REWRITES them into the new format instead of preserving them
193
+ * forever. Lives here rather than in rules-sync.mjs because rules-sync.mjs
194
+ * imports from this module; it re-exports this function.
195
+ *
196
+ * @param {string} content - raw rule file content
197
+ * @returns {boolean}
175
198
  */
176
- function lineWithinFrontmatter(startLine, body, bodyIndex) {
177
- return startLine + 1 + body.slice(0, bodyIndex).split('\n').length;
199
+ export function isPluginOwnedContent(content) {
200
+ const lines = content.split(/\r?\n/);
201
+ if ((lines[0] ?? '').startsWith(PLUGIN_HEADER_PREFIX)) return true;
202
+ const fm = locateFrontmatter(lines);
203
+ if (!fm || fm.startLine !== 0) return false;
204
+ const next = lines.slice(fm.endLine + 1).find((l) => l.trim() !== '');
205
+ return next !== undefined && next.startsWith(PLUGIN_HEADER_PREFIX);
206
+ }
207
+
208
+ /**
209
+ * Strips one pair of surrounding quotes, as rule-loader.mjs does for list items.
210
+ * @param {string} s
211
+ * @returns {string}
212
+ */
213
+ function unquoteItem(s) {
214
+ return s.trim().replace(/^["']|["']$/g, '');
215
+ }
216
+
217
+ /**
218
+ * Reads the `globs:` and `paths:` sequences of a frontmatter body EACH ON ITS
219
+ * OWN. `parseGlobsFrontmatter()` cannot answer this: it merges the two keys by
220
+ * design (`globs:` wins, `paths:` is discarded), so it cannot say which key
221
+ * carried which list. Accepts the three value shapes rule-loader.mjs accepts —
222
+ * indented block list, flow list `[a, b]`, single inline value.
223
+ *
224
+ * @param {string[]} bodyLines - frontmatter lines between the `---` markers
225
+ * @returns {{ globs?: { values: string[], index: number }, paths?: { values: string[], index: number } }}
226
+ * `index` is the 0-based body line of the key.
227
+ */
228
+ function readScopeKeys(bodyLines) {
229
+ /** @type {{ globs?: { values: string[], index: number }, paths?: { values: string[], index: number } }} */
230
+ const found = {};
231
+ let active = null;
232
+ bodyLines.forEach((raw, index) => {
233
+ const line = raw.replace(/\s+$/, '');
234
+ if (line === '' || /^\s*#/.test(line)) return;
235
+ if (active) {
236
+ const item = /^\s+-\s+(.*)$/.exec(line);
237
+ if (item) {
238
+ found[active].values.push(unquoteItem(item[1]));
239
+ return;
240
+ }
241
+ active = null;
242
+ }
243
+ const key = /^(globs|paths):\s*(.*)$/.exec(line);
244
+ if (!key) return;
245
+ const [, name, value] = key;
246
+ let values = [];
247
+ if (value === '') {
248
+ active = name;
249
+ } else if (value.startsWith('[') && value.endsWith(']')) {
250
+ const inner = value.slice(1, -1).trim();
251
+ values = inner === '' ? [] : inner.split(',').map(unquoteItem);
252
+ } else {
253
+ values = [unquoteItem(value)];
254
+ }
255
+ found[name] = { values, index };
256
+ });
257
+ return found;
178
258
  }
179
259
 
180
260
  /**
@@ -463,7 +543,7 @@ const FOREIGN_TOKEN_RE = /[A-Z][a-z]+[A-Z]/;
463
543
 
464
544
  /**
465
545
  * @typedef {object} RuleViolation
466
- * @property {string} rule - check id (paths-frontmatter | provenance-header | placeholder | zero-match-globs | foreign-glob)
546
+ * @property {string} rule - check id (frontmatter-not-at-top | globs-paths-mismatch | provenance-header | placeholder | zero-match-globs | foreign-glob)
467
547
  * @property {'error'|'warn'} severity
468
548
  * @property {string} message
469
549
  * @property {number} [line] - 1-based line number, when known
@@ -486,52 +566,94 @@ export function validateRuleContent({ content, relPath, targetRoot = null, requi
486
566
  /** @type {RuleViolation[]} */
487
567
  const violations = [];
488
568
 
489
- // ---- paths-frontmatter (error) ----
490
- const fm = extractFrontmatter(content);
491
- if (fm) {
492
- const pathsMatch = /^paths:/m.exec(fm.body);
493
- if (pathsMatch) {
569
+ const lines = content.split(/\r?\n/);
570
+ const fm = locateFrontmatter(lines);
571
+
572
+ // ---- frontmatter-not-at-top (error) ----
573
+ // Claude Code reads frontmatter only when `---` is line 1. Anything before
574
+ // it (the provenance comment, a blank line) makes it see NO frontmatter, so
575
+ // a path-scoped rule loads ALWAYS-ON in every consumer repo, with no error.
576
+ // Two detectors, one verdict (BV-003): `locateFrontmatter` sees a block
577
+ // behind blank lines / single-line comments (the provenance shape);
578
+ // `parseGlobsFrontmatter` (the loader — the same code the wave injector
579
+ // runs) additionally sees one behind ANY text, e.g. a heading. Either
580
+ // signal is the defect; the finding is reported once.
581
+ const loaderDefect = parseGlobsFrontmatter(content).defect === FRONTMATTER_NOT_AT_TOP;
582
+ if ((fm && fm.startLine !== 0) || (!(fm && fm.startLine === 0) && loaderDefect)) {
583
+ const openerLine = fm ? fm.startLine + 1 : lines.findIndex((l) => l === '---') + 1;
584
+ violations.push({
585
+ rule: 'frontmatter-not-at-top',
586
+ severity: 'error',
587
+ message:
588
+ `${relPath}: the frontmatter block opens on line ${openerLine}, not line 1. ` +
589
+ `Claude Code reads frontmatter only when '---' is the very first line, so it sees none ` +
590
+ `here and loads this rule always-on, ignoring its 'paths:' scope. Move the block to the ` +
591
+ `top; the provenance header goes on the first line after the closing '---'.`,
592
+ line: 1,
593
+ });
594
+ }
595
+
596
+ // ---- globs-paths-mismatch (error) ----
597
+ // Judged only on a line-1 block: a misplaced block is read by no loader,
598
+ // and frontmatter-not-at-top already reports it.
599
+ if (fm && fm.startLine === 0) {
600
+ const { globs, paths } = readScopeKeys(fm.bodyLines);
601
+ // body index i sits on 1-based file line i + 2 when the opener is line 1.
602
+ const keyLine = (k) => k.index + 2;
603
+ let detail = null;
604
+ let line;
605
+ if (globs && !paths) {
606
+ detail =
607
+ `declares 'globs:' but no 'paths:'. Claude Code reads only 'paths:', so it loads this ` +
608
+ `rule always-on. Add 'paths:' carrying the identical list.`;
609
+ line = keyLine(globs);
610
+ } else if (paths && !globs) {
611
+ detail =
612
+ `declares 'paths:' but no 'globs:'. rule-loader.mjs prefers 'globs:' (#742/#795); a ` +
613
+ `rule vendored out through the plugin's rules/ library carries both keys. Add 'globs:' ` +
614
+ `carrying the identical list. This does NOT apply to a repo's own consolidated ` +
615
+ `.claude/rules/ files — those are paths:-only by design and this gate never scans them.`;
616
+ line = keyLine(paths);
617
+ } else if (globs && paths) {
618
+ const g = new Set(globs.values);
619
+ const p = new Set(paths.values);
620
+ const onlyGlobs = [...g].filter((x) => !p.has(x));
621
+ const onlyPaths = [...p].filter((x) => !g.has(x));
622
+ if (onlyGlobs.length > 0 || onlyPaths.length > 0) {
623
+ detail =
624
+ `'globs:' and 'paths:' carry different lists (only in globs: ${JSON.stringify(onlyGlobs)}; ` +
625
+ `only in paths: ${JSON.stringify(onlyPaths)}). Claude Code scopes by 'paths:', ` +
626
+ `rule-loader.mjs by 'globs:', so the two would activate this rule on different files. ` +
627
+ `Make both lists identical.`;
628
+ line = keyLine(globs.index <= paths.index ? globs : paths);
629
+ }
630
+ }
631
+ if (detail) {
494
632
  violations.push({
495
- rule: 'paths-frontmatter',
633
+ rule: 'globs-paths-mismatch',
496
634
  severity: 'error',
497
- // NOTE (#795 / corrected 2026-07-25): the previous wording claimed rule-loader.mjs
498
- // does not recognize `paths:` and that such a rule loads always-on. Both are false
499
- // since #795 — `rule-loader.mjs:275` accepts `paths` alongside `globs`, and `:313`
500
- // treats it as a fallback alias (`globs:` wins silently when both are present), so a
501
- // `paths:`-only rule IS glob-scoped. The probe itself stays: it enforces the canonical
502
- // vendoring form, which is a convention gate, not a loader-compatibility gate. That
503
- // intent survives #795 and is the subject of the #742 fleet canonicalisation sweep.
504
- // SCOPE (2026-09-16): this fires only over `rules/` library sources — the module doc's
505
- // § Scope section names every caller. Do NOT re-point it at a repo's own
506
- // `.claude/rules/`: those consolidated files are `paths:`-canonical and obeying this
507
- // remedy there would undo the consolidation (docs/rule-authoring.md point 3).
508
- message:
509
- `${relPath}: frontmatter declares a top-level 'paths:' key. It is a recognized alias ` +
510
- `for 'globs:' (issue #795), so the rule does load glob-scoped — but 'globs:' is the ` +
511
- `canonical form for a rule VENDORED OUT through the plugin's rules/ library, which is ` +
512
- `this probe's only population. Migrate to 'globs:' (see issue #742). This does NOT ` +
513
- `apply to a repo's own consolidated .claude/rules/ files — those are paths:-canonical ` +
514
- `(Claude Code's native loader reads only 'paths:') and this gate never scans them.`,
515
- line: lineWithinFrontmatter(fm.startLine, fm.body, pathsMatch.index),
635
+ message: `${relPath}: frontmatter ${detail}`,
636
+ line,
516
637
  });
517
638
  }
518
639
  }
519
640
 
520
641
  // ---- provenance-header (error, opt-in) ----
521
- if (requireProvenance) {
522
- const firstLine = content.split('\n')[0] ?? '';
523
- if (!firstLine.startsWith(PLUGIN_HEADER_PREFIX)) {
524
- violations.push({
525
- rule: 'provenance-header',
526
- severity: 'error',
527
- message:
528
- `${relPath}: missing provenance header — the first line must start with ` +
529
- `${JSON.stringify(PLUGIN_HEADER_PREFIX)} (see rules-sync.mjs PLUGIN_HEADER_PREFIX). ` +
530
- `Without it, rules-sync.mjs mis-detects this file as a local override on the next re-sync ` +
531
- `and will never update it again.`,
532
- line: 1,
533
- });
534
- }
642
+ // A header on line 1 followed by a frontmatter is accepted HERE (the file
643
+ // has a header, and isPluginOwnedContent must keep old vendored copies
644
+ // plugin-owned); frontmatter-not-at-top reports that shape, once.
645
+ if (requireProvenance && !isPluginOwnedContent(content)) {
646
+ violations.push({
647
+ rule: 'provenance-header',
648
+ severity: 'error',
649
+ message:
650
+ `${relPath}: missing provenance header — a line starting with ` +
651
+ `${JSON.stringify(PLUGIN_HEADER_PREFIX)} (see rules-sync.mjs PLUGIN_HEADER_PREFIX) must ` +
652
+ `stand either on line 1 (a rule without frontmatter) or on the first non-blank line after ` +
653
+ `the closing '---' of a frontmatter that opens on line 1. Without it, rules-sync.mjs ` +
654
+ `mis-detects this file as a local override on the next re-sync and will never update it again.`,
655
+ line: 1,
656
+ });
535
657
  }
536
658
 
537
659
  // ---- placeholder (error) — skip matches inside fenced code blocks ----
@@ -569,6 +691,9 @@ export function validateRuleContent({ content, relPath, targetRoot = null, requi
569
691
  }
570
692
 
571
693
  // ---- globs: derived checks (zero-match-globs, foreign-glob) ----
694
+ // Driven by the loader's own parse, so these judge exactly the list
695
+ // rule-loader.mjs will scope by (`globs:`, else its `paths:` alias). On a
696
+ // misplaced block that yields no globs — frontmatter-not-at-top covers it.
572
697
  let globs;
573
698
  try {
574
699
  ({ globs } = parseGlobsFrontmatter(content));
@@ -617,8 +742,9 @@ export function validateRuleContent({ content, relPath, targetRoot = null, requi
617
742
  }
618
743
 
619
744
  /**
620
- * Validates every `*.md` rule file under `dir`, recursively (skips dotfiles
621
- * and `_index.md`).
745
+ * Validates every `*.md` rule file under `dir`, recursively (skips dotfiles,
746
+ * `_index.md` and `README.md` — neither is a vendored rule: `rules-sync.mjs`
747
+ * vendors only the entries listed in `_index.md`, #1164).
622
748
  *
623
749
  * When `pluginRoot` is given, every scanned file is additionally passed through
624
750
  * `scanVendoringLeaks()` and the findings are collected into the additive
@@ -642,7 +768,7 @@ export function validateRulesDir({ dir, targetRoot = null, requireProvenance = f
642
768
  const absPath = join(absDir, entry.name);
643
769
  if (entry.isDirectory()) {
644
770
  files.push(...collectRuleFiles(absPath));
645
- } else if (entry.isFile() && entry.name.endsWith('.md') && entry.name !== '_index.md') {
771
+ } else if (entry.isFile() && entry.name.endsWith('.md') && entry.name !== '_index.md' && entry.name !== 'README.md') {
646
772
  files.push(relative(dir, absPath).replace(/\\/g, '/'));
647
773
  }
648
774
  }
@@ -5,10 +5,18 @@
5
5
  * vault writes under `40-learnings/<repoNs>/` and `50-sessions/<repoNs>/`.
6
6
  *
7
7
  * Contract:
8
- * resolveRepoNamespace({ vaultName?, cwd? }) → string
8
+ * resolveRepoNamespace({ vaultName?, repoRoot? }) → string
9
9
  *
10
- * - Pure + deterministic (given the same git remote / cwd / vaultName input,
11
- * and the same host-local pseudonym map).
10
+ * - Pure + deterministic (given the same vaultName, the same `.vault.yaml`
11
+ * under `repoRoot`, the same git remote, and the same host-local pseudonym
12
+ * map).
13
+ * - `repoRoot` (#1389) selects the directory whose `.vault.yaml` is read;
14
+ * it defaults to `process.cwd()`. Named limit: it scopes ONLY that lookup —
15
+ * the {@link deriveRepo} fallback still resolves from the process cwd and
16
+ * is cached per process, so a `repoRoot` that differs from the cwd AND has
17
+ * no declared slug falls back to the cwd's git identity. Every production
18
+ * caller passes its own cwd today; revisit if a caller ever mirrors a repo
19
+ * other than the one it runs in.
12
20
  * - Returns a lowercase kebab slug safe for use as a filesystem path segment.
13
21
  * - Host-local pseudonym mapping (Epic #725 D5): consulted ONLY at the redaction
14
22
  * site (only when a segment is owner-leaky). If such a repo (raw or sanitised)
@@ -47,6 +55,7 @@ import { isOwnerLeakySegment } from '../../lib/validate/check-owner-leakage.mjs'
47
55
  import { loadPseudonymMap } from './pseudonym-map.mjs';
48
56
  import { loadHostPaths, resolveHostPath } from '../config/host-paths.mjs';
49
57
  import { isQueryFailure, resolvePreferredRemote } from '../vcs-repo-spec.mjs';
58
+ import { readVaultSlug } from '../vault-yaml.mjs';
50
59
 
51
60
  // ── Lazy pseudonym-map path resolution (Epic #725 D5) ────────────────────────
52
61
  // The map path comes from env SO_NAMESPACE_MAP > owner.yaml paths.namespace-map-path
@@ -237,19 +246,48 @@ function lookupPseudonym(base, seg) {
237
246
  *
238
247
  * @param {object} [opts]
239
248
  * @param {string|null} [opts.vaultName] - Optional override for the repo identifier.
240
- * When non-empty and non-whitespace, used in place of the git-derived repo name.
241
- * When absent, the namespace is derived from the git origin via deriveRepo().
249
+ * When non-empty and non-whitespace, used in place of the declared/git-derived
250
+ * repo name. When absent, the base is the repo's declared `.vault.yaml`
251
+ * `metadata.slug` (#1131), else the git origin via deriveRepo().
252
+ * @param {string|null} [opts.repoRoot] - Directory whose `.vault.yaml` supplies
253
+ * the declared slug (#1389). `undefined`/`null` → `process.cwd()`. Does NOT
254
+ * redirect the deriveRepo() fallback (see the module header's named limit).
242
255
  * @returns {string} A single kebab-slug path segment, e.g. 'session-orchestrator'.
243
256
  * Special returns:
244
257
  * - 'unknown-repo' — slug derivation produced an empty string.
245
258
  * - 'redacted-repo' — the raw or slugified value matched an owner-leakage pattern
246
259
  * (CP1 personal home path / CP6 private slug / CP10 personal name in Projects path).
247
260
  */
248
- export function resolveRepoNamespace({ vaultName = null } = {}) {
249
- // Choose the base identifier: explicit override first, then git-derived.
261
+ export function resolveRepoNamespace({ vaultName = null, repoRoot = null } = {}) {
262
+ // Choose the base identifier. Precedence: explicit `vaultName` override >
263
+ // the repo's declared `.vault.yaml` `metadata.slug` (#1131) > git-derived.
264
+ //
265
+ // WHY the slug sits HERE and not in deriveRepo(): deriveRepo() is the RAW
266
+ // repo IDENTITY (`org/repo`) and has consumers that want exactly that and
267
+ // nothing else — `vault-relocation-rules.mjs`, `relocate-vault-corpus.mjs`,
268
+ // `named-vault-resolver.mjs` (which documents the `org/repo-name` format).
269
+ // #1131 concerns the vault FOLDER namespace, which is what THIS function
270
+ // decides, so the lookup belongs at this candidate-selection point.
271
+ //
272
+ // Note what this placement does NOT buy: the `source-repo:` frontmatter is
273
+ // this function's output, not deriveRepo()'s (#732; process.mjs deliberately
274
+ // shares ONE value between the write path and the rendered field), so a repo
275
+ // whose declared slug differs from its derived name gets a new `source-repo`
276
+ // either way. That is inherent to moving the folder, not a side effect of
277
+ // where the lookup lives.
278
+ //
279
+ // It sits BELOW `vaultName` (an explicit per-call/operator override must stay
280
+ // honourable) and ABOVE derivation (a declared registration beats a guess from
281
+ // the remote URL or the checkout directory: the remote answers "where does
282
+ // this code live?", the slug answers "which vault project IS this?", and a
283
+ // host-side rename legitimately diverges the two). It sits ABOVE the leak
284
+ // guard below deliberately — a declared slug is operator data flowing into a
285
+ // written path and is checked by CP1/CP6/CP10 and the pseudonym map exactly
286
+ // like any git-derived identifier.
287
+ const declaredSlug = readVaultSlug(repoRoot ?? process.cwd());
250
288
  const base = (vaultName && typeof vaultName === 'string' && vaultName.trim())
251
289
  ? vaultName.trim()
252
- : deriveRepo();
290
+ : (declaredSlug ?? deriveRepo());
253
291
 
254
292
  // Sanitise: collapse to last path segment, lowercase, strip non-[a-z0-9-].
255
293
  const seg = subjectToSlug(base);
@@ -666,8 +666,12 @@ export async function processLearning(rawEntry, _lineNum, ctx) {
666
666
  });
667
667
  }
668
668
 
669
- // #660: namespace new writes under a per-repo subdirectory.
670
- const repoNs = resolveRepoNamespace({ vaultName: ctx?.vaultName ?? null });
669
+ // #660: namespace new writes under a per-repo subdirectory. #1389: `ctx.repoRoot`
670
+ // is optional — absent, resolveRepoNamespace falls back to process.cwd().
671
+ const repoNs = resolveRepoNamespace({
672
+ vaultName: ctx?.vaultName ?? null,
673
+ repoRoot: ctx?.repoRoot ?? null,
674
+ });
671
675
  // #725 D2: thread the resolved repo namespace into the learning frontmatter as
672
676
  // `source-repo` for cross-repo attribution. repoNs is already sanitised +
673
677
  // leak-guarded by resolveRepoNamespace, so it is safe to interpolate as-is. The
@@ -898,7 +902,10 @@ export async function processSession(rawEntry, _lineNum, ctx) {
898
902
  // owner-leaky repo's real name reached the vault through the session-note
899
903
  // frontmatter even though the directory AND the learning `source-repo` field
900
904
  // were already pseudonym-mapped/redacted (#732 leak-guard bypass).
901
- const repoNs = resolveRepoNamespace({ vaultName: ctx?.vaultName ?? null });
905
+ const repoNs = resolveRepoNamespace({
906
+ vaultName: ctx?.vaultName ?? null,
907
+ repoRoot: ctx?.repoRoot ?? null,
908
+ });
902
909
 
903
910
  // Quality gate (PRD F1.2): skip sessions whose rendered narrative is too short.
904
911
  // Measure on the rendered markdown body so the check is schema-agnostic across
@@ -476,14 +476,24 @@ export function generateSessionNote(entry, options = {}) {
476
476
  // #1276: lifecycle-only records omit every older count alias. Started
477
477
  // measures participation; completed/planned-only counts retain their
478
478
  // labels so a plan or a completion count never claims dispatch coverage.
479
- const completedAgents = waveCount(w.agent_count_completed);
480
- const plannedAgents = waveCount(w.agent_count_planned);
479
+ // The `agents_*` triple is the same lifecycle measurement under the
480
+ // de-underscored producer spelling: `metrics-collection.md` documents
481
+ // `agent_count_{planned,started,completed}`, but the live ledger carries
482
+ // one session (`main-2026-09-18-session-1`, 5 waves, measured 2026-09-18
483
+ // over `.orchestrator/metrics/sessions.jsonl`) whose waves emit only
484
+ // `agents_planned` / `agents_started` / `agents_completed` — every one of
485
+ // its 15 counts rendered `?` beside a real number, exactly the #1074 bug
486
+ // class. Each alias sits at the precedence of its documented twin, so no
487
+ // cell that already rendered a value can change.
488
+ const completedAgents = waveCount(w.agent_count_completed) ?? waveCount(w.agents_completed);
489
+ const plannedAgents = waveCount(w.agent_count_planned) ?? waveCount(w.agents_planned);
481
490
  const agentsCell =
482
491
  waveCount(w.agent_count) ??
483
492
  waveCount(w.agents) ??
484
493
  waveCount(w.agents_dispatched) ??
485
494
  waveCount(w.dispatched) ??
486
495
  waveCount(w.agent_count_started) ??
496
+ waveCount(w.agents_started) ??
487
497
  (completedAgents === undefined ? undefined : `${completedAgents} completed`) ??
488
498
  (plannedAgents === undefined ? MISSING_CELL : `${plannedAgents} planned`);
489
499
  const filesCell = waveCount(w.files_changed) ?? waveCount(w.files) ?? MISSING_CELL;