session-orchestrator 3.24.0 → 4.0.1

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 (435) hide show
  1. package/.agents/skills/architecture/SKILL.md +18 -0
  2. package/.agents/skills/autopilot/SKILL.md +17 -0
  3. package/.agents/skills/bootstrap/SKILL.md +20 -0
  4. package/.agents/skills/brainstorm/SKILL.md +22 -0
  5. package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
  6. package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
  7. package/.agents/skills/debug/SKILL.md +22 -0
  8. package/.agents/skills/discovery/SKILL.md +20 -0
  9. package/.agents/skills/dispatcher/SKILL.md +15 -0
  10. package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
  11. package/.agents/skills/ecosystem-health/SKILL.md +20 -0
  12. package/.agents/skills/eli5/SKILL.md +20 -0
  13. package/.agents/skills/eval/SKILL.md +21 -0
  14. package/.agents/skills/evolve/SKILL.md +21 -0
  15. package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
  16. package/.agents/skills/gitlab-ops/SKILL.md +20 -0
  17. package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
  18. package/.agents/skills/grill/SKILL.md +22 -0
  19. package/.agents/skills/hook-development/SKILL.md +15 -0
  20. package/.agents/skills/mcp-builder/SKILL.md +15 -0
  21. package/.agents/skills/memory-cleanup/SKILL.md +21 -0
  22. package/.agents/skills/mode-selector/SKILL.md +17 -0
  23. package/.agents/skills/npm-publish/SKILL.md +16 -0
  24. package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
  25. package/.agents/skills/persona-panel/SKILL.md +17 -0
  26. package/.agents/skills/plan/SKILL.md +20 -0
  27. package/.agents/skills/playwright-driver/SKILL.md +20 -0
  28. package/.agents/skills/quality-gates/SKILL.md +20 -0
  29. package/.agents/skills/reconcile/SKILL.md +21 -0
  30. package/.agents/skills/remote-offload/SKILL.md +20 -0
  31. package/.agents/skills/repo-audit/SKILL.md +16 -0
  32. package/.agents/skills/session-end/SKILL.md +20 -0
  33. package/.agents/skills/session-plan/SKILL.md +20 -0
  34. package/.agents/skills/session-start/SKILL.md +20 -0
  35. package/.agents/skills/spinout/SKILL.md +16 -0
  36. package/.agents/skills/sunset-review/SKILL.md +16 -0
  37. package/.agents/skills/test-runner/SKILL.md +20 -0
  38. package/.agents/skills/tmux-layout/SKILL.md +21 -0
  39. package/.agents/skills/using-orchestrator/SKILL.md +17 -0
  40. package/.agents/skills/vault-mirror/SKILL.md +15 -0
  41. package/.agents/skills/vault-sync/SKILL.md +15 -0
  42. package/.agents/skills/wave-executor/SKILL.md +20 -0
  43. package/.agents/skills/write-executable-plan/SKILL.md +22 -0
  44. package/.claude-plugin/marketplace.json +1 -1
  45. package/.claude-plugin/plugin.json +1 -1
  46. package/.codex-plugin/plugin.json +3 -2
  47. package/.codex-plugin/skills/architecture/SKILL.md +20 -0
  48. package/.codex-plugin/skills/autopilot/SKILL.md +21 -0
  49. package/.codex-plugin/skills/autopilot/agents/openai.yaml +5 -0
  50. package/.codex-plugin/skills/bootstrap/SKILL.md +22 -0
  51. package/.codex-plugin/skills/bootstrap/agents/openai.yaml +5 -0
  52. package/.codex-plugin/skills/brainstorm/SKILL.md +22 -0
  53. package/.codex-plugin/skills/brainstorm/agents/openai.yaml +5 -0
  54. package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +17 -0
  55. package/.codex-plugin/skills/close/SKILL.md +21 -0
  56. package/.codex-plugin/skills/close/agents/openai.yaml +5 -0
  57. package/.codex-plugin/skills/convergence-monitoring/SKILL.md +24 -0
  58. package/.codex-plugin/skills/debug/SKILL.md +21 -0
  59. package/.codex-plugin/skills/debug/agents/openai.yaml +5 -0
  60. package/.codex-plugin/skills/discovery/SKILL.md +21 -0
  61. package/.codex-plugin/skills/discovery/agents/openai.yaml +5 -0
  62. package/.codex-plugin/skills/dispatcher/SKILL.md +21 -0
  63. package/.codex-plugin/skills/dispatcher/agents/openai.yaml +5 -0
  64. package/.codex-plugin/skills/docs-orchestrator/SKILL.md +20 -0
  65. package/.codex-plugin/skills/ecosystem-health/SKILL.md +22 -0
  66. package/.codex-plugin/skills/eli5/SKILL.md +21 -0
  67. package/.codex-plugin/skills/eli5/agents/openai.yaml +5 -0
  68. package/.codex-plugin/skills/eval/SKILL.md +21 -0
  69. package/.codex-plugin/skills/eval/agents/openai.yaml +5 -0
  70. package/.codex-plugin/skills/evolve/SKILL.md +21 -0
  71. package/.codex-plugin/skills/evolve/agents/openai.yaml +5 -0
  72. package/.codex-plugin/skills/frontmatter-guard/SKILL.md +17 -0
  73. package/.codex-plugin/skills/gitlab-ops/SKILL.md +22 -0
  74. package/.codex-plugin/skills/gitlab-portfolio/SKILL.md +17 -0
  75. package/.codex-plugin/skills/go/SKILL.md +22 -0
  76. package/.codex-plugin/skills/go/agents/openai.yaml +5 -0
  77. package/.codex-plugin/skills/grill/SKILL.md +21 -0
  78. package/.codex-plugin/skills/grill/agents/openai.yaml +5 -0
  79. package/.codex-plugin/skills/harness-audit/SKILL.md +19 -0
  80. package/.codex-plugin/skills/harness-audit/agents/openai.yaml +5 -0
  81. package/.codex-plugin/skills/hook-development/SKILL.md +17 -0
  82. package/.codex-plugin/skills/mcp-builder/SKILL.md +17 -0
  83. package/.codex-plugin/skills/memory-cleanup/SKILL.md +21 -0
  84. package/.codex-plugin/skills/memory-cleanup/agents/openai.yaml +5 -0
  85. package/.codex-plugin/skills/mode-selector/SKILL.md +19 -0
  86. package/.codex-plugin/skills/npm-publish/SKILL.md +18 -0
  87. package/.codex-plugin/skills/peekaboo-driver/SKILL.md +20 -0
  88. package/.codex-plugin/skills/persona-panel/SKILL.md +22 -0
  89. package/.codex-plugin/skills/persona-panel/agents/openai.yaml +5 -0
  90. package/.codex-plugin/skills/plan/SKILL.md +22 -0
  91. package/.codex-plugin/skills/plan/agents/openai.yaml +5 -0
  92. package/.codex-plugin/skills/playwright-driver/SKILL.md +22 -0
  93. package/.codex-plugin/skills/portfolio/SKILL.md +21 -0
  94. package/.codex-plugin/skills/portfolio/agents/openai.yaml +5 -0
  95. package/.codex-plugin/skills/quality-gates/SKILL.md +22 -0
  96. package/.codex-plugin/skills/reconcile/SKILL.md +21 -0
  97. package/.codex-plugin/skills/reconcile/agents/openai.yaml +5 -0
  98. package/.codex-plugin/skills/release/SKILL.md +22 -0
  99. package/.codex-plugin/skills/release/agents/openai.yaml +5 -0
  100. package/.codex-plugin/skills/remote-offload/SKILL.md +22 -0
  101. package/.codex-plugin/skills/repo-audit/SKILL.md +19 -0
  102. package/.codex-plugin/skills/repo-audit/agents/openai.yaml +5 -0
  103. package/.codex-plugin/skills/session/SKILL.md +21 -0
  104. package/.codex-plugin/skills/session/agents/openai.yaml +5 -0
  105. package/.codex-plugin/skills/session-end/SKILL.md +22 -0
  106. package/.codex-plugin/skills/session-plan/SKILL.md +22 -0
  107. package/.codex-plugin/skills/session-start/SKILL.md +22 -0
  108. package/.codex-plugin/skills/spinout/SKILL.md +21 -0
  109. package/.codex-plugin/skills/spinout/agents/openai.yaml +5 -0
  110. package/.codex-plugin/skills/sunset-review/SKILL.md +21 -0
  111. package/.codex-plugin/skills/sunset-review/agents/openai.yaml +5 -0
  112. package/.codex-plugin/skills/templates-ack/SKILL.md +21 -0
  113. package/.codex-plugin/skills/templates-ack/agents/openai.yaml +5 -0
  114. package/.codex-plugin/skills/test/SKILL.md +21 -0
  115. package/.codex-plugin/skills/test/agents/openai.yaml +5 -0
  116. package/.codex-plugin/skills/test-runner/SKILL.md +22 -0
  117. package/.codex-plugin/skills/tmux-layout/SKILL.md +23 -0
  118. package/.codex-plugin/skills/using-orchestrator/SKILL.md +19 -0
  119. package/.codex-plugin/skills/vault-mirror/SKILL.md +17 -0
  120. package/.codex-plugin/skills/vault-sync/SKILL.md +17 -0
  121. package/.codex-plugin/skills/wave-executor/SKILL.md +22 -0
  122. package/.codex-plugin/skills/write-executable-plan/SKILL.md +24 -0
  123. package/.cursor/commands/autopilot.md +2 -2
  124. package/.cursor/commands/bootstrap.md +1 -1
  125. package/.cursor/commands/brainstorm.md +1 -1
  126. package/.cursor/commands/debug.md +1 -1
  127. package/.cursor/commands/discovery.md +1 -1
  128. package/.cursor/commands/dispatcher.md +2 -2
  129. package/.cursor/commands/eli5.md +2 -2
  130. package/.cursor/commands/eval.md +2 -2
  131. package/.cursor/commands/evolve.md +1 -1
  132. package/.cursor/commands/go.md +1 -1
  133. package/.cursor/commands/grill.md +2 -2
  134. package/.cursor/commands/memory-cleanup.md +2 -2
  135. package/.cursor/commands/persona-panel.md +1 -1
  136. package/.cursor/commands/plan.md +1 -1
  137. package/.cursor/commands/portfolio.md +1 -1
  138. package/.cursor/commands/reconcile.md +2 -2
  139. package/.cursor/commands/release.md +2 -2
  140. package/.cursor/commands/session.md +2 -2
  141. package/.cursor/commands/spinout.md +2 -2
  142. package/.cursor/commands/sunset-review.md +2 -2
  143. package/.cursor/commands/templates-ack.md +2 -2
  144. package/.cursor/commands/test.md +2 -2
  145. package/.cursor/skills/brainstorm/SKILL.md +1 -1
  146. package/.cursor/skills/eval/SKILL.md +1 -1
  147. package/.cursor/skills/quality-gates/SKILL.md +1 -1
  148. package/.cursor/skills/remote-offload/SKILL.md +1 -1
  149. package/.cursor-plugin/plugin.json +30 -0
  150. package/.orchestrator/policy/blocked-commands.json +121 -0
  151. package/.orchestrator/policy/ecosystem.schema.json +66 -0
  152. package/.orchestrator/policy/quality-gates.example.json +16 -0
  153. package/.orchestrator/policy/quality-gates.schema.json +38 -0
  154. package/.orchestrator/policy/templates-policy.json +27 -0
  155. package/.orchestrator/policy/test-profiles.json +47 -0
  156. package/AGENTS.md +225 -0
  157. package/CHANGELOG.md +1314 -2
  158. package/NOTICE +11 -6
  159. package/README.md +135 -94
  160. package/agents/eval-judge.md +1 -1
  161. package/agents/skill-applied-judge.md +1 -1
  162. package/assets/wave-lifecycle.svg +98 -0
  163. package/commands/release.md +6 -3
  164. package/commands/session.md +18 -3
  165. package/docs/README.md +4 -0
  166. package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
  167. package/docs/baseline.md +67 -0
  168. package/docs/ci-setup.md +108 -62
  169. package/docs/codex-setup.md +107 -29
  170. package/docs/components.md +38 -16
  171. package/docs/cursor-setup.md +6 -2
  172. package/docs/events-schema.md +9 -6
  173. package/docs/instruction-delivery.md +69 -0
  174. package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
  175. package/docs/migration-v4.md +365 -0
  176. package/docs/pi-setup.md +6 -1
  177. package/docs/plugin-architecture-v3.md +1 -1
  178. package/docs/rule-authoring.md +85 -19
  179. package/docs/scope-collision-guard.md +5 -5
  180. package/docs/session-config-reference.md +57 -56
  181. package/docs/session-config-template.md +6 -29
  182. package/docs/telemetry.md +157 -3
  183. package/docs/vault-docs-architecture.md +50 -11
  184. package/hooks/_lib/hook-import-set.json +1488 -0
  185. package/hooks/_lib/subagent-transcript.mjs +562 -0
  186. package/hooks/config-protection.mjs +2 -2
  187. package/hooks/cwd-change-restore.mjs +2 -2
  188. package/hooks/enforce-commands.mjs +69 -0
  189. package/hooks/hooks-codex.json +1 -1
  190. package/hooks/hooks-cursor.json +10 -0
  191. package/hooks/hooks-pi.json +5 -0
  192. package/hooks/hooks.json +6 -1
  193. package/hooks/loop-guard.mjs +3 -3
  194. package/hooks/on-session-end.mjs +2 -2
  195. package/hooks/on-session-start.mjs +103 -2
  196. package/hooks/on-stop.mjs +60 -14
  197. package/hooks/operator-steer.mjs +2 -2
  198. package/hooks/post-bash-write-verify.mjs +85 -0
  199. package/hooks/post-edit-import-probe.mjs +344 -0
  200. package/hooks/post-subagent-discovery-validator.mjs +187 -431
  201. package/hooks/post-tool-batch-wave-signal.mjs +118 -4
  202. package/hooks/post-tool-failure-corrective-context.mjs +2 -2
  203. package/hooks/post-tooluse-frontend-slop.mjs +3 -3
  204. package/hooks/pre-bash-destructive-guard.mjs +39 -13
  205. package/hooks/skill-invocation-telemetry.mjs +17 -5
  206. package/hooks/subagent-telemetry.mjs +13 -4
  207. package/monitors/monitors.json +3 -3
  208. package/package.json +9 -1
  209. package/pi/prompts/session.md +2 -2
  210. package/scripts/backfill-abandoned-sessions.mjs +50 -4
  211. package/scripts/backfill-learnings-from-vault.mjs +9 -3
  212. package/scripts/dialectic-deriver.mjs +73 -8
  213. package/scripts/export-hw-learnings.mjs +113 -1
  214. package/scripts/generate-agents-skills.mjs +378 -0
  215. package/scripts/generate-codex-skills.mjs +246 -0
  216. package/scripts/generate-cursor-adapter.mjs +45 -8
  217. package/scripts/generate-hook-import-set.mjs +292 -0
  218. package/scripts/lib/agent-status.mjs +13 -2
  219. package/scripts/lib/auto-dream.mjs +38 -36
  220. package/scripts/lib/autonomy/suitability.mjs +6 -0
  221. package/scripts/lib/autopilot/loop.mjs +2 -2
  222. package/scripts/lib/ci-status-banner.mjs +220 -75
  223. package/scripts/lib/codex/plugin-contract.mjs +88 -6
  224. package/scripts/lib/config/auto-dream.mjs +2 -1
  225. package/scripts/lib/config/block-header.mjs +8 -0
  226. package/scripts/lib/config/block-preprocess.mjs +177 -0
  227. package/scripts/lib/config/broken-window.mjs +2 -1
  228. package/scripts/lib/config/cold-start.mjs +2 -1
  229. package/scripts/lib/config/config-protection.mjs +22 -2
  230. package/scripts/lib/config/context-coverage.mjs +2 -1
  231. package/scripts/lib/config/cross-repo.mjs +2 -1
  232. package/scripts/lib/config/custom-phases.mjs +2 -1
  233. package/scripts/lib/config/dialectic.mjs +2 -1
  234. package/scripts/lib/config/discovery-validator.mjs +2 -1
  235. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
  236. package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
  237. package/scripts/lib/config/docs-orchestrator.mjs +2 -1
  238. package/scripts/lib/config/docs-staleness.mjs +2 -1
  239. package/scripts/lib/config/drift-check.mjs +2 -1
  240. package/scripts/lib/config/eval.mjs +2 -1
  241. package/scripts/lib/config/events-rotation.mjs +2 -1
  242. package/scripts/lib/config/evolve.mjs +8 -2
  243. package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
  244. package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
  245. package/scripts/lib/config/handover-gate.mjs +2 -1
  246. package/scripts/lib/config/health-endpoints.mjs +7 -2
  247. package/scripts/lib/config/host-paths.mjs +20 -4
  248. package/scripts/lib/config/issue-budget.mjs +2 -1
  249. package/scripts/lib/config/loop-guard.mjs +2 -1
  250. package/scripts/lib/config/memory.mjs +2 -1
  251. package/scripts/lib/config/moc-staleness.mjs +2 -1
  252. package/scripts/lib/config/persona-gate-wave.mjs +2 -1
  253. package/scripts/lib/config/private-config-dir.mjs +67 -0
  254. package/scripts/lib/config/reconcile.mjs +2 -1
  255. package/scripts/lib/config/remote-hosts.mjs +2 -1
  256. package/scripts/lib/config/section-extractor.mjs +7 -1
  257. package/scripts/lib/config/skill-evolution.mjs +2 -1
  258. package/scripts/lib/config/slopcheck.mjs +2 -1
  259. package/scripts/lib/config/state-md-lock.mjs +2 -1
  260. package/scripts/lib/config/templates-first.mjs +2 -1
  261. package/scripts/lib/config/test.mjs +2 -1
  262. package/scripts/lib/config/vault-integration.mjs +7 -1
  263. package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
  264. package/scripts/lib/config/vault-staleness.mjs +2 -1
  265. package/scripts/lib/config/vault-sync.mjs +2 -1
  266. package/scripts/lib/config/verification-auto-fix.mjs +2 -1
  267. package/scripts/lib/config/wave-reviewers.mjs +2 -1
  268. package/scripts/lib/config/worktree-orphans.mjs +2 -1
  269. package/scripts/lib/convergence-monitor.mjs +82 -16
  270. package/scripts/lib/dispatcher/rank.mjs +124 -48
  271. package/scripts/lib/ecosystem-health.mjs +16 -2
  272. package/scripts/lib/eval/engine.mjs +9 -1
  273. package/scripts/lib/eval/session-resolve.mjs +23 -4
  274. package/scripts/lib/events.mjs +22 -6
  275. package/scripts/lib/frontmatter-guard.mjs +131 -13
  276. package/scripts/lib/gates/gate-full.mjs +30 -0
  277. package/scripts/lib/gates/gate-helpers.mjs +76 -0
  278. package/scripts/lib/hardware-pattern-detector.mjs +18 -1
  279. package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
  280. package/scripts/lib/host-identity.mjs +50 -11
  281. package/scripts/lib/instruction-budget-guard.mjs +171 -5
  282. package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
  283. package/scripts/lib/learnings/io.mjs +60 -6
  284. package/scripts/lib/memory-proposals/store.mjs +30 -22
  285. package/scripts/lib/owner-config-banner.mjs +41 -6
  286. package/scripts/lib/owner-config-loader.mjs +21 -10
  287. package/scripts/lib/owner-interview.mjs +3 -3
  288. package/scripts/lib/owner-yaml.mjs +215 -15
  289. package/scripts/lib/platform.mjs +108 -15
  290. package/scripts/lib/plugin-update-banner.mjs +414 -0
  291. package/scripts/lib/project-hygiene.mjs +38 -2
  292. package/scripts/lib/qg-command-drift-banner.mjs +50 -12
  293. package/scripts/lib/quality-gate.mjs +133 -44
  294. package/scripts/lib/reconcile/emitter.mjs +68 -6
  295. package/scripts/lib/reconcile/engine.mjs +51 -11
  296. package/scripts/lib/reconcile/idempotency.mjs +37 -4
  297. package/scripts/lib/reconcile/writer.mjs +40 -18
  298. package/scripts/lib/session-close-backfill.mjs +67 -9
  299. package/scripts/lib/session-id.mjs +12 -23
  300. package/scripts/lib/session-identity/own-session.mjs +125 -10
  301. package/scripts/lib/session-lock-shape.mjs +43 -0
  302. package/scripts/lib/session-lock.mjs +5 -10
  303. package/scripts/lib/session-registry.mjs +25 -9
  304. package/scripts/lib/session-schema/constants.mjs +64 -3
  305. package/scripts/lib/session-schema/validator.mjs +38 -4
  306. package/scripts/lib/session-start-probes.mjs +30 -1
  307. package/scripts/lib/sessions-staleness-banner.mjs +18 -11
  308. package/scripts/lib/skill-health/join.mjs +17 -4
  309. package/scripts/lib/state-md.mjs +78 -0
  310. package/scripts/lib/sunset/walker.mjs +6 -0
  311. package/scripts/lib/telemetry/schema.mjs +202 -9
  312. package/scripts/lib/telemetry/sync.mjs +368 -12
  313. package/scripts/lib/telemetry-flush-health-banner.mjs +211 -0
  314. package/scripts/lib/validate/check-agents-skills.mjs +327 -0
  315. package/scripts/lib/validate/check-agents.mjs +3 -3
  316. package/scripts/lib/validate/check-codex-skills.mjs +191 -0
  317. package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
  318. package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
  319. package/scripts/lib/validate/check-owner-leakage.mjs +319 -22
  320. package/scripts/lib/validate/check-skill-links.mjs +193 -0
  321. package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
  322. package/scripts/lib/validate/check-test-git-config-target.mjs +192 -12
  323. package/scripts/lib/validate/check-unwired-features.mjs +163 -15
  324. package/scripts/lib/validate/check-validator-registration.mjs +10 -4
  325. package/scripts/lib/validate/confidential-names.mjs +95 -30
  326. package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
  327. package/scripts/lib/validate/repo-files.mjs +48 -14
  328. package/scripts/lib/vault-backfill/template.mjs +63 -6
  329. package/scripts/lib/vault-mirror/process.mjs +165 -42
  330. package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
  331. package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
  332. package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
  333. package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
  334. package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
  335. package/scripts/lib/wave-resource-gate.mjs +8 -2
  336. package/scripts/lib/wave-sizing.mjs +4 -1
  337. package/scripts/lib/wave-transcript-tail.mjs +118 -4
  338. package/scripts/materialize-wave-scope.mjs +12 -5
  339. package/scripts/memory-propose.mjs +19 -5
  340. package/scripts/migrate-cold-start-seed.mjs +4 -1
  341. package/scripts/parse-config.mjs +60 -3
  342. package/scripts/release.mjs +430 -31
  343. package/scripts/repair-invalid-sessions.mjs +3 -3
  344. package/scripts/run-quality-gate.mjs +128 -11
  345. package/scripts/site-numbers.mjs +344 -8
  346. package/scripts/sweep-expired-learnings.mjs +90 -0
  347. package/scripts/sync-vault-schema.mjs +3 -1
  348. package/scripts/telemetry.mjs +2 -2
  349. package/scripts/validate-plugin.mjs +164 -0
  350. package/scripts/validate-wave-scope.mjs +28 -8
  351. package/scripts/wave-scope-binding.mjs +215 -0
  352. package/skills/_shared/instruction-file-resolution.md +10 -0
  353. package/skills/_shared/parallel-aware-preamble.md +1 -0
  354. package/skills/_shared/platform-tools.md +1 -1
  355. package/skills/_shared/state-ownership.md +1 -1
  356. package/skills/architecture/SKILL.md +7 -5
  357. package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
  358. package/skills/autopilot/SKILL.md +4 -18
  359. package/skills/claude-md-drift-check/SKILL.md +5 -1
  360. package/skills/claude-md-drift-check/checker.mjs +62 -2
  361. package/skills/convergence-monitoring/SIGNALS.md +55 -0
  362. package/skills/discovery/probes/vault-staleness.mjs +37 -13
  363. package/skills/discovery/probes-arch.md +20 -18
  364. package/skills/dispatcher/SKILL.md +3 -2
  365. package/skills/evolve/SKILL.md +65 -26
  366. package/skills/frontmatter-guard/SKILL.md +11 -5
  367. package/skills/npm-publish/SKILL.md +1 -1
  368. package/skills/reconcile/SKILL.md +33 -0
  369. package/skills/remote-offload/SKILL.md +1 -1
  370. package/skills/session-end/SKILL.md +18 -905
  371. package/skills/session-end/phase-3-6-tail.md +10 -3
  372. package/skills/session-end/plan-verification.md +221 -155
  373. package/skills/session-end/references/phase-2-quality-gate.md +93 -0
  374. package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
  375. package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
  376. package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
  377. package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
  378. package/skills/session-end/references/session-summary-template.md +62 -0
  379. package/skills/session-plan/SKILL.md +49 -0
  380. package/skills/session-start/SKILL.md +22 -904
  381. package/skills/session-start/phase-8-5-express-path.md +1 -1
  382. package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
  383. package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
  384. package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
  385. package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
  386. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
  387. package/skills/session-start/references/phase-4-ssot-environment-check.md +160 -0
  388. package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
  389. package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
  390. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
  391. package/skills/vault-sync/SKILL.md +10 -0
  392. package/skills/vault-sync/validator.mjs +21 -27
  393. package/skills/wave-executor/SKILL.md +15 -1
  394. package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
  395. package/skills/wave-executor/references/wave-loop-review.md +570 -0
  396. package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
  397. package/skills/wave-executor/wave-loop.md +14 -1309
  398. package/templates/_shared/journey-manifest.md +10 -6
  399. package/.cursor/commands/autopilot-multi.md +0 -14
  400. package/.cursor/commands/contract-version-bump.md +0 -14
  401. package/.cursor/commands/journey-audit.md +0 -14
  402. package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
  403. package/.cursor/skills/daily/SKILL.md +0 -12
  404. package/.cursor/skills/domain-model/SKILL.md +0 -13
  405. package/.cursor/skills/journey-audit/SKILL.md +0 -13
  406. package/.cursor/skills/skill-creator/SKILL.md +0 -13
  407. package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
  408. package/commands/autopilot-multi.md +0 -74
  409. package/commands/contract-version-bump.md +0 -28
  410. package/commands/journey-audit.md +0 -43
  411. package/pi/prompts/autopilot-multi.md +0 -12
  412. package/pi/prompts/contract-version-bump.md +0 -12
  413. package/pi/prompts/journey-audit.md +0 -12
  414. package/scripts/autopilot-multi.mjs +0 -885
  415. package/scripts/backfill-learnings-expires.mjs +0 -196
  416. package/scripts/backfill-learnings.mjs +0 -203
  417. package/scripts/fleet-instruction-scan.mjs +0 -141
  418. package/scripts/lib/autopilot/dep-graph.mjs +0 -417
  419. package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
  420. package/scripts/lib/webhook-url.mjs +0 -105
  421. package/scripts/lifecycle-sim-v6.mjs +0 -347
  422. package/scripts/migrate-learnings-jsonl.mjs +0 -189
  423. package/scripts/migrate-subagents-jsonl.mjs +0 -196
  424. package/scripts/upload-social-preview.mjs +0 -316
  425. package/skills/_shared/model-selection.md +0 -64
  426. package/skills/contract-version-bump/SKILL.md +0 -219
  427. package/skills/daily/SKILL.md +0 -222
  428. package/skills/daily/generate.sh +0 -92
  429. package/skills/daily/templates/daily.md.tpl +0 -36
  430. package/skills/journey-audit/SKILL.md +0 -270
  431. package/skills/skill-creator/SKILL.md +0 -168
  432. package/skills/ubiquitous-language/SKILL.md +0 -97
  433. package/skills/vault-sync/package-lock.json +0 -40
  434. /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
  435. /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
@@ -81,11 +81,12 @@
81
81
  * 1 — at least one failure (or usage error / unreadable root)
82
82
  */
83
83
 
84
- import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';
84
+ import { readFileSync, readdirSync, statSync, existsSync, realpathSync } from 'node:fs';
85
85
  import { join, extname, relative, basename, sep, resolve } from 'node:path';
86
86
  import { execFileSync } from 'node:child_process';
87
87
  import { argv } from 'node:process';
88
88
  import { fileURLToPath } from 'node:url';
89
+ import { createRequire } from 'node:module';
89
90
  // NOTE: the two host-local helper modules (../config/host-paths.mjs and
90
91
  // ./confidential-names.mjs) are imported DYNAMICALLY inside
91
92
  // getConfidentialNamePatterns(), NOT statically here. This scanner is a
@@ -93,7 +94,9 @@ import { fileURLToPath } from 'node:url';
93
94
  // "Reuse the same scanner") — the .husky/pre-commit E2E and any consumer that
94
95
  // copies ONLY this file into a fresh tree would otherwise crash at module load
95
96
  // with ERR_MODULE_NOT_FOUND, blocking clean commits. Dynamic import lets CP11 go
96
- // silently inert when the helpers are absent while CP1–CP10 run unchanged.
97
+ // inert (one WARN line, exit unchanged) when the helpers are absent while CP1–CP10
98
+ // run unchanged. That degrade is scoped to ERR_MODULE_NOT_FOUND ALONE (#1244) —
99
+ // every other CP11 load failure fails CLOSED; see getConfidentialNamePatterns().
97
100
 
98
101
  // ---------------------------------------------------------------------------
99
102
  // CLI / import-mode detection (#661)
@@ -103,7 +106,25 @@ import { fileURLToPath } from 'node:url';
103
106
  // AND an importable library (the canonicalization helpers are unit-tested in
104
107
  // isolation). When imported, the top-level scan + process.exit() must NOT run.
105
108
  // `isMain` is true only when this file is the node entry point.
106
- const isMain = argv[1] !== undefined && resolve(argv[1]) === fileURLToPath(import.meta.url);
109
+ // REALPATH BOTH SIDES (#1244 / same class as the #1153 argv[1] main-guard finding).
110
+ // `fileURLToPath(import.meta.url)` is already canonicalized by Node's ESM loader,
111
+ // so a plain `resolve(argv[1])` comparison silently fails whenever the invocation
112
+ // path traverses a symlink — on macOS every `/tmp/...` path does (`/tmp` →
113
+ // `/private/tmp`). The failure mode is the worst one a security guard has: isMain
114
+ // stays false, runScan() never runs, and the process prints NOTHING and exits 0,
115
+ // i.e. a clean-looking pass that never scanned a byte. realpathSync both sides so
116
+ // the two spellings of the same file compare equal; if realpathSync throws (path
117
+ // gone, permission denied) fall back to the historical comparison.
118
+ function canonicalPath(p) {
119
+ try {
120
+ return realpathSync(p);
121
+ } catch {
122
+ return p;
123
+ }
124
+ }
125
+ const isMain =
126
+ argv[1] !== undefined &&
127
+ canonicalPath(resolve(argv[1])) === canonicalPath(fileURLToPath(import.meta.url));
107
128
 
108
129
  // CLI: single positional arg required (only enforced when run directly).
109
130
  const pluginRoot = argv[2];
@@ -568,8 +589,10 @@ function escapeRegex(s) {
568
589
  // its cases red (#974).
569
590
  //
570
591
  // The dynamic-import degrade used by getConfidentialNamePatterns() is NOT
571
- // available here. That helper degrades to `[]` CP11 goes inert, CP1–CP10 keep
572
- // running, nothing leaks. A failed REDACTION has the opposite failure direction:
592
+ // available here. That helper degrades to `[]` for the standalone-copy case alone
593
+ // CP11 goes inert, CP1–CP10 keep running, nothing leaks (every OTHER load
594
+ // failure there is now a counted FAIL, #1244). A failed REDACTION has the opposite
595
+ // failure direction:
573
596
  // it prints confidential names verbatim into a PUBLIC GitHub-Actions log, which
574
597
  // is precisely the exposure this function exists to prevent (Fix 1 below). The
575
598
  // redaction sink must be unconditionally present, so it lives inline.
@@ -645,6 +668,78 @@ function redactSpans(line, patterns) {
645
668
  return out;
646
669
  }
647
670
 
671
+ /**
672
+ * The THREE modules getConfidentialNamePatterns() imports directly, as absolute
673
+ * URLs resolved against THIS file. Used only to classify an ERR_MODULE_NOT_FOUND:
674
+ * `err.url` carries the URL of the module that could not be found (measured on
675
+ * Node 24 — a relative specifier yields `url`, a bare package specifier yields
676
+ * none), so a miss on one of these three is the standalone single-file copy,
677
+ * while a miss anywhere DEEPER (a transitive of an in-tree helper, or a bare
678
+ * package) is a broken install that must fail CLOSED rather than go inert.
679
+ */
680
+ const CP11_DIRECT_SIBLING_URLS = new Set(
681
+ ['../config/host-paths.mjs', './confidential-names.mjs', '../owner-yaml.mjs'].map(
682
+ (spec) => new URL(spec, import.meta.url).href,
683
+ ),
684
+ );
685
+
686
+ /**
687
+ * True when an ERR_MODULE_NOT_FOUND names one of this scanner's own three DIRECT
688
+ * helper imports — i.e. the documented standalone-vendoring shape. False for a
689
+ * transitive relative module or a bare package (no `err.url` at all), which is a
690
+ * broken in-tree install: CP11 then fails closed instead of silently returning
691
+ * zero patterns while names ARE configured.
692
+ *
693
+ * @param {{ url?: string }} err
694
+ * @returns {boolean}
695
+ */
696
+ function isMissingDirectSibling(err) {
697
+ return typeof err?.url === 'string' && CP11_DIRECT_SIBLING_URLS.has(err.url);
698
+ }
699
+
700
+ /**
701
+ * Was `paths.confidential-names-file` actually written into owner.yaml?
702
+ *
703
+ * WHY THIS RE-READS THE FILE. `loadOwnerConfig()` reports an invalid OPTIONAL
704
+ * section only as `droppedSections: [{ section: 'paths', errors }]` and replaces
705
+ * `config.paths` with the DEFAULTS — the raw keys of the dropped section are not
706
+ * recoverable from its return value, and its `errors[]` name a key only for the
707
+ * per-key `paths.<key> must be a string` case (a `paths: 42` shape names none).
708
+ * So the one question CP11's verdict turns on — did the operator configure a
709
+ * names file AT ALL — has no answer in the loader's contract today. Re-parsing
710
+ * the file for that single key is the minimal derivation; widening the loader's
711
+ * return shape would touch every one of its callers.
712
+ *
713
+ * Only reached when the YAML already parsed once inside loadOwnerConfig (the
714
+ * dropped-section branch implies that), so `js-yaml` is resolvable here; 'unknown'
715
+ * is the defensive residue and makes the caller fail closed.
716
+ *
717
+ * Returns a CLASS, never the value: the configured path is host-local and this
718
+ * scanner's output is captured by a PUBLIC CI mirror (see the no-path rule above).
719
+ *
720
+ * @param {string} ownerYamlPath
721
+ * @returns {'configured'|'absent'|'unknown'}
722
+ */
723
+ function rawConfidentialNamesKeyState(ownerYamlPath) {
724
+ let parsed;
725
+ try {
726
+ const yaml = createRequire(import.meta.url)('js-yaml');
727
+ parsed = yaml.load(readFileSync(ownerYamlPath, 'utf8'));
728
+ } catch {
729
+ return 'unknown';
730
+ }
731
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return 'unknown';
732
+ const paths = parsed.paths;
733
+ // `paths:` absent, null, or not a mapping at all → the key cannot be in there.
734
+ if (paths === null || typeof paths !== 'object' || Array.isArray(paths)) return 'absent';
735
+ const value = paths['confidential-names-file'];
736
+ if (value === undefined || value === null) return 'absent';
737
+ // A non-string (or empty-string) value is still an ATTEMPT to configure CP11 —
738
+ // except '' , which is the schema's documented "no override" spelling.
739
+ if (typeof value === 'string' && value.trim() === '') return 'absent';
740
+ return 'configured';
741
+ }
742
+
648
743
  /**
649
744
  * CP11: build word-boundary, case-insensitive regexes from the host-local
650
745
  * confidential-names list (#728a). Mirrors CP6_PATTERNS (private slugs), but the
@@ -656,7 +751,7 @@ function redactSpans(line, patterns) {
656
751
  * be called only from runScan() — NOT at module top-level. This module is
657
752
  * dual-mode (CLI entry point AND importable library, `isMain` guard); a top-level
658
753
  * fs read would fire on every library import (e.g. pseudonym-map.mjs importing
659
- * isOwnerLeakySegment). loadConfidentialNames caches per path, so calling this
754
+ * isOwnerLeakySegment). inspectConfidentialNames caches per path, so calling this
660
755
  * once per scan reads the file at most once.
661
756
  *
662
757
  * STANDALONE-SAFE: the two host-local helper modules are DYNAMICALLY imported
@@ -667,23 +762,198 @@ function redactSpans(line, patterns) {
667
762
  * with ERR_MODULE_NOT_FOUND. When the helpers are unresolvable (or throw), CP11
668
763
  * degrades to inert ([] patterns) and CP1–CP10 run unchanged.
669
764
  *
670
- * @returns {Promise<RegExp[]>} one regex per configured name, or [] when
671
- * unconfigured / unusable / unresolvable (standalone copy).
765
+ * FAIL CLOSED WHEN CP11 WAS EXPECTED (GitLab #1244). The single bare
766
+ * `try { } catch { return [] }` this function used to be conflated three
767
+ * outcomes that must not share a verdict, and printed `PASS` for all three:
768
+ * (a) the helpers are unresolvable — the STANDALONE single-file copy. The
769
+ * intended degrade, and the ONLY one: inert + one WARN line, exit unchanged.
770
+ * (b) CP11 is not configured at all (no names file, no env) — the ~99% default:
771
+ * inactive, silent, PASS. Unchanged. This INCLUDES an owner.yaml whose
772
+ * `paths:` section was dropped as invalid for some OTHER key (the #1244
773
+ * fix over-reached here and failed every commit on such a host): the raw
774
+ * key is re-read (rawConfidentialNamesKeyState) and, when absent, CP11 is
775
+ * INACTIVE — one WARN naming the dropped section, no FAIL, exit unchanged.
776
+ * (c) CP11 IS configured — env set, a names file resolved, or the raw
777
+ * `paths.confidential-names-file` key present in a `paths:` section that
778
+ * was dropped as invalid — or its configuration is unknowable because
779
+ * owner.yaml exists but cannot be parsed (e.g. `js-yaml` missing) — and
780
+ * could not be loaded. Previously indistinguishable from (b): the scanner matched
781
+ * nothing and printed `PASS: no owner-privacy leakage found`. A guard that
782
+ * cannot run must say so and FAIL, never report the clean verdict it did
783
+ * not earn — so this returns a `disabledReason` that runScan turns into a
784
+ * `CP11 DISABLED` stderr line plus a counted FAIL (exit 1).
785
+ *
786
+ * The reason strings deliberately carry NO PATH. This scanner's stdout+stderr are
787
+ * captured by a PUBLIC GitHub-Actions mirror, and the confidential-names path is
788
+ * host-local — echoing it there would leak the very `/Users/<name>/…` shape CP1
789
+ * exists to block. The operator knows their own path; the CLASS of failure is what
790
+ * this line has to convey.
791
+ *
792
+ * @returns {Promise<{ patterns: RegExp[], disabledReason?: string, inertWarn?: string }>}
672
793
  */
673
794
  async function getConfidentialNamePatterns() {
795
+ let helpers;
674
796
  try {
675
- const { loadHostPaths, resolveHostPath } = await import('../config/host-paths.mjs');
676
- const { loadConfidentialNames } = await import('./confidential-names.mjs');
677
- const ctx = loadHostPaths();
678
- const namesPath = resolveHostPath('confidential-names-file', '', ctx);
679
- const names = loadConfidentialNames({ namesPath });
680
- if (!names) return [];
681
- return names.map((name) => new RegExp(`\\b${escapeRegex(name)}\\b`, 'i'));
682
- } catch {
683
- // Helper modules absent (standalone single-file vendoring) or a config read
684
- // failure CP11 goes silently inert; the CP1–CP10 rules need none of these
685
- // modules and continue to enforce the scan.
686
- return [];
797
+ helpers = {
798
+ hostPaths: await import('../config/host-paths.mjs'),
799
+ confidentialNames: await import('./confidential-names.mjs'),
800
+ // Already a transitive dependency (host-paths.mjs imports it statically), so
801
+ // this adds no file to the standalone-copy chain the husky E2E mirrors.
802
+ ownerYaml: await import('../owner-yaml.mjs'),
803
+ };
804
+ } catch (err) {
805
+ if (err?.code === 'ERR_MODULE_NOT_FOUND' && isMissingDirectSibling(err)) {
806
+ // (a) standalone single-file vendoring. The degrade is documented but it
807
+ // must not swallow a CP11 that the operator DID configure (GitLab #1262).
808
+ // No helper resolved here, so the only configuration signal still readable
809
+ // is the raw env var: a literal mirror of ENV_KEYS['confidential-names-file']
810
+ // in scripts/lib/config/host-paths.mjs, which cannot be imported on this
811
+ // branch by construction.
812
+ //
813
+ // LIMIT (named, not hidden): an owner.yaml-configured names file is
814
+ // STRUCTURALLY undetectable in a standalone copy — resolveOwnerYamlPath()
815
+ // lives in the very module chain that failed to resolve, so there is no way
816
+ // to learn that owner.yaml even exists, let alone what it configures. Only
817
+ // the env route can fail closed here; the owner.yaml route degrades inert.
818
+ const envNamesPath = process.env.SO_CONFIDENTIAL_NAMES_FILE;
819
+ if (typeof envNamesPath === 'string' && envNamesPath.trim() !== '') {
820
+ return {
821
+ patterns: [],
822
+ disabledReason:
823
+ 'CP11 configured via SO_CONFIDENTIAL_NAMES_FILE but the confidential-names helpers are not resolvable (standalone copy) — failing closed',
824
+ };
825
+ }
826
+ return {
827
+ patterns: [],
828
+ inertWarn: 'CP11 inert — confidential-names helpers not resolvable (standalone copy)',
829
+ };
830
+ }
831
+ // Any OTHER import failure is a broken in-tree install, not the vendoring case.
832
+ return { patterns: [], disabledReason: `confidential-names helpers failed to load (${err?.name ?? 'Error'})` };
833
+ }
834
+
835
+ try {
836
+ const { loadHostPaths, resolveHostPath } = helpers.hostPaths;
837
+ const { inspectConfidentialNames } = helpers.confidentialNames;
838
+ const { resolveOwnerYamlPath } = helpers.ownerYaml;
839
+
840
+ // ONE owner.yaml load. Since #1251 loadHostPaths() passes the loader's own
841
+ // health (source / reason / droppedSections) straight through, so the
842
+ // env>owner.yaml>default precedence and the load's diagnosis come from the
843
+ // same read — no second loadOwnerConfig() call to recover what was discarded.
844
+ const hostCtx = loadHostPaths();
845
+
846
+ // FAIL-CLOSED on an unknowable load (LOW-2). `loadHostPaths` has its own
847
+ // defensive catch that swallows a THROWING owner loader and returns
848
+ // `{ ownerConfig: undefined, env, source: undefined, reason: undefined }`.
849
+ // Without this branch that shape resolves `namesPath` to '' , fires no
850
+ // reason branch, and returns `{ patterns: [] }` — a silent PASS.
851
+ // Only the throw produces all-undefined: `loadOwnerConfig` returns
852
+ // `{ config: getDefaults(), source: 'defaults', errors: [] }` for a
853
+ // genuinely ABSENT owner.yaml (owner-yaml.mjs:587-590) and `source:
854
+ // 'defaults'` with a `reason` for every read/parse failure — so a missing
855
+ // file keeps taking the normal path below. Latent today (the loader
856
+ // returns on every error path), load-bearing if that ever changes.
857
+ if (
858
+ hostCtx.ownerConfig === undefined &&
859
+ hostCtx.source === undefined &&
860
+ hostCtx.reason === undefined
861
+ ) {
862
+ return {
863
+ patterns: [],
864
+ disabledReason:
865
+ 'CP11 could not determine whether a confidential-names file is configured (owner config loader failed) — failing closed',
866
+ };
867
+ }
868
+
869
+ const namesPath = resolveHostPath('confidential-names-file', '', hostCtx);
870
+
871
+ if (typeof namesPath !== 'string' || namesPath.trim() === '') {
872
+ // Nothing resolves a names file. That is either (b) — genuinely
873
+ // unconfigured — or a state in which the answer is UNKNOWABLE because the
874
+ // owner.yaml that would carry it could not be read. Unknowable is (c).
875
+ if (existsSync(resolveOwnerYamlPath())) {
876
+ if (hostCtx.reason === 'yaml-parser-missing') {
877
+ return {
878
+ patterns: [],
879
+ disabledReason:
880
+ "owner.yaml exists but 'js-yaml' is not installed, so a configured confidential-names-file cannot be resolved (run 'npm install')",
881
+ };
882
+ }
883
+ if (hostCtx.reason === 'unparseable') {
884
+ return {
885
+ patterns: [],
886
+ disabledReason:
887
+ 'owner.yaml exists but could not be parsed, so a configured confidential-names-file cannot be resolved',
888
+ };
889
+ }
890
+ if (hostCtx.droppedSections?.some((d) => d.section === 'paths')) {
891
+ // The paths: section was replaced by its default because SOME key in it
892
+ // is invalid — which says nothing yet about whether CP11 was configured.
893
+ // Re-read the RAW file for that one key (the loader does not expose it on
894
+ // this branch; see rawConfidentialNamesKeyState) and only then decide.
895
+ const rawKey = rawConfidentialNamesKeyState(resolveOwnerYamlPath());
896
+ if (rawKey === 'configured') {
897
+ return {
898
+ patterns: [],
899
+ disabledReason:
900
+ 'owner.yaml has an invalid paths: section, so a configured confidential-names-file cannot be resolved',
901
+ };
902
+ }
903
+ if (rawKey === 'unknown') {
904
+ return {
905
+ patterns: [],
906
+ disabledReason:
907
+ 'owner.yaml has an invalid paths: section and could not be re-read, so a configured confidential-names-file cannot be ruled out',
908
+ };
909
+ }
910
+ // 'absent' — CP11 was never configured here. Inactive, not disabled:
911
+ // ONE WARN naming the dropped section, no FAIL, exit unchanged.
912
+ return {
913
+ patterns: [],
914
+ inertWarn:
915
+ 'CP11 inactive — owner.yaml\'s paths: section was dropped as invalid, but it configures no confidential-names-file',
916
+ };
917
+ }
918
+ }
919
+ return { patterns: [] }; // (b) the ~99% default — inactive, silent.
920
+ }
921
+
922
+ // A names file IS configured. Since #1250 the loader REPORTS the class it
923
+ // used to collapse into `null`, so the verdict reads straight off `status` —
924
+ // no second read of the file to re-derive it. Only `[]` ('empty') is an
925
+ // operator choice (inactive, silent); 'missing'/'malformed'/'all-dropped'
926
+ // mean a configured guard cannot run, which is (c) and fails closed. The
927
+ // reasons carry no path (see above). `inspectConfidentialNames` is the
928
+ // discriminated entry point; `loadConfidentialNames` keeps its 4.0.0
929
+ // `string[] | null` shape for external deep-importers and cannot express
930
+ // this distinction.
931
+ const { status, names } = inspectConfidentialNames({ namesPath });
932
+ if (status === 'missing') {
933
+ return { patterns: [], disabledReason: 'a confidential-names-file is configured but does not exist' };
934
+ }
935
+ if (status === 'malformed') {
936
+ return {
937
+ patterns: [],
938
+ disabledReason: 'the configured confidential-names-file is unreadable or not a JSON array of names',
939
+ };
940
+ }
941
+ if (status === 'all-dropped') {
942
+ // W4 finding F3 (fail-open): the file listed entries and validation rejected
943
+ // every one of them (non-strings, blanks, over-long). That is a CORRUPTED
944
+ // list, not the `[]` opt-out — CP11 would run with zero patterns and report
945
+ // the clean verdict it never earned. Same class as 'malformed'.
946
+ return {
947
+ patterns: [],
948
+ disabledReason:
949
+ 'every entry in the configured confidential-names-file was rejected as invalid or oversized',
950
+ };
951
+ }
952
+ if (status !== 'ok') return { patterns: [] }; // 'empty' — the operator's `[]` opt-out.
953
+ return { patterns: names.map((name) => new RegExp(`\\b${escapeRegex(name)}\\b`, 'i')) };
954
+ } catch (err) {
955
+ // The helpers resolved but something below threw. CP11 cannot run — fail closed.
956
+ return { patterns: [], disabledReason: `confidential-names resolution failed (${err?.name ?? 'Error'})` };
687
957
  }
688
958
  }
689
959
 
@@ -839,6 +1109,10 @@ function isAllowlisted(relPath, line) {
839
1109
  'site/index.html',
840
1110
  'site/impressum/index.html',
841
1111
  'site/datenschutz/index.html',
1112
+ // The German landing page (2026-09-07 redesign) carries the same publisher
1113
+ // identity (JSON-LD @id + footer rel="author") as site/index.html. Exact
1114
+ // path, never a site/de/ prefix — site/guide/index.html stays outside.
1115
+ 'site/de/index.html',
842
1116
  ]);
843
1117
  const inAllowlistedFile = ALLOWLISTED_URL_PATHS.has(norm);
844
1118
 
@@ -945,7 +1219,24 @@ const scanFiles = textFiles.filter((f) => {
945
1219
  // unresolvable (standalone single-file copy) → the CP11 block below is a no-op.
946
1220
  // Awaited once, before the per-line loop, because the helpers are now dynamically
947
1221
  // imported (standalone-safe) — CP1–CP10 behaviour is unchanged.
948
- const cp11Patterns = await getConfidentialNamePatterns();
1222
+ //
1223
+ // #1244: a CP11 that was EXPECTED but could not load is a DISABLED guard, not a
1224
+ // clean scan — it is announced on stderr and counted as a FAIL so the run exits
1225
+ // non-zero. CP1–CP10 still run to completion either way: a disabled CP11 must not
1226
+ // suppress the findings the other ten rules can still make.
1227
+ const cp11 = await getConfidentialNamePatterns();
1228
+ const cp11Patterns = cp11.patterns;
1229
+ if (cp11.inertWarn) {
1230
+ console.error(`WARN: ${cp11.inertWarn}`);
1231
+ }
1232
+ if (cp11.disabledReason) {
1233
+ console.error(`CP11 DISABLED: ${cp11.disabledReason}`);
1234
+ // Shaped like the per-file FAIL lines (`<where> — <CPn label>: <content>`) so the
1235
+ // report's `— CPn` attribution parser sees CP11 here too; `<scan-wide>` stands in
1236
+ // for the file position because a disabled guard is a property of the RUN, not of
1237
+ // any one file. The reason carries no path (see getConfidentialNamePatterns).
1238
+ fail(`<scan-wide> — CP11 (guard disabled): ${cp11.disabledReason}`);
1239
+ }
949
1240
 
950
1241
  /** @type {Array<{relPath: string, lineNum: number, pattern: string, lineContent: string}>} */
951
1242
  const violations = [];
@@ -1089,7 +1380,13 @@ for (const filePath of scanFiles) {
1089
1380
  // The spec does not say to deduplicate, so keep as-is.
1090
1381
 
1091
1382
  if (violations.length === 0) {
1092
- pass(`no owner-privacy leakage found across ${scanFiles.length} scanned files`);
1383
+ // #1244: only claim the clean verdict the run actually earned. With CP11
1384
+ // disabled, ten of eleven rules ran — say that instead of "no leakage found".
1385
+ if (cp11.disabledReason) {
1386
+ console.log(` (CP1–CP10 found no leakage across ${scanFiles.length} scanned files; CP11 did not run)`);
1387
+ } else {
1388
+ pass(`no owner-privacy leakage found across ${scanFiles.length} scanned files`);
1389
+ }
1093
1390
  } else {
1094
1391
  for (const v of violations) {
1095
1392
  // Choke-point redaction (Fix 1 + Fix 2): scrub every configured confidential
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-skill-links.mjs — every relative markdown link under an instruction surface must resolve.
4
+ *
5
+ * WHY THIS EXISTS (measured 2026-09-06, session main-2026-09-06-deep-1):
6
+ * The #1157 `references/` splits moved 16 phase blocks out of three oversized skill bodies and
7
+ * verified each move with a sha256 of the moved block plus a full-reconstruction hash. That method
8
+ * proves the CONTENT is unchanged — and is blind by construction to the one defect class a move
9
+ * creates: a relative link whose correctness depends on the file's DEPTH in the tree.
10
+ *
11
+ * Three links broke and no gate saw it. The worst was
12
+ * `skills/session-end/references/phase-3-documentation-updates.md` → `./phase-3-6-tail.md`, which is
13
+ * the dispatcher for the six session-end tail phases (memory proposals, expired-learnings sweep,
14
+ * auto-dream, skill judge, auto-dialectic, reconciliation). From inside `references/` that path
15
+ * resolves one directory too deep; the target sits a level up. A coordinator following the prose
16
+ * would have found nothing there and silently skipped the tail.
17
+ *
18
+ * The existing neighbours cannot cover this: `check-skill-script-paths.mjs` scans only
19
+ * `scripts/**.mjs|.sh` and `hooks/**.sh` TARGETS, and `claude-md-drift-check` counts dead script
20
+ * citations, not intra-surface markdown links. Different predicate, different blind spot.
21
+ *
22
+ * WHAT IT CHECKS
23
+ * For every `.md` under the scanned surfaces, every inline link `[text](target)` whose target is
24
+ * relative (not http(s):, not mailto:, not `#anchor`, not an absolute path) must exist on disk,
25
+ * resolved against the LINKING FILE's own directory. A `#fragment` suffix is stripped before the
26
+ * existence check — anchors are out of scope (no heading index here); the path half is not.
27
+ *
28
+ * DELIBERATE NON-CHECKS
29
+ * - Link text, anchors, and http(s) reachability (a network check in a validator is a flake).
30
+ * - Reference-style links and bare `<...>` autolinks: not used by this repo's instruction files
31
+ * (measured: 0 occurrences). If one appears, this checker stays silent rather than guessing —
32
+ * recorded here so the gap is known rather than assumed absent.
33
+ * - Fenced code blocks AND inline-code spans are skipped: a path inside an example command or
34
+ * inside backticks is illustrative, not a link. The inline-code carve-out is not cosmetic —
35
+ * `skills/memory-cleanup/SKILL.md:163` documents the MEMORY.md index FORMAT as
36
+ * `` `- [Title](file.md) — hook` ``, and without it that literal template is the checker's
37
+ * only "finding", i.e. the guard's first act would be to demand a doc be made wrong.
38
+ *
39
+ * Exit 0 = every relative link resolves. Exit 1 = at least one does not; each is printed as
40
+ * `file:line target` so it can be fixed without a search.
41
+ */
42
+
43
+ import { readFileSync, readdirSync, statSync } from 'node:fs';
44
+ import { join, dirname, resolve, relative, sep } from 'node:path';
45
+
46
+ /**
47
+ * Surfaces whose markdown is instruction, i.e. read and acted on.
48
+ *
49
+ * `docs/` joined the list in #1258 after the annotate-before-widen step the sibling
50
+ * `check-skill-script-paths.mjs` ran for #1208: a read-only re-scan (`dirs: ['docs']`,
51
+ * 2026-09-07) returned 92 files / 111 relative links / 8 findings — six of them GitLab
52
+ * `-/issues/N` renderer links now carved out by `SKIP_TARGET`, two of them genuinely dangling
53
+ * links to PRDs that had been archived to the private Meta-Vault. Both classes were resolved
54
+ * before the widening, so the widening itself lands at 0 findings.
55
+ */
56
+ export const SCAN_DIRS = Object.freeze(['skills', 'commands', 'agents', '.claude/rules', 'docs']);
57
+
58
+ /** Path segments that end the walk: vendored or machine-owned trees, never instruction. */
59
+ export const PRUNE_DIRS = new Set(['node_modules', '.git', 'coverage', 'dist', '.pnpm']);
60
+
61
+ /**
62
+ * Targets this checker never resolves against the filesystem.
63
+ *
64
+ * The `-/issues/N` · `-/merge_requests/N` branch is a GitLab RENDERER convention, not a link
65
+ * defect: `../../../-/issues/174` is how a doc nested two levels deep points at the project's
66
+ * issue tracker, and GitLab resolves it correctly in its own Markdown view. It leaves the repo
67
+ * by construction, so a filesystem checker can only ever call it dangling. Measured 2026-09-07:
68
+ * 6 of the 8 `docs/` findings were exactly this shape, all in `docs/owner-config-schema.md`.
69
+ *
70
+ * The branch is anchored at BOTH ends. Left-unanchored it would still be a carve-out, but
71
+ * right-unanchored it swallowed every path that merely PASSES THROUGH such a segment —
72
+ * `docs/-/issues/12/../../secrets.md` is a genuinely dangling link and was SKIPped. The
73
+ * optional trailing `#note_123` is kept because GitLab issue links legitimately carry one.
74
+ */
75
+ const SKIP_TARGET = /^(https?:|mailto:|#|\/)|(^|\/)-\/(issues|merge_requests)\/\d+(#[\w-]*)?$/i;
76
+ const LINK_RE = /\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
77
+ const INLINE_CODE_RE = /(`+)[^`]*?\1/g;
78
+
79
+ /**
80
+ * Blank out inline-code spans, preserving column count so a reported line number still lines up.
81
+ * A `[x](y)` inside backticks is quoted TEXT, not a link — see DELIBERATE NON-CHECKS above.
82
+ */
83
+ function stripInlineCode(line) {
84
+ return line.replace(INLINE_CODE_RE, (m) => ' '.repeat(m.length));
85
+ }
86
+
87
+ /** Does the path exist? A stat error (ENOENT, ELOOP, EACCES) is a non-resolving link, not a crash. */
88
+ function statOk(abs) {
89
+ try { statSync(abs); return true; } catch { return false; }
90
+ }
91
+
92
+ /** Lines inside fenced code blocks — a path in an example is not a link. */
93
+ function fencedLineNumbers(text) {
94
+ const fenced = new Set();
95
+ let inFence = false;
96
+ text.split('\n').forEach((line, i) => {
97
+ if (/^\s*(```|~~~)/.test(line)) { inFence = !inFence; fenced.add(i + 1); return; }
98
+ if (inFence) fenced.add(i + 1);
99
+ });
100
+ return fenced;
101
+ }
102
+
103
+ /**
104
+ * Markdown files under the scanned surfaces, enumerated from the FILESYSTEM.
105
+ *
106
+ * Deliberately not `git ls-files`: that lists tracked files only, so a brand-new instruction file
107
+ * — the exact moment a split or a new skill lands — is invisible to the sweep until it is staged,
108
+ * and the check would report clean on the tree that carries the defect. This repo has the incident
109
+ * on record (`.claude/rules/measurement-discipline.md` § "A `git grep` drift sweep cannot see
110
+ * untracked files": a release sweep passed, then failed after the commit, from the same working
111
+ * tree with no edit in between). The cost of the filesystem walk is that a gitignored `.md` under
112
+ * these four directories would also be checked — there are none, and one would be a finding worth
113
+ * seeing anyway.
114
+ *
115
+ * @param {string} repoRoot
116
+ * @param {{dirs?: readonly string[]}} [options] `dirs` defaults to {@link SCAN_DIRS}; it exists so
117
+ * an annotate-before-widen dry-run over a candidate surface needs no re-implementation of this
118
+ * predicate (same shape as `scanSkillScriptPaths({ dirs })`).
119
+ * @returns {string[]} repo-relative paths, sorted for stable output
120
+ */
121
+ export function listMarkdown(repoRoot, { dirs = SCAN_DIRS } = {}) {
122
+ const out = [];
123
+ for (const dir of dirs) {
124
+ const abs = join(repoRoot, dir);
125
+ let entries;
126
+ try { entries = readdirSync(abs, { recursive: true, withFileTypes: true }); } catch { continue; }
127
+ for (const e of entries) {
128
+ if (!e.isFile() || !e.name.endsWith('.md')) continue;
129
+ // parentPath is absolute; make the record repo-relative with POSIX separators.
130
+ const rel = relative(repoRoot, join(e.parentPath ?? abs, e.name));
131
+ const posix = sep === '/' ? rel : rel.split(sep).join('/');
132
+ // Vendored trees are not an instruction surface: `skills/vault-sync/node_modules/` is real
133
+ // and its bundled READMEs link into their own upstream repo layout, which is not ours to fix.
134
+ if (posix.split('/').some((s) => PRUNE_DIRS.has(s))) continue;
135
+ out.push(posix);
136
+ }
137
+ }
138
+ return out.sort();
139
+ }
140
+
141
+ /**
142
+ * @param {string} [repoRoot]
143
+ * @param {{dirs?: readonly string[]}} [options] `dirs` defaults to {@link SCAN_DIRS} — see
144
+ * {@link listMarkdown}.
145
+ * @returns {{ok: boolean, checked: number, files: number, findings: Array<{file: string, line: number, target: string}>}}
146
+ */
147
+ export function checkSkillLinks(repoRoot = process.cwd(), { dirs = SCAN_DIRS } = {}) {
148
+ const findings = [];
149
+ let checked = 0;
150
+ const files = listMarkdown(repoRoot, { dirs });
151
+
152
+ for (const rel of files) {
153
+ const abs = join(repoRoot, rel);
154
+ let text;
155
+ try { text = readFileSync(abs, 'utf8'); } catch { continue; }
156
+ const fenced = fencedLineNumbers(text);
157
+ const baseDir = dirname(abs);
158
+
159
+ text.split('\n').forEach((line, idx) => {
160
+ const lineNo = idx + 1;
161
+ if (fenced.has(lineNo)) return;
162
+ for (const m of stripInlineCode(line).matchAll(LINK_RE)) {
163
+ const raw = m[1];
164
+ if (!raw || SKIP_TARGET.test(raw)) continue;
165
+ const target = raw.split('#')[0];
166
+ if (!target) continue; // pure fragment
167
+ checked += 1;
168
+ const resolved = resolve(baseDir, target);
169
+ // Never let a link escape the repo: an out-of-tree target is a finding, not a pass.
170
+ const inside = !relative(repoRoot, resolved).startsWith('..');
171
+ if (!inside || !statOk(resolved)) findings.push({ file: rel, line: lineNo, target: raw });
172
+ }
173
+ });
174
+ }
175
+
176
+ return { ok: findings.length === 0, checked, files: files.length, findings };
177
+ }
178
+
179
+ function main() {
180
+ const repoRoot = process.argv[2] ? resolve(process.argv[2]) : process.cwd();
181
+ const { ok, checked, files, findings } = checkSkillLinks(repoRoot);
182
+ if (!ok) {
183
+ for (const f of findings) {
184
+ process.stderr.write(` FAIL: ${f.file}:${f.line} → ${f.target} — relative link does not resolve from this file's directory\n`);
185
+ }
186
+ process.stderr.write(`Results: ${findings.length} unresolved relative link(s) in ${files} markdown file(s) under ${SCAN_DIRS.join(', ')}\n`);
187
+ process.exit(1);
188
+ }
189
+ // Two-space indent: validate-plugin.mjs tallies on /^ {2}(PASS|FAIL):/m.
190
+ process.stdout.write(` PASS: ${checked} relative link(s) in ${files} markdown file(s) resolve\n`);
191
+ }
192
+
193
+ if (import.meta.url === `file://${process.argv[1]}`) main();