session-orchestrator 3.16.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 (762) hide show
  1. package/.claude-plugin/marketplace.json +29 -0
  2. package/.claude-plugin/plugin.json +18 -0
  3. package/.codex-plugin/agents/explorer.toml +14 -0
  4. package/.codex-plugin/agents/session-reviewer.toml +23 -0
  5. package/.codex-plugin/agents/wave-worker.toml +15 -0
  6. package/.codex-plugin/config.toml +20 -0
  7. package/.codex-plugin/plugin.json +37 -0
  8. package/.cursor/rules/000-session-orchestrator.mdc +73 -0
  9. package/.cursor/rules/010-session-workflow.mdc +170 -0
  10. package/.cursor/rules/020-quality-gates.mdc +128 -0
  11. package/.cursor/rules/030-wave-execution.mdc +216 -0
  12. package/.cursor/rules/040-discovery.mdc +242 -0
  13. package/.cursor/rules/050-plan.mdc +235 -0
  14. package/.cursor/rules/060-evolve.mdc +232 -0
  15. package/.cursor/rules/070-gitlab-ops.mdc +246 -0
  16. package/.cursor/rules/080-ecosystem-health.mdc +145 -0
  17. package/.mcp.json +8 -0
  18. package/CHANGELOG.md +1544 -0
  19. package/LICENSE +21 -0
  20. package/NOTICE +64 -0
  21. package/README.md +242 -0
  22. package/SECURITY.md +90 -0
  23. package/agents/AGENTS.md +136 -0
  24. package/agents/analyst.md +99 -0
  25. package/agents/architect-reviewer.md +93 -0
  26. package/agents/code-implementer.md +106 -0
  27. package/agents/db-specialist.md +104 -0
  28. package/agents/dialectic-deriver.md +139 -0
  29. package/agents/docs-writer.md +113 -0
  30. package/agents/eval-judge.md +146 -0
  31. package/agents/memory-proposal-collector.md +297 -0
  32. package/agents/qa-strategist.md +102 -0
  33. package/agents/schemas/analyst.schema.json +46 -0
  34. package/agents/schemas/architect-reviewer.schema.json +50 -0
  35. package/agents/schemas/code-implementer.schema.json +61 -0
  36. package/agents/schemas/db-specialist.schema.json +80 -0
  37. package/agents/schemas/docs-writer.schema.json +56 -0
  38. package/agents/schemas/persona-panel-sidecar.schema.json +245 -0
  39. package/agents/schemas/qa-strategist.schema.json +46 -0
  40. package/agents/schemas/security-reviewer.schema.json +86 -0
  41. package/agents/schemas/session-reviewer.schema.json +69 -0
  42. package/agents/schemas/test-writer.schema.json +69 -0
  43. package/agents/schemas/ui-developer.schema.json +90 -0
  44. package/agents/schemas/ux-evaluator.schema.json +51 -0
  45. package/agents/security-reviewer.md +236 -0
  46. package/agents/session-reviewer.md +201 -0
  47. package/agents/skill-applied-judge.md +122 -0
  48. package/agents/test-writer.md +123 -0
  49. package/agents/ui-developer.md +109 -0
  50. package/agents/ux-evaluator.md +161 -0
  51. package/assets/icon.svg +11 -0
  52. package/assets/og-card.png +0 -0
  53. package/assets/og-card.svg +47 -0
  54. package/commands/autopilot-multi.md +74 -0
  55. package/commands/autopilot.md +80 -0
  56. package/commands/bootstrap.md +56 -0
  57. package/commands/brainstorm.md +48 -0
  58. package/commands/close.md +24 -0
  59. package/commands/debug.md +36 -0
  60. package/commands/discovery.md +32 -0
  61. package/commands/dispatcher.md +59 -0
  62. package/commands/eval.md +28 -0
  63. package/commands/evolve.md +10 -0
  64. package/commands/go.md +41 -0
  65. package/commands/grill.md +45 -0
  66. package/commands/harness-audit.md +26 -0
  67. package/commands/memory-cleanup.md +25 -0
  68. package/commands/persona-panel.md +121 -0
  69. package/commands/plan.md +15 -0
  70. package/commands/portfolio.md +97 -0
  71. package/commands/reconcile.md +23 -0
  72. package/commands/repo-audit.md +24 -0
  73. package/commands/session.md +30 -0
  74. package/commands/spinout.md +15 -0
  75. package/commands/sunset-review.md +27 -0
  76. package/commands/templates-ack.md +96 -0
  77. package/commands/test.md +97 -0
  78. package/docs/README.md +105 -0
  79. package/docs/USER-GUIDE.md +1403 -0
  80. package/docs/ci-setup.md +81 -0
  81. package/docs/codex-setup.md +142 -0
  82. package/docs/components.md +74 -0
  83. package/docs/cursor-setup.md +104 -0
  84. package/docs/events-schema.md +81 -0
  85. package/docs/migration-v3.md +148 -0
  86. package/docs/owner-config-schema.md +154 -0
  87. package/docs/persona-panel.md +433 -0
  88. package/docs/pi-setup.md +115 -0
  89. package/docs/plugin-architecture-v3.md +296 -0
  90. package/docs/pm-skills-marketplace.md +114 -0
  91. package/docs/policy-cache-validation-2026-04-28.md +118 -0
  92. package/docs/rule-authoring.md +316 -0
  93. package/docs/session-config-reference.md +1439 -0
  94. package/docs/session-config-template.md +961 -0
  95. package/docs/vault-docs-architecture.md +297 -0
  96. package/hooks/_lib/lock-bootstrap.mjs +272 -0
  97. package/hooks/_lib/lock-reconcile.mjs +93 -0
  98. package/hooks/_lib/profile-gate.mjs +95 -0
  99. package/hooks/_lib/transcript-history.mjs +211 -0
  100. package/hooks/agent-teams-h3-test.sh +362 -0
  101. package/hooks/config-protection.mjs +0 -0
  102. package/hooks/cwd-change-restore.mjs +131 -0
  103. package/hooks/enforce-commands.mjs +179 -0
  104. package/hooks/enforce-scope.mjs +273 -0
  105. package/hooks/hooks-codex.json +60 -0
  106. package/hooks/hooks-cursor.json +15 -0
  107. package/hooks/hooks-pi.json +115 -0
  108. package/hooks/hooks.json +215 -0
  109. package/hooks/loop-guard.mjs +260 -0
  110. package/hooks/on-session-end.mjs +217 -0
  111. package/hooks/on-session-start.mjs +660 -0
  112. package/hooks/on-stop.mjs +294 -0
  113. package/hooks/operator-steer.mjs +64 -0
  114. package/hooks/post-edit-validate.mjs +225 -0
  115. package/hooks/post-subagent-discovery-validator.mjs +398 -0
  116. package/hooks/post-tool-batch-wave-signal.mjs +328 -0
  117. package/hooks/post-tool-failure-corrective-context.mjs +248 -0
  118. package/hooks/post-tooluse-frontend-slop.mjs +184 -0
  119. package/hooks/pre-bash-destructive-guard.mjs +515 -0
  120. package/hooks/pre-bash-memory-propose-audit.mjs +206 -0
  121. package/hooks/pre-bash-staging-fence.mjs +223 -0
  122. package/hooks/pre-bash-templates-first.mjs +404 -0
  123. package/hooks/run-node.sh +72 -0
  124. package/hooks/skill-invocation-telemetry.mjs +99 -0
  125. package/hooks/subagent-telemetry.mjs +249 -0
  126. package/hooks/wave-scope-commit-guard.mjs +191 -0
  127. package/monitors/monitors.json +14 -0
  128. package/output-styles/finding-report.md +48 -0
  129. package/output-styles/session-report.md +53 -0
  130. package/output-styles/wave-summary.md +38 -0
  131. package/package.json +94 -0
  132. package/pi/extensions/session-orchestrator.ts +25 -0
  133. package/pi/prompts/autopilot-multi.md +12 -0
  134. package/pi/prompts/autopilot.md +12 -0
  135. package/pi/prompts/bootstrap.md +12 -0
  136. package/pi/prompts/brainstorm.md +12 -0
  137. package/pi/prompts/close.md +11 -0
  138. package/pi/prompts/debug.md +12 -0
  139. package/pi/prompts/discovery.md +12 -0
  140. package/pi/prompts/dispatcher.md +12 -0
  141. package/pi/prompts/eval.md +12 -0
  142. package/pi/prompts/evolve.md +12 -0
  143. package/pi/prompts/go.md +12 -0
  144. package/pi/prompts/grill.md +12 -0
  145. package/pi/prompts/harness-audit.md +12 -0
  146. package/pi/prompts/memory-cleanup.md +12 -0
  147. package/pi/prompts/persona-panel.md +12 -0
  148. package/pi/prompts/plan.md +12 -0
  149. package/pi/prompts/portfolio.md +12 -0
  150. package/pi/prompts/reconcile.md +12 -0
  151. package/pi/prompts/repo-audit.md +12 -0
  152. package/pi/prompts/session.md +12 -0
  153. package/pi/prompts/spinout.md +12 -0
  154. package/pi/prompts/sunset-review.md +12 -0
  155. package/pi/prompts/templates-ack.md +12 -0
  156. package/pi/prompts/test.md +12 -0
  157. package/rules/_index.md +51 -0
  158. package/rules/always-on/commit-discipline.md +26 -0
  159. package/rules/always-on/npm-quality-gates.md +26 -0
  160. package/rules/always-on/parallel-sessions.md +43 -0
  161. package/rules/opt-in-domain/prompt-caching.md +270 -0
  162. package/rules/opt-in-stack/backend-data.md +188 -0
  163. package/rules/opt-in-stack/backend.md +390 -0
  164. package/rules/opt-in-stack/frontend.md +98 -0
  165. package/rules/opt-in-stack/security-web.md +194 -0
  166. package/rules/opt-in-stack/swift.md +65 -0
  167. package/scripts/archive-closed-prds.mjs +416 -0
  168. package/scripts/autopilot-multi.mjs +802 -0
  169. package/scripts/autopilot.mjs +383 -0
  170. package/scripts/backfill-abandoned-sessions.mjs +265 -0
  171. package/scripts/backfill-learnings-expires.mjs +196 -0
  172. package/scripts/backfill-learnings.mjs +203 -0
  173. package/scripts/backfill-sessions.mjs +282 -0
  174. package/scripts/check-doc-consistency.sh +279 -0
  175. package/scripts/check-package-manager.mjs +445 -0
  176. package/scripts/ci/assert-vitest-green.mjs +267 -0
  177. package/scripts/codex-install.mjs +435 -0
  178. package/scripts/compute-grounding-injection.sh +186 -0
  179. package/scripts/cursor-install.mjs +113 -0
  180. package/scripts/dialectic-deriver.mjs +573 -0
  181. package/scripts/emit-event.mjs +160 -0
  182. package/scripts/emit-session.mjs +212 -0
  183. package/scripts/eval-session.mjs +262 -0
  184. package/scripts/export-hw-learnings.mjs +437 -0
  185. package/scripts/gc-stale-worktrees.mjs +666 -0
  186. package/scripts/generate-pi-prompts.mjs +127 -0
  187. package/scripts/harness-audit.mjs +287 -0
  188. package/scripts/lib/agent-frontmatter.mjs +266 -0
  189. package/scripts/lib/agent-output-schema.mjs +166 -0
  190. package/scripts/lib/agent-status.mjs +303 -0
  191. package/scripts/lib/ajv-loader.mjs +34 -0
  192. package/scripts/lib/auto-dialectic.mjs +382 -0
  193. package/scripts/lib/auto-dream.mjs +471 -0
  194. package/scripts/lib/autonomy/suitability.mjs +212 -0
  195. package/scripts/lib/autopilot/dep-graph.mjs +417 -0
  196. package/scripts/lib/autopilot/durable-telemetry.mjs +121 -0
  197. package/scripts/lib/autopilot/flags.mjs +104 -0
  198. package/scripts/lib/autopilot/kill-switches.mjs +174 -0
  199. package/scripts/lib/autopilot/loop.mjs +320 -0
  200. package/scripts/lib/autopilot/mr-draft.mjs +520 -0
  201. package/scripts/lib/autopilot/multi-killswitch.mjs +184 -0
  202. package/scripts/lib/autopilot/recent-runs.mjs +106 -0
  203. package/scripts/lib/autopilot/stall-sampler.mjs +97 -0
  204. package/scripts/lib/autopilot/telemetry.mjs +224 -0
  205. package/scripts/lib/autopilot/worktree-pipeline.mjs +605 -0
  206. package/scripts/lib/autopilot-telemetry.mjs +11 -0
  207. package/scripts/lib/autopilot.mjs +39 -0
  208. package/scripts/lib/backlog-scan.mjs +179 -0
  209. package/scripts/lib/bootstrap-lock-freshness.mjs +260 -0
  210. package/scripts/lib/bootstrap-lock-refresh.mjs +186 -0
  211. package/scripts/lib/build-live-signals.mjs +150 -0
  212. package/scripts/lib/ci-status-banner.mjs +425 -0
  213. package/scripts/lib/claude-md-budget-lint.mjs +246 -0
  214. package/scripts/lib/cli-flags.mjs +158 -0
  215. package/scripts/lib/codex/plugin-contract.mjs +610 -0
  216. package/scripts/lib/cold-start-detector.mjs +240 -0
  217. package/scripts/lib/command-blocker.mjs +458 -0
  218. package/scripts/lib/common.mjs +333 -0
  219. package/scripts/lib/config/auto-dream.mjs +77 -0
  220. package/scripts/lib/config/block-header.mjs +94 -0
  221. package/scripts/lib/config/broken-window.mjs +114 -0
  222. package/scripts/lib/config/coercers.mjs +248 -0
  223. package/scripts/lib/config/cold-start.mjs +92 -0
  224. package/scripts/lib/config/config-protection.mjs +120 -0
  225. package/scripts/lib/config/cross-repo.mjs +104 -0
  226. package/scripts/lib/config/custom-phases.mjs +213 -0
  227. package/scripts/lib/config/dialectic.mjs +92 -0
  228. package/scripts/lib/config/discovery-validator.mjs +75 -0
  229. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +240 -0
  230. package/scripts/lib/config/dispatcher-autonomy.mjs +152 -0
  231. package/scripts/lib/config/docs-orchestrator.mjs +90 -0
  232. package/scripts/lib/config/docs-staleness.mjs +96 -0
  233. package/scripts/lib/config/drift-check.mjs +155 -0
  234. package/scripts/lib/config/eval.mjs +130 -0
  235. package/scripts/lib/config/events-rotation.mjs +74 -0
  236. package/scripts/lib/config/evolve.mjs +308 -0
  237. package/scripts/lib/config/frontend-slop-hook.mjs +104 -0
  238. package/scripts/lib/config/gitlab-portfolio.mjs +150 -0
  239. package/scripts/lib/config/handover-gate.mjs +106 -0
  240. package/scripts/lib/config/host-paths.mjs +76 -0
  241. package/scripts/lib/config/io.mjs +54 -0
  242. package/scripts/lib/config/loop-guard.mjs +117 -0
  243. package/scripts/lib/config/memory.mjs +150 -0
  244. package/scripts/lib/config/persona-gate-wave.mjs +258 -0
  245. package/scripts/lib/config/reconcile.mjs +205 -0
  246. package/scripts/lib/config/section-extractor.mjs +100 -0
  247. package/scripts/lib/config/skill-evolution.mjs +112 -0
  248. package/scripts/lib/config/slopcheck.mjs +99 -0
  249. package/scripts/lib/config/state-md-lock.mjs +83 -0
  250. package/scripts/lib/config/templates-first.mjs +94 -0
  251. package/scripts/lib/config/test.mjs +113 -0
  252. package/scripts/lib/config/vault-integration.mjs +201 -0
  253. package/scripts/lib/config/vault-mirror-quality.mjs +99 -0
  254. package/scripts/lib/config/vault-staleness.mjs +84 -0
  255. package/scripts/lib/config/vault-sync.mjs +96 -0
  256. package/scripts/lib/config/verification-auto-fix.mjs +84 -0
  257. package/scripts/lib/config/wave-reviewers.mjs +133 -0
  258. package/scripts/lib/config-schema.mjs +345 -0
  259. package/scripts/lib/config.mjs +474 -0
  260. package/scripts/lib/convergence-monitor.mjs +389 -0
  261. package/scripts/lib/coordinator-snapshot.mjs +371 -0
  262. package/scripts/lib/crypto-digest-utils.mjs +91 -0
  263. package/scripts/lib/discovery/helpers.mjs +127 -0
  264. package/scripts/lib/discovery/triage-state.mjs +279 -0
  265. package/scripts/lib/dispatcher/cli.mjs +257 -0
  266. package/scripts/lib/dispatcher/enumerate.mjs +243 -0
  267. package/scripts/lib/dispatcher/rank.mjs +363 -0
  268. package/scripts/lib/ecosystem-health.mjs +224 -0
  269. package/scripts/lib/ecosystem-wizard/ci-detector.mjs +18 -0
  270. package/scripts/lib/ecosystem-wizard/config-parser.mjs +54 -0
  271. package/scripts/lib/ecosystem-wizard/config-writer.mjs +287 -0
  272. package/scripts/lib/ecosystem-wizard/package-manager-detector.mjs +42 -0
  273. package/scripts/lib/ecosystem-wizard/wizard-prompt.mjs +246 -0
  274. package/scripts/lib/ecosystem-wizard.mjs +48 -0
  275. package/scripts/lib/env-check.mjs +89 -0
  276. package/scripts/lib/eval/engine.mjs +605 -0
  277. package/scripts/lib/eval/judge.mjs +433 -0
  278. package/scripts/lib/eval/report.mjs +367 -0
  279. package/scripts/lib/eval/schema.mjs +618 -0
  280. package/scripts/lib/eval/session-resolve.mjs +137 -0
  281. package/scripts/lib/eval/sink.mjs +77 -0
  282. package/scripts/lib/events-rotation.mjs +86 -0
  283. package/scripts/lib/events-schema.mjs +81 -0
  284. package/scripts/lib/events.mjs +80 -0
  285. package/scripts/lib/evolve/autonomy-verdict.mjs +461 -0
  286. package/scripts/lib/evolve/autopilot-effectiveness.mjs +293 -0
  287. package/scripts/lib/exclusivity-matrix.mjs +68 -0
  288. package/scripts/lib/fetch-baseline.mjs +311 -0
  289. package/scripts/lib/file-lock.mjs +512 -0
  290. package/scripts/lib/frontend-detect/detect.mjs +138 -0
  291. package/scripts/lib/frontend-detect/rules.mjs +295 -0
  292. package/scripts/lib/frontmatter-guard.mjs +241 -0
  293. package/scripts/lib/gates/echo-stub-detect.mjs +39 -0
  294. package/scripts/lib/gates/gate-baseline.mjs +42 -0
  295. package/scripts/lib/gates/gate-full.mjs +85 -0
  296. package/scripts/lib/gates/gate-helpers.mjs +231 -0
  297. package/scripts/lib/gates/gate-incremental.mjs +76 -0
  298. package/scripts/lib/gates/gate-per-file.mjs +55 -0
  299. package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +447 -0
  300. package/scripts/lib/gitlab-portfolio/aggregator.mjs +383 -0
  301. package/scripts/lib/gitlab-portfolio/cli.mjs +428 -0
  302. package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +289 -0
  303. package/scripts/lib/gitlab-portfolio/vcs-detect.mjs +182 -0
  304. package/scripts/lib/handover-gate.mjs +222 -0
  305. package/scripts/lib/hardening.mjs +43 -0
  306. package/scripts/lib/hardware-pattern-detector.mjs +238 -0
  307. package/scripts/lib/harness-audit/categories/category1.mjs +123 -0
  308. package/scripts/lib/harness-audit/categories/category2.mjs +145 -0
  309. package/scripts/lib/harness-audit/categories/category3.mjs +143 -0
  310. package/scripts/lib/harness-audit/categories/category4.mjs +202 -0
  311. package/scripts/lib/harness-audit/categories/category5.mjs +152 -0
  312. package/scripts/lib/harness-audit/categories/category6.mjs +211 -0
  313. package/scripts/lib/harness-audit/categories/category7.mjs +125 -0
  314. package/scripts/lib/harness-audit/categories/category8.mjs +328 -0
  315. package/scripts/lib/harness-audit/categories/category9.mjs +294 -0
  316. package/scripts/lib/harness-audit/categories/helpers.mjs +165 -0
  317. package/scripts/lib/harness-audit/categories.mjs +19 -0
  318. package/scripts/lib/historical-guard.mjs +15 -0
  319. package/scripts/lib/host-identity.mjs +262 -0
  320. package/scripts/lib/instruction-budget-guard.mjs +332 -0
  321. package/scripts/lib/io.mjs +304 -0
  322. package/scripts/lib/issue-close-strip-labels.mjs +161 -0
  323. package/scripts/lib/language-mappers/README.md +57 -0
  324. package/scripts/lib/language-mappers/index.mjs +165 -0
  325. package/scripts/lib/language-mappers/markdown.mjs +149 -0
  326. package/scripts/lib/language-mappers/python.mjs +249 -0
  327. package/scripts/lib/language-mappers/swift.mjs +201 -0
  328. package/scripts/lib/language-mappers/typescript.mjs +433 -0
  329. package/scripts/lib/learnings/expiry-sweep.mjs +164 -0
  330. package/scripts/lib/learnings/filters.mjs +43 -0
  331. package/scripts/lib/learnings/io.mjs +255 -0
  332. package/scripts/lib/learnings/schema.mjs +518 -0
  333. package/scripts/lib/learnings/surface.mjs +207 -0
  334. package/scripts/lib/learnings.mjs +42 -0
  335. package/scripts/lib/lock-reaper.mjs +648 -0
  336. package/scripts/lib/locks/index.mjs +31 -0
  337. package/scripts/lib/locks/lock-body.mjs +62 -0
  338. package/scripts/lib/locks/staging-fence-lock.mjs +267 -0
  339. package/scripts/lib/locks/state-md-lock.mjs +351 -0
  340. package/scripts/lib/loop-readiness-banner.mjs +144 -0
  341. package/scripts/lib/memory-banner.mjs +478 -0
  342. package/scripts/lib/memory-cleanup/worktree-sweep.mjs +108 -0
  343. package/scripts/lib/memory-cleanup-stamp.mjs +56 -0
  344. package/scripts/lib/memory-paths.mjs +31 -0
  345. package/scripts/lib/memory-proposals/collector.mjs +334 -0
  346. package/scripts/lib/memory-proposals/schema.mjs +289 -0
  347. package/scripts/lib/memory-proposals/sink.mjs +507 -0
  348. package/scripts/lib/memory-proposals/store.mjs +441 -0
  349. package/scripts/lib/mission-status-schema.mjs +114 -0
  350. package/scripts/lib/mode-selector/alternatives.mjs +64 -0
  351. package/scripts/lib/mode-selector/constants.mjs +29 -0
  352. package/scripts/lib/mode-selector/context-pressure.mjs +157 -0
  353. package/scripts/lib/mode-selector/rationale.mjs +55 -0
  354. package/scripts/lib/mode-selector/scoring.mjs +221 -0
  355. package/scripts/lib/mode-selector-accuracy.mjs +121 -0
  356. package/scripts/lib/mode-selector.mjs +160 -0
  357. package/scripts/lib/multi-provider-build/providers.mjs +64 -0
  358. package/scripts/lib/multi-provider-build/templating.mjs +130 -0
  359. package/scripts/lib/named-baseline-resolver.mjs +233 -0
  360. package/scripts/lib/named-vault-resolver.mjs +433 -0
  361. package/scripts/lib/owner-config/coerce.mjs +29 -0
  362. package/scripts/lib/owner-config/constants.mjs +21 -0
  363. package/scripts/lib/owner-config/defaults.mjs +50 -0
  364. package/scripts/lib/owner-config/error.mjs +19 -0
  365. package/scripts/lib/owner-config/index.mjs +13 -0
  366. package/scripts/lib/owner-config/merge.mjs +52 -0
  367. package/scripts/lib/owner-config/validate.mjs +259 -0
  368. package/scripts/lib/owner-config-banner.mjs +126 -0
  369. package/scripts/lib/owner-config-loader.mjs +159 -0
  370. package/scripts/lib/owner-config.example.yaml +72 -0
  371. package/scripts/lib/owner-config.mjs +28 -0
  372. package/scripts/lib/owner-interview.mjs +243 -0
  373. package/scripts/lib/owner-yaml.mjs +571 -0
  374. package/scripts/lib/package-manager.mjs +160 -0
  375. package/scripts/lib/path-utils.mjs +217 -0
  376. package/scripts/lib/peer-cards/merger.mjs +310 -0
  377. package/scripts/lib/peer-cards/reader.mjs +125 -0
  378. package/scripts/lib/peer-cards/schema.mjs +230 -0
  379. package/scripts/lib/peer-cards/staleness-banner.mjs +86 -0
  380. package/scripts/lib/peer-cards/writer.mjs +138 -0
  381. package/scripts/lib/peer-discovery.mjs +200 -0
  382. package/scripts/lib/persona-panel/catalog-loader.mjs +577 -0
  383. package/scripts/lib/persona-panel/consolidator.mjs +370 -0
  384. package/scripts/lib/persona-panel/persona-runner.mjs +375 -0
  385. package/scripts/lib/persona-panel/threshold.mjs +130 -0
  386. package/scripts/lib/pi-hook-bridge.mjs +328 -0
  387. package/scripts/lib/platform.mjs +266 -0
  388. package/scripts/lib/playwright-driver/runner.mjs +297 -0
  389. package/scripts/lib/plugin-root.mjs +210 -0
  390. package/scripts/lib/pre-dispatch-check.mjs +126 -0
  391. package/scripts/lib/product-repo-detect.mjs +121 -0
  392. package/scripts/lib/profiles/registry.mjs +176 -0
  393. package/scripts/lib/profiles/schema.mjs +209 -0
  394. package/scripts/lib/qg-command-drift-banner.mjs +88 -0
  395. package/scripts/lib/quality-gate/diagnostics.mjs +92 -0
  396. package/scripts/lib/quality-gate.mjs +536 -0
  397. package/scripts/lib/quality-gates-cache.mjs +228 -0
  398. package/scripts/lib/quality-gates-policy.mjs +95 -0
  399. package/scripts/lib/recommendations-v0.mjs +156 -0
  400. package/scripts/lib/reconcile/eligibility.mjs +203 -0
  401. package/scripts/lib/reconcile/emitter.mjs +244 -0
  402. package/scripts/lib/reconcile/engine.mjs +412 -0
  403. package/scripts/lib/reconcile/idempotency.mjs +239 -0
  404. package/scripts/lib/reconcile/renderer.mjs +211 -0
  405. package/scripts/lib/reconcile/writer.mjs +293 -0
  406. package/scripts/lib/reconcile-nudge-banner.mjs +284 -0
  407. package/scripts/lib/resource-probe/evaluate.mjs +190 -0
  408. package/scripts/lib/resource-probe/parsers.mjs +181 -0
  409. package/scripts/lib/resource-probe/probe-platform.mjs +300 -0
  410. package/scripts/lib/resource-probe.mjs +95 -0
  411. package/scripts/lib/rule-loader.mjs +552 -0
  412. package/scripts/lib/rules-sync.mjs +439 -0
  413. package/scripts/lib/scope-gate.mjs +496 -0
  414. package/scripts/lib/session-close-backfill.mjs +539 -0
  415. package/scripts/lib/session-discovery.mjs +256 -0
  416. package/scripts/lib/session-end/phase-skip.mjs +357 -0
  417. package/scripts/lib/session-end/worktree-cleanup.mjs +112 -0
  418. package/scripts/lib/session-id.mjs +362 -0
  419. package/scripts/lib/session-lock.mjs +703 -0
  420. package/scripts/lib/session-registry.mjs +355 -0
  421. package/scripts/lib/session-schema/aliases.mjs +71 -0
  422. package/scripts/lib/session-schema/constants.mjs +115 -0
  423. package/scripts/lib/session-schema/normalizer.mjs +66 -0
  424. package/scripts/lib/session-schema/timestamps.mjs +64 -0
  425. package/scripts/lib/session-schema/validator.mjs +453 -0
  426. package/scripts/lib/session-schema.mjs +71 -0
  427. package/scripts/lib/session-token-rollup.mjs +137 -0
  428. package/scripts/lib/sessions-staleness-banner.mjs +247 -0
  429. package/scripts/lib/skill-evolution/blast-radius-classifier.mjs +114 -0
  430. package/scripts/lib/skill-evolution/candidate-intake.mjs +270 -0
  431. package/scripts/lib/skill-evolution/config-validation-gate.mjs +279 -0
  432. package/scripts/lib/skill-evolution/engine.mjs +719 -0
  433. package/scripts/lib/skill-evolution/idempotency.mjs +279 -0
  434. package/scripts/lib/skill-evolution/mr-opener.mjs +507 -0
  435. package/scripts/lib/skill-health/join.mjs +181 -0
  436. package/scripts/lib/skill-health/score.mjs +123 -0
  437. package/scripts/lib/skill-invocations-schema.mjs +214 -0
  438. package/scripts/lib/skill-judge.mjs +348 -0
  439. package/scripts/lib/skill-judgments-schema.mjs +264 -0
  440. package/scripts/lib/slopcheck.mjs +501 -0
  441. package/scripts/lib/soul-resolve.mjs +118 -0
  442. package/scripts/lib/spiral-carryover.mjs +495 -0
  443. package/scripts/lib/state-md/body-sections.mjs +851 -0
  444. package/scripts/lib/state-md/frontmatter-mutators.mjs +453 -0
  445. package/scripts/lib/state-md/mission-status.mjs +247 -0
  446. package/scripts/lib/state-md/recommendations.mjs +57 -0
  447. package/scripts/lib/state-md/yaml-parser.mjs +234 -0
  448. package/scripts/lib/state-md-peer-guard.mjs +232 -0
  449. package/scripts/lib/state-md.mjs +53 -0
  450. package/scripts/lib/subagents-schema.mjs +309 -0
  451. package/scripts/lib/sunset/walker.mjs +1192 -0
  452. package/scripts/lib/test-runner/artifact-paths.mjs +94 -0
  453. package/scripts/lib/test-runner/fingerprint.mjs +33 -0
  454. package/scripts/lib/test-runner/issue-reconcile.mjs +770 -0
  455. package/scripts/lib/tmux-layout/layouts.mjs +224 -0
  456. package/scripts/lib/tmux-layout/telemetry-stats.mjs +100 -0
  457. package/scripts/lib/tmux-layout/telemetry.mjs +88 -0
  458. package/scripts/lib/tmux-layout/tmux-shell.mjs +82 -0
  459. package/scripts/lib/tmux-layout/vcs-detector.mjs +88 -0
  460. package/scripts/lib/validate/check-agents.mjs +457 -0
  461. package/scripts/lib/validate/check-codex-plugin.mjs +37 -0
  462. package/scripts/lib/validate/check-commands.mjs +148 -0
  463. package/scripts/lib/validate/check-component-paths.mjs +112 -0
  464. package/scripts/lib/validate/check-dead-bridge.mjs +180 -0
  465. package/scripts/lib/validate/check-hooks-symmetry.mjs +258 -0
  466. package/scripts/lib/validate/check-json-files.mjs +116 -0
  467. package/scripts/lib/validate/check-owner-leakage.mjs +1011 -0
  468. package/scripts/lib/validate/check-path-utils-canary.mjs +175 -0
  469. package/scripts/lib/validate/check-peekaboo-driver-canary.mjs +201 -0
  470. package/scripts/lib/validate/check-pi-package.mjs +110 -0
  471. package/scripts/lib/validate/check-pi-prompts.mjs +43 -0
  472. package/scripts/lib/validate/check-playwright-mcp-canary.mjs +154 -0
  473. package/scripts/lib/validate/check-plugin-json.mjs +96 -0
  474. package/scripts/lib/validate/check-plugin-monitors.mjs +206 -0
  475. package/scripts/lib/validate/check-plugin-schema.mjs +137 -0
  476. package/scripts/lib/validate/check-rules.mjs +143 -0
  477. package/scripts/lib/validate/check-session-plan-routing.mjs +154 -0
  478. package/scripts/lib/validate/check-test-fixture-shapes.mjs +280 -0
  479. package/scripts/lib/validate/check-unicode-safety.mjs +533 -0
  480. package/scripts/lib/validate/confidential-names.mjs +169 -0
  481. package/scripts/lib/validate/dead-bridge-corpus.mjs +141 -0
  482. package/scripts/lib/validate/dead-bridge-detectors.mjs +568 -0
  483. package/scripts/lib/validate/tier-inference.mjs +100 -0
  484. package/scripts/lib/validate-vendored-rules.mjs +519 -0
  485. package/scripts/lib/vault-archive.mjs +404 -0
  486. package/scripts/lib/vault-backfill/glab.mjs +164 -0
  487. package/scripts/lib/vault-backfill/manifest.mjs +75 -0
  488. package/scripts/lib/vault-backfill/template.mjs +130 -0
  489. package/scripts/lib/vault-consolidate-fs.mjs +331 -0
  490. package/scripts/lib/vault-migration-rules.mjs +155 -0
  491. package/scripts/lib/vault-mirror/auto-commit.mjs +203 -0
  492. package/scripts/lib/vault-mirror/namespace.mjs +152 -0
  493. package/scripts/lib/vault-mirror/process.mjs +567 -0
  494. package/scripts/lib/vault-mirror/pseudonym-map.mjs +164 -0
  495. package/scripts/lib/vault-mirror/render-learnings.mjs +201 -0
  496. package/scripts/lib/vault-mirror/render-sessions.mjs +367 -0
  497. package/scripts/lib/vault-mirror/render.mjs +8 -0
  498. package/scripts/lib/vault-mirror/utils.mjs +217 -0
  499. package/scripts/lib/vault-relocation-rules.mjs +555 -0
  500. package/scripts/lib/vault-repo-backfill.mjs +235 -0
  501. package/scripts/lib/vault-staleness-banner.mjs +142 -0
  502. package/scripts/lib/vault-status/board-writer.mjs +769 -0
  503. package/scripts/lib/vault-status/narrative-mirror.mjs +544 -0
  504. package/scripts/lib/vault-sync-baseline.mjs +152 -0
  505. package/scripts/lib/wave-context.mjs +29 -0
  506. package/scripts/lib/wave-executor/pool.mjs +248 -0
  507. package/scripts/lib/wave-resource-gate.mjs +204 -0
  508. package/scripts/lib/wave-sizing.mjs +75 -0
  509. package/scripts/lib/webhook-url.mjs +105 -0
  510. package/scripts/lib/workspace.mjs +198 -0
  511. package/scripts/lib/worktree/constants.mjs +35 -0
  512. package/scripts/lib/worktree/index.mjs +17 -0
  513. package/scripts/lib/worktree/lifecycle.mjs +287 -0
  514. package/scripts/lib/worktree/listing.mjs +118 -0
  515. package/scripts/lib/worktree/meta.mjs +64 -0
  516. package/scripts/lib/worktree-freshness.mjs +313 -0
  517. package/scripts/lib/worktree.mjs +15 -0
  518. package/scripts/lifecycle-sim-v6.mjs +347 -0
  519. package/scripts/lock-reaper.mjs +185 -0
  520. package/scripts/mcp-server.sh +241 -0
  521. package/scripts/measure-policy-cache-effectiveness.mjs +427 -0
  522. package/scripts/memory-propose.mjs +464 -0
  523. package/scripts/migrate-cold-start-seed.mjs +404 -0
  524. package/scripts/migrate-learnings-jsonl.mjs +189 -0
  525. package/scripts/migrate-legacy-learnings.sh +61 -0
  526. package/scripts/migrate-sessions-jsonl.mjs +448 -0
  527. package/scripts/migrate-subagents-jsonl.mjs +196 -0
  528. package/scripts/migrate-vault-paths.mjs +796 -0
  529. package/scripts/parse-config.mjs +149 -0
  530. package/scripts/pi-install.mjs +117 -0
  531. package/scripts/print-applicable-rules.mjs +247 -0
  532. package/scripts/promote-vault-strict.mjs +496 -0
  533. package/scripts/relocate-vault-corpus.mjs +1178 -0
  534. package/scripts/run-migrate-v2-cross-repo.mjs +385 -0
  535. package/scripts/run-quality-gate.mjs +216 -0
  536. package/scripts/spikes/h3-agent-teams/preflight.sh +53 -0
  537. package/scripts/spikes/h3-agent-teams/run-h3.sh +112 -0
  538. package/scripts/spikes/h3-agent-teams/setup.sh +137 -0
  539. package/scripts/spikes/h3-agent-teams/toggle.sh +38 -0
  540. package/scripts/sweep-expired-learnings.mjs +135 -0
  541. package/scripts/sync-vault-schema.mjs +376 -0
  542. package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +8 -0
  543. package/scripts/tmux-layout.mjs +245 -0
  544. package/scripts/token-audit.sh +191 -0
  545. package/scripts/typecheck.mjs +42 -0
  546. package/scripts/upload-social-preview.mjs +316 -0
  547. package/scripts/validate-config.mjs +46 -0
  548. package/scripts/validate-plugin-manifests.mjs +163 -0
  549. package/scripts/validate-plugin.mjs +264 -0
  550. package/scripts/validate-wave-scope.mjs +289 -0
  551. package/scripts/vault-backfill.mjs +404 -0
  552. package/scripts/vault-consolidate.mjs +596 -0
  553. package/scripts/vault-integration-watcher.mjs +394 -0
  554. package/scripts/vault-mirror.mjs +430 -0
  555. package/skills/_shared/bootstrap-gate.md +111 -0
  556. package/skills/_shared/config-reading.md +226 -0
  557. package/skills/_shared/instruction-file-resolution.md +79 -0
  558. package/skills/_shared/model-selection.md +64 -0
  559. package/skills/_shared/monitor-patterns.md +300 -0
  560. package/skills/_shared/parallel-aware-auq.md +121 -0
  561. package/skills/_shared/parallel-aware-preamble.md +185 -0
  562. package/skills/_shared/platform-tools.md +96 -0
  563. package/skills/_shared/state-ownership.md +221 -0
  564. package/skills/architecture/DEEPENING.md +37 -0
  565. package/skills/architecture/INTERFACE-DESIGN.md +44 -0
  566. package/skills/architecture/LANGUAGE.md +53 -0
  567. package/skills/architecture/SKILL.md +92 -0
  568. package/skills/autopilot/SKILL.md +419 -0
  569. package/skills/bootstrap/SKILL.md +592 -0
  570. package/skills/bootstrap/STATE.md.template +24 -0
  571. package/skills/bootstrap/_shared-template.md +243 -0
  572. package/skills/bootstrap/deep-template.md +659 -0
  573. package/skills/bootstrap/fast-template.md +251 -0
  574. package/skills/bootstrap/intensity-heuristic.md +80 -0
  575. package/skills/bootstrap/public-fallback.md +342 -0
  576. package/skills/bootstrap/standard-template.md +736 -0
  577. package/skills/bootstrap/templates/agents/project-code-review.md +18 -0
  578. package/skills/bootstrap/templates/agents/project-discovery.md +18 -0
  579. package/skills/bootstrap/templates/agents/project-quality-gate.md +18 -0
  580. package/skills/brainstorm/SKILL.md +268 -0
  581. package/skills/brainstorm/soul.md +49 -0
  582. package/skills/claude-md-drift-check/SKILL.md +186 -0
  583. package/skills/claude-md-drift-check/checker.mjs +1380 -0
  584. package/skills/claude-md-drift-check/checker.sh +37 -0
  585. package/skills/claude-md-drift-check/package.json +16 -0
  586. package/skills/convergence-monitoring/README.md +39 -0
  587. package/skills/convergence-monitoring/SIGNALS.md +246 -0
  588. package/skills/convergence-monitoring/SKILL.md +285 -0
  589. package/skills/daily/SKILL.md +222 -0
  590. package/skills/daily/generate.sh +92 -0
  591. package/skills/daily/templates/daily.md.tpl +36 -0
  592. package/skills/debug/SKILL.md +188 -0
  593. package/skills/debug/soul.md +35 -0
  594. package/skills/discovery/SKILL.md +567 -0
  595. package/skills/discovery/issue-templates.md +237 -0
  596. package/skills/discovery/probes/docs-staleness.mjs +195 -0
  597. package/skills/discovery/probes/frontend-slop.mjs +186 -0
  598. package/skills/discovery/probes/ssot-code-diff.mjs +310 -0
  599. package/skills/discovery/probes/supply-chain-slopcheck.mjs +440 -0
  600. package/skills/discovery/probes/vault-narrative-staleness.mjs +355 -0
  601. package/skills/discovery/probes/vault-staleness.mjs +272 -0
  602. package/skills/discovery/probes-arch.md +252 -0
  603. package/skills/discovery/probes-audit.md +95 -0
  604. package/skills/discovery/probes-code.md +329 -0
  605. package/skills/discovery/probes-docs.md +76 -0
  606. package/skills/discovery/probes-feature.md +150 -0
  607. package/skills/discovery/probes-infra.md +138 -0
  608. package/skills/discovery/probes-intro.md +25 -0
  609. package/skills/discovery/probes-session.md +495 -0
  610. package/skills/discovery/probes-supply-chain.md +94 -0
  611. package/skills/discovery/probes-ui.md +147 -0
  612. package/skills/discovery/probes-vault.md +64 -0
  613. package/skills/discovery/slop-patterns.md +115 -0
  614. package/skills/dispatcher/SKILL.md +173 -0
  615. package/skills/docs-orchestrator/SKILL.md +362 -0
  616. package/skills/docs-orchestrator/audience-mapping.md +140 -0
  617. package/skills/domain-model/ADR-FORMAT.md +47 -0
  618. package/skills/domain-model/CONTEXT-FORMAT.md +77 -0
  619. package/skills/domain-model/SKILL.md +85 -0
  620. package/skills/ecosystem-health/SKILL.md +119 -0
  621. package/skills/ecosystem-health/wizard.md +193 -0
  622. package/skills/eval/SKILL.md +293 -0
  623. package/skills/eval/rubric-v1.md +218 -0
  624. package/skills/evolve/SKILL.md +546 -0
  625. package/skills/frontmatter-guard/SKILL.md +126 -0
  626. package/skills/gitlab-ops/SKILL.md +368 -0
  627. package/skills/gitlab-portfolio/SKILL.md +196 -0
  628. package/skills/grill/SKILL.md +185 -0
  629. package/skills/grill/soul.md +55 -0
  630. package/skills/hook-development/SKILL.md +413 -0
  631. package/skills/mcp-builder/SKILL.md +260 -0
  632. package/skills/memory-cleanup/SKILL.md +310 -0
  633. package/skills/mode-selector/SKILL.md +226 -0
  634. package/skills/peekaboo-driver/SKILL.md +237 -0
  635. package/skills/peekaboo-driver/soul.md +32 -0
  636. package/skills/persona-panel/SKILL.md +365 -0
  637. package/skills/persona-panel/persona-format.md +205 -0
  638. package/skills/persona-panel/presets/designer-lens.md +87 -0
  639. package/skills/persona-panel/presets/engineer-lens.md +88 -0
  640. package/skills/persona-panel/presets/pm-lens.md +86 -0
  641. package/skills/plan/SKILL.md +496 -0
  642. package/skills/plan/mode-feature.md +141 -0
  643. package/skills/plan/mode-new.md +297 -0
  644. package/skills/plan/mode-retro.md +271 -0
  645. package/skills/plan/prd-feature-template.md +132 -0
  646. package/skills/plan/prd-full-template.md +151 -0
  647. package/skills/plan/prd-reviewer-prompt.md +103 -0
  648. package/skills/plan/retro-template.md +75 -0
  649. package/skills/plan/soul.md +62 -0
  650. package/skills/playwright-driver/SKILL.md +226 -0
  651. package/skills/playwright-driver/soul.md +30 -0
  652. package/skills/quality-gates/SKILL.md +212 -0
  653. package/skills/reconcile/SKILL.md +324 -0
  654. package/skills/repo-audit/SKILL.md +272 -0
  655. package/skills/session-end/SKILL.md +1044 -0
  656. package/skills/session-end/discovery-scan.md +37 -0
  657. package/skills/session-end/drift-operations.md +97 -0
  658. package/skills/session-end/learning-patterns.md +78 -0
  659. package/skills/session-end/metrics-collection.md +175 -0
  660. package/skills/session-end/phase-3-2-docs-verification.md +148 -0
  661. package/skills/session-end/phase-3-6-tail.md +344 -0
  662. package/skills/session-end/phase-3-7a-recommendations.md +86 -0
  663. package/skills/session-end/plan-verification.md +288 -0
  664. package/skills/session-end/session-metrics-write.md +223 -0
  665. package/skills/session-end/vault-operations.md +50 -0
  666. package/skills/session-end/verification-checklist.md +20 -0
  667. package/skills/session-plan/SKILL.md +554 -0
  668. package/skills/session-plan/wave-template.md +37 -0
  669. package/skills/session-start/SKILL.md +1043 -0
  670. package/skills/session-start/phase-2-5-docs-planning.md +119 -0
  671. package/skills/session-start/phase-4-5-resource-health.md +49 -0
  672. package/skills/session-start/phase-7-1-premise-check.md +47 -0
  673. package/skills/session-start/phase-7-5-mode-selector.md +237 -0
  674. package/skills/session-start/phase-8-5-express-path.md +61 -0
  675. package/skills/session-start/presentation-format.md +81 -0
  676. package/skills/session-start/soul.md +57 -0
  677. package/skills/skill-creator/SKILL.md +168 -0
  678. package/skills/spinout/SKILL.md +76 -0
  679. package/skills/sunset-review/SKILL.md +96 -0
  680. package/skills/test-runner/SKILL.md +362 -0
  681. package/skills/test-runner/rubric-v1.md +388 -0
  682. package/skills/test-runner/soul.md +46 -0
  683. package/skills/tmux-layout/SKILL.md +104 -0
  684. package/skills/ubiquitous-language/SKILL.md +97 -0
  685. package/skills/using-orchestrator/SKILL.md +144 -0
  686. package/skills/vault-mirror/SKILL.md +234 -0
  687. package/skills/vault-sync/SKILL.md +319 -0
  688. package/skills/vault-sync/package-lock.json +40 -0
  689. package/skills/vault-sync/package.json +11 -0
  690. package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +8 -0
  691. package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
  692. package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +8 -0
  693. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
  694. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +8 -0
  695. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +8 -0
  696. package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +8 -0
  697. package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +10 -0
  698. package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +8 -0
  699. package/skills/vault-sync/tests/fixtures/clean-vault/README.md +3 -0
  700. package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +11 -0
  701. package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
  702. package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +9 -0
  703. package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +8 -0
  704. package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
  705. package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
  706. package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +7 -0
  707. package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +9 -0
  708. package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
  709. package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +11 -0
  710. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +3 -0
  711. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +3 -0
  712. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
  713. package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +11 -0
  714. package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
  715. package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +11 -0
  716. package/skills/vault-sync/tests/schema-drift.test.mjs +133 -0
  717. package/skills/vault-sync/validator.mjs +658 -0
  718. package/skills/vault-sync/validator.sh +55 -0
  719. package/skills/wave-executor/SKILL.md +496 -0
  720. package/skills/wave-executor/circuit-breaker.md +169 -0
  721. package/skills/wave-executor/wave-loop.md +1043 -0
  722. package/skills/write-executable-plan/SKILL.md +237 -0
  723. package/skills/write-executable-plan/plan-template.md +154 -0
  724. package/templates/_minimal/CLAUDE.md.tmpl +41 -0
  725. package/templates/_minimal/README.md.tmpl +15 -0
  726. package/templates/_minimal/gitignore.tmpl +47 -0
  727. package/templates/_shared/harte-regeln.md +16 -0
  728. package/templates/_shared/loop.md +90 -0
  729. package/templates/_shared/rules/parallel-sessions.md +77 -0
  730. package/templates/nextjs-minimal/README.md +30 -0
  731. package/templates/nextjs-minimal/app/layout.tsx +18 -0
  732. package/templates/nextjs-minimal/app/page.tsx +7 -0
  733. package/templates/nextjs-minimal/eslint.config.mjs +16 -0
  734. package/templates/nextjs-minimal/next.config.mjs +4 -0
  735. package/templates/nextjs-minimal/package.json +27 -0
  736. package/templates/nextjs-minimal/tsconfig.json +23 -0
  737. package/templates/node-minimal/README.md +33 -0
  738. package/templates/node-minimal/eslint.config.mjs +10 -0
  739. package/templates/node-minimal/package.json +21 -0
  740. package/templates/node-minimal/src/index.ts +1 -0
  741. package/templates/node-minimal/tests/sanity.test.ts +5 -0
  742. package/templates/node-minimal/tsconfig.json +17 -0
  743. package/templates/personas/README.md +150 -0
  744. package/templates/personas/accounting-compliance.v1.md +120 -0
  745. package/templates/personas/accounting-tax-advisor.v1.md +116 -0
  746. package/templates/personas/buyer-p1-cto.v1.md +125 -0
  747. package/templates/personas/buyer-p2-kanzlei.v1.md +134 -0
  748. package/templates/personas/buyer-p3-build.v1.md +130 -0
  749. package/templates/personas/buyer-p4-tech-veto.v1.md +130 -0
  750. package/templates/personas/buyer-p5-solo.v1.md +132 -0
  751. package/templates/personas/buyer-p6-ld.v1.md +130 -0
  752. package/templates/personas/klima-ai-expert.v1.md +114 -0
  753. package/templates/personas/klima-physicist.v1.md +117 -0
  754. package/templates/python-uv/README.md +28 -0
  755. package/templates/python-uv/pyproject.toml +32 -0
  756. package/templates/python-uv/src/__PROJECT_NAME__/__init__.py +0 -0
  757. package/templates/python-uv/src/__PROJECT_NAME__/main.py +6 -0
  758. package/templates/python-uv/tests/test_sanity.py +2 -0
  759. package/templates/static-html/README.md +19 -0
  760. package/templates/static-html/index.html +15 -0
  761. package/templates/static-html/script.js +1 -0
  762. package/templates/static-html/styles.css +26 -0
@@ -0,0 +1,1011 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-owner-leakage.mjs — Scan tracked files for owner-privacy leakage patterns.
4
+ *
5
+ * Implements the #462 audit trail durable CI guard (#471).
6
+ * Canonicalization-before-matching refactor (issue #661): the historical
7
+ * form-by-form treadmill (a fresh regex per encoding — P1 bare-no-slash #631,
8
+ * P9 dash-encoded #634, …) is replaced by a single CANONICALIZATION step.
9
+ * `canonicalizeLine()` collapses every known textual encoding of a path
10
+ * (URL-percent, dash-as-separator, backslash, double-slash, `\uXXXX`/`%uXXXX`
11
+ * escapes, HTML/numeric entities, unicode homoglyph slashes/dashes, case)
12
+ * into ONE canonical slash-form, and the owner-secret patterns are matched
13
+ * ONCE against that canonical form (plus the raw line, for belt-and-braces).
14
+ * A novel encoding of the same path normalizes to the same canonical string,
15
+ * so it is caught structurally — no new regex required. Err toward
16
+ * over-matching: this is a security guard, a false-positive is cheap, a
17
+ * false-negative ships a leak to the public mirror.
18
+ *
19
+ * Usage: check-owner-leakage.mjs <plugin-root>
20
+ *
21
+ * Forbidden patterns (canonical rules CP1–CP10):
22
+ * CP1 personal home path `/Users/bernhardg…` — matched on the CANONICAL form,
23
+ * so the slash-form (P1, #631), the dash-encoded projects-dir form
24
+ * (legacy P9, #634), URL-percent-encoded, backslash, and any future
25
+ * separator-encoding all collapse to one rule.
26
+ * CP2 private GitLab host `gitlab.gotzendorfer.at`
27
+ * CP3 private events domain `events.gotzendorfer.at`
28
+ * CP4 private package scope `@goetzendorfer/…`
29
+ * CP5 DEFAULT_GITLAB_HOST on line with 'gotzendorfer' OR as exported const
30
+ * CP6 private project slugs (see PRIVATE_SLUGS constant below). Two scopes:
31
+ * the tracked-file scan (runScan) uses the FULL 7-slug PRIVATE_SLUGS list;
32
+ * the in-process vault-namespace guard (isOwnerLeakySegment) uses the
33
+ * narrower CP6_INPROCESS_PATTERNS — PRIVATE_SLUGS minus VAULT_CLEAR_SLUGS,
34
+ * leaving only 2 retained slugs. See the VAULT_CLEAR_SLUGS carve-out
35
+ * (issue #59 owner decision 2026-07-18).
36
+ * CP7 catch-all `gotzendorfer.at` not matching an allowlisted exclusion
37
+ * CP8 full RFC1918 private dotted-quad (10.x.x.x / 192.168.x.x / 172.16-31.x.x)
38
+ * — internal IP leak. Placeholder `.x` forms and CIDR/range notation are NOT
39
+ * matched (only literal 4-octet IPs), so SSRF-range docs stay clean.
40
+ * CP10 `~/Projects/<PersonalName>/` — personal-name segment in a Projects path
41
+ * (name-denylist driven; see PERSONAL_NAMES constant below; issue #653)
42
+ * CP11 host-local confidential customer/repo names — matched from a NEVER-COMMITTED
43
+ * list referenced by owner.yaml `paths.confidential-names-file`
44
+ * (env SO_CONFIDENTIAL_NAMES_FILE; issue #728a). UNIQUE among CP-rules: a CP11
45
+ * violation REDACTS the matched name span from its reported lineContent, because
46
+ * this scanner runs in a PUBLIC GitHub-Actions mirror — printing the confidential
47
+ * name verbatim to the public CI log would be a worse leak than the one guarded.
48
+ *
49
+ * CP11 is INACTIVE unless a host-local names file is configured (the ~99% default),
50
+ * so public CI and unconfigured hosts see no behaviour change.
51
+ *
52
+ * Legacy P-numbering note: P1+P9 are now ONE canonical rule (CP1). The FAIL
53
+ * label still names the offending class for the audit trail.
54
+ *
55
+ * Exclusions (line-scoped, never whole-file):
56
+ * 1. Lines with office@gotzendorfer.at or security@gotzendorfer.at
57
+ * and no other gotzendorfer.at token
58
+ * 2. https://gotzendorfer.at[...] URLs in README.md and the 3 manifest files
59
+ * 3. Manifest author/email/url/websiteURL/privacyPolicyURL/termsOfServiceURL keys
60
+ * in .claude-plugin/plugin.json, .claude-plugin/marketplace.json, .codex-plugin/plugin.json
61
+ * (covered by exclusions 1 + 2 above; listed explicitly for audit trail)
62
+ * 4. .orchestrator/audits/** — never scanned (excluded in file enumeration)
63
+ * 5. tests/lib/events-default-url.test.mjs — ONLY the exact JSDoc contract line:
64
+ * " * - No literal `events.gotzendorfer.at` URL appears anywhere in scripts/ or hooks/."
65
+ * (a real string-literal events.gotzendorfer.at elsewhere in that file still FAILs)
66
+ * 6. tests/scripts/export-hw-learnings.test.mjs — exempt from P8 ONLY: the RFC1918
67
+ * IPs there are the redaction subject of the anonymizeString suite, not leaks.
68
+ *
69
+ * Exit codes:
70
+ * 0 — all checks passed
71
+ * 1 — at least one failure (or usage error / unreadable root)
72
+ */
73
+
74
+ import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';
75
+ import { join, extname, relative, basename, sep, resolve } from 'node:path';
76
+ import { execFileSync } from 'node:child_process';
77
+ import { argv } from 'node:process';
78
+ import { fileURLToPath } from 'node:url';
79
+ // NOTE: the two host-local helper modules (../config/host-paths.mjs and
80
+ // ./confidential-names.mjs) are imported DYNAMICALLY inside
81
+ // getConfidentialNamePatterns(), NOT statically here. This scanner is a
82
+ // documented standalone single-file vendoring target (security.md § Owner-Privacy:
83
+ // "Reuse the same scanner") — the .husky/pre-commit E2E and any consumer that
84
+ // copies ONLY this file into a fresh tree would otherwise crash at module load
85
+ // with ERR_MODULE_NOT_FOUND, blocking clean commits. Dynamic import lets CP11 go
86
+ // silently inert when the helpers are absent while CP1–CP10 run unchanged.
87
+
88
+ // ---------------------------------------------------------------------------
89
+ // CLI / import-mode detection (#661)
90
+ // ---------------------------------------------------------------------------
91
+ //
92
+ // This module is BOTH a CLI script (run by validate-plugin + .husky/pre-commit)
93
+ // AND an importable library (the canonicalization helpers are unit-tested in
94
+ // isolation). When imported, the top-level scan + process.exit() must NOT run.
95
+ // `isMain` is true only when this file is the node entry point.
96
+ const isMain = argv[1] !== undefined && resolve(argv[1]) === fileURLToPath(import.meta.url);
97
+
98
+ // CLI: single positional arg required (only enforced when run directly).
99
+ const pluginRoot = argv[2];
100
+ if (isMain && !pluginRoot) {
101
+ console.error('Usage: check-owner-leakage.mjs <plugin-root>');
102
+ process.exit(1);
103
+ }
104
+
105
+ // ---------------------------------------------------------------------------
106
+ // Private slugs constant — #462 audit trail (list is CLOSED: add only after audit review)
107
+ // ---------------------------------------------------------------------------
108
+ const PRIVATE_SLUGS = [
109
+ 'launchpad-ai-factory',
110
+ 'Codex-Hackathon',
111
+ 'buchhaltgenie',
112
+ 'AngebotsChecker',
113
+ 'wien-forschungsfragen-klima',
114
+ 'aiat-pmo-module',
115
+ 'mail-assistant',
116
+ ];
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // VAULT_CLEAR_SLUGS carve-out (issue #59, owner decision 2026-07-18)
120
+ // ---------------------------------------------------------------------------
121
+ //
122
+ // Slugs that must STILL be blocked from leaking into TRACKED public-mirror files
123
+ // (so they remain in PRIVATE_SLUGS / CP6_PATTERNS, which runScan uses) but are
124
+ // CLEARED for use as an IN-PROCESS vault-namespace segment — the vault is a
125
+ // host-local, gitignored corpus, so collapsing these to the shared 'redacted-repo'
126
+ // bucket needlessly de-isolates per-repo notes without any public-leak benefit.
127
+ // isOwnerLeakySegment iterates the narrower CP6_INPROCESS_PATTERNS (PRIVATE_SLUGS
128
+ // minus these), so a carved-out slug resolves to its own namespace while the
129
+ // tracked-file scanner is UNCHANGED. Comparison is case-insensitive: values are
130
+ // lowercased here and every CP6_INPROCESS_PATTERNS filter lowercases before
131
+ // membership test.
132
+ export const VAULT_CLEAR_SLUGS = new Set([
133
+ 'buchhaltgenie',
134
+ 'mail-assistant',
135
+ 'wien-forschungsfragen-klima',
136
+ 'launchpad-ai-factory',
137
+ 'angebotschecker',
138
+ ]);
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Canonicalization (issue #661)
142
+ // ---------------------------------------------------------------------------
143
+ //
144
+ // The historical evasion class (`encoded-path-forms-evade-slash-form-scanners`,
145
+ // #95) is structural: the SAME personal path is re-spelled with a different
146
+ // SEPARATOR or escape so a slash-anchored regex misses it. Rather than add a
147
+ // fresh regex per encoding, we DECODE every known encoding back to a single
148
+ // canonical slash-form ONCE, then match path patterns against that form.
149
+ //
150
+ // Decodings applied, in order (each is idempotent / safe to over-apply):
151
+ // 1. URL-percent escapes (%2F → /, %2E → ., %2D → -, %5C → \, %20 → space …)
152
+ // 2. `\uXXXX` / `%uXXXX` JS/JSON unicode escapes for /, ., -, \
153
+ // 3. HTML numeric + named entities for /, ., - (&#47; &#x2F; &sol; …)
154
+ // 4. Unicode homoglyph separators → ASCII (fullwidth / division slash → '/',
155
+ // various dashes/hyphens → '-')
156
+ // 5. Backslash → forward slash, and any run of separators collapsed
157
+ // 6. Dash-as-separator → slash (Claude Code projects-dir encoding, #634)
158
+ //
159
+ // After decoding, ALL of `/Users/bernhardg.`, `-Users-bernhardg--`,
160
+ // `%2FUsers%2Fbernhardg`, `\Users\bernhardg`, `/Users/bernhardg`, and a
161
+ // novel future spelling collapse to a canonical `/Users/bernhardg…` string the
162
+ // CP1 rule matches. CASE IS PRESERVED through every decode step so the
163
+ // `/Users` (capital-U) case-sensitivity contract and the uppercase-username
164
+ // near-miss guard both survive. Over-matching is intentional (security guard).
165
+
166
+ /** Unicode homoglyph / variant SLASH code points that should canonicalize to '/'. */
167
+ const HOMOGLYPH_SLASHES = /[⁄∕/⧸╱]/g; // ⁄ ∕ / ⧸ ╱
168
+ /** Unicode homoglyph / variant DASH code points that should canonicalize to '-'. */
169
+ const HOMOGLYPH_DASHES =
170
+ /[‐‑‒–—―−﹘﹣-]/g; // ‐ ‑ ‒ – — ― − ﹘ ﹣ -
171
+
172
+ /**
173
+ * Zero-width / format / invisible code points that a leak can splice INTO the
174
+ * username (`/Users/bern<U+200B>hardg`) to evade a contiguous-literal match.
175
+ * Stripped from the canonical form before matching (Finding 3). Over-stripping
176
+ * is safe for a security guard — these glyphs never legitimately appear inside a
177
+ * filesystem path segment. Code points (escaped to keep the source ASCII-clean
178
+ * and lint-quiet): tab U+0009, soft-hyphen U+00AD, ZWSP U+200B, ZWNJ U+200C,
179
+ * ZWJ U+200D, word-joiner U+2060, BOM/ZWNBSP U+FEFF. Built from an explicit
180
+ * code-point list via a non-character-class alternation so the combining ZWJ
181
+ * does not trip no-misleading-character-class.
182
+ */
183
+ const ZERO_WIDTH_FORMAT = new RegExp(
184
+ '(?:' +
185
+ ['\\u0009', '\\u00ad', '\\u200b', '\\u200c', '\\u200d', '\\u2060', '\\ufeff'].join('|') +
186
+ ')',
187
+ 'gu',
188
+ );
189
+
190
+ /**
191
+ * Decode the printable-ASCII range of a hex code point, or null when out of
192
+ * range. Used by the percent / unicode-escape decoders so the LETTERS of
193
+ * `Users`/`bernhardg` (e.g. `%55` → `U`, `%62` → `b`) are decoded, not only the
194
+ * separators (Finding 4). Over-decoding into the full printable-ASCII range is
195
+ * intentional and safe for a security guard. CASE IS PRESERVED because the hex
196
+ * value itself encodes the case (`%55`→`U`, `%75`→`u`).
197
+ * @param {number} cp
198
+ * @returns {string|null}
199
+ */
200
+ function printableAsciiFromCodePoint(cp) {
201
+ return cp >= 0x20 && cp <= 0x7e ? String.fromCharCode(cp) : null;
202
+ }
203
+
204
+ /**
205
+ * Decode URL-percent escapes back to their ASCII characters, WITHOUT touching
206
+ * unrelated literal `%`s (decodeURIComponent would throw on a stray `%`). The
207
+ * decode targets the WHOLE printable-ASCII range — both separators (`%2F` → /)
208
+ * AND the alphanumerics of `Users`/`bernhardg` (`%55` → U, #Finding 4) — and the
209
+ * `%uXXXX` JS/JSON-style percent-unicode escape. Double-encoding (`%252F`) is
210
+ * handled by the fixpoint loop in canonicalizeLine (a single decode pass turns
211
+ * `%252F` → `%2F`; the next pass turns `%2F` → `/`). Only the small set of
212
+ * always-relevant separators is hard-coded; everything else in printable-ASCII
213
+ * is decoded generically.
214
+ * @param {string} s
215
+ * @returns {string}
216
+ */
217
+ function decodePercent(s) {
218
+ // `%uXXXX` (JS/JSON-style percent-unicode) — BMP code point in printable ASCII.
219
+ let out = s.replace(/%u([0-9a-fA-F]{4})/g, (_m, hex) => {
220
+ const cp = parseInt(hex, 16);
221
+ const ch = printableAsciiFromCodePoint(cp);
222
+ return ch === null ? _m : ch;
223
+ });
224
+ // `%XX` — any printable-ASCII byte (separators AND letters/digits).
225
+ out = out.replace(/%([0-9a-fA-F]{2})/g, (_m, hex) => {
226
+ const cp = parseInt(hex, 16);
227
+ const ch = printableAsciiFromCodePoint(cp);
228
+ return ch === null ? _m : ch;
229
+ });
230
+ return out;
231
+ }
232
+
233
+ /**
234
+ * Decode JS/JSON `\uXXXX` escapes back to their ASCII characters — separators
235
+ * AND the alphanumerics of `Users`/`bernhardg` (Finding 4). Out-of-range code
236
+ * points are left intact.
237
+ * @param {string} s
238
+ * @returns {string}
239
+ */
240
+ function decodeUnicodeEscapes(s) {
241
+ return s.replace(/\\u([0-9a-fA-F]{4})/g, (_m, hex) => {
242
+ const cp = parseInt(hex, 16);
243
+ const ch = printableAsciiFromCodePoint(cp);
244
+ return ch === null ? _m : ch;
245
+ });
246
+ }
247
+
248
+ /**
249
+ * Decode HTML named + numeric character references. Named entities cover the
250
+ * path separators; numeric entities (`&#85;`, `&#x55;`) decode the WHOLE
251
+ * printable-ASCII range so the LETTERS of `Users`/`bernhardg` are decoded, not
252
+ * only the separators (Finding 4). Out-of-range code points are left intact.
253
+ * @param {string} s
254
+ * @returns {string}
255
+ */
256
+ function decodeHtmlEntities(s) {
257
+ return s
258
+ .replace(/&sol;/gi, '/')
259
+ .replace(/&period;/gi, '.')
260
+ .replace(/&(?:dash|hyphen);/gi, '-')
261
+ // numeric hex entity — any printable-ASCII code point
262
+ .replace(/&#x([0-9a-fA-F]+);/g, (_m, hex) => {
263
+ const ch = printableAsciiFromCodePoint(parseInt(hex, 16));
264
+ return ch === null ? _m : ch;
265
+ })
266
+ // numeric decimal entity — any printable-ASCII code point
267
+ .replace(/&#(\d+);/g, (_m, dec) => {
268
+ const ch = printableAsciiFromCodePoint(parseInt(dec, 10));
269
+ return ch === null ? _m : ch;
270
+ });
271
+ }
272
+
273
+ /**
274
+ * Canonicalize one line of text into a single slash-form string suitable for
275
+ * separator-agnostic path matching. CASE IS PRESERVED — the owner-path rule
276
+ * (CP1) is anchored on the case-sensitive `/Users/bernhardg` literal, so a
277
+ * deliberately-lowercased `/users/...` source literal and an uppercase-letter
278
+ * username near-miss (`/Users/bernhardgXfoo`) both stay distinguishable after
279
+ * canonicalization. Only SEPARATORS / escapes are normalized, never letter case.
280
+ * Public for unit-testing the decode steps.
281
+ * @param {string} line
282
+ * @returns {string} canonical form (case preserved)
283
+ */
284
+ export function canonicalizeLine(line) {
285
+ let s = String(line);
286
+ // 0. strip zero-width / format / invisible chars (and tab) spliced into a
287
+ // path segment to break a contiguous-literal match (Finding 3). Done first
288
+ // AND inside the fixpoint loop so a glyph wedged between two ENCODED chars
289
+ // (e.g. `%2<U+200B>F`) also vanishes once its neighbours decode.
290
+ s = s.replace(ZERO_WIDTH_FORMAT, '');
291
+ // 1–3. decode every textual encoding to a FIXPOINT (Finding 5). A single pass
292
+ // only peels one encoding layer; a nested encoding (one decoder's output
293
+ // feeding another — `%252F` → `%2F` → `/`, or an entity whose decoded form
294
+ // is itself a percent-escape) needs the loop. The decoders are monotone
295
+ // (each only ever shrinks/normalizes), so the loop converges; the bound is
296
+ // a safety valve against any pathological non-converging input.
297
+ for (let i = 0; i < 12; i++) {
298
+ const before = s;
299
+ // strip format chars exposed by the previous round's decoding
300
+ s = s.replace(ZERO_WIDTH_FORMAT, '');
301
+ // percent escapes (separators AND letters, plus %uXXXX)
302
+ s = decodePercent(s);
303
+ // JS/JSON unicode escapes
304
+ s = decodeUnicodeEscapes(s);
305
+ // HTML entities (named + numeric, separators AND letters)
306
+ s = decodeHtmlEntities(s);
307
+ if (s === before) break; // fixpoint reached
308
+ }
309
+ // 4. unicode homoglyph separators → ASCII
310
+ s = s.replace(HOMOGLYPH_SLASHES, '/').replace(HOMOGLYPH_DASHES, '-');
311
+ // 5. backslash → forward slash
312
+ s = s.replace(/\\/g, '/');
313
+ // 6. dash-as-separator → slash. The Claude Code projects-dir encoding maps
314
+ // BOTH '/' and '.' to '-', so a leading/embedded dash run reconstructs the
315
+ // original separators. Converting every '-' run to '/' is over-broad but
316
+ // safe for matching: CP1 anchors on `/Users/bernhardg` which only appears
317
+ // when the real path is present. Collapse runs of '-'.
318
+ s = s.replace(/-+/g, '/');
319
+ // collapse repeated slashes produced by the decodings
320
+ s = s.replace(/\/{2,}/g, '/');
321
+ return s;
322
+ }
323
+
324
+ // ---------------------------------------------------------------------------
325
+ // Forbidden patterns (matched against canonical and/or raw form)
326
+ // ---------------------------------------------------------------------------
327
+
328
+ /**
329
+ * CP1: personal home path — matched on the CANONICAL (lowercased, slash-form)
330
+ * line. Because canonicalization collapses the slash-form (#631), the
331
+ * dash-encoded projects-dir form (#634), percent/unicode/backslash/homoglyph
332
+ * spellings into one string, a SINGLE rule replaces the old P1+P9 pair and
333
+ * catches future encodings structurally.
334
+ *
335
+ * Mirrors the ORIGINAL P1 regex EXACTLY, applied to the canonical (separator-
336
+ * normalized, case-PRESERVED) form: `/Users/bernhardg` + `[a-z.]*` (lowercase-
337
+ * letter / dot continuation — catches the full-username form
338
+ * `/Users/bernhardgoetzendorfer/`) + a trailing slash OR a word boundary.
339
+ *
340
+ * Because canonicalization is case-preserving, this single rule simultaneously:
341
+ * - is case-SENSITIVE on the host segment (`/Users`, not `/users`) — a
342
+ * deliberately-lowercased `/users/bernhardg.` literal does NOT match;
343
+ * - keeps the #631 fix (bare `/Users/bernhardg.` at EOL matches via `\b`);
344
+ * - keeps the old false-positive profile (the `[a-z.]*`-then-boundary stops
345
+ * at an uppercase / digit / underscore continuation):
346
+ * `/Users/bernhardgXfoo` → `X` (uppercase) stops `[a-z.]*`, `g`→`X` no `\b` → reject
347
+ * `/Users/bernhardg9` → `9` word-char, no `\b` after `g` → reject
348
+ * `/Users/bernhardg_home` → `_` word-char, no `\b` after `g` → reject
349
+ * `/Users/bernhardo` → diverges before `g` → reject
350
+ * `/Users/bernhardgoetzendorfer/` → `[a-z.]*` eats `oetzendorfer`, `/` → MATCH
351
+ *
352
+ * The win over the old P1+P9 pair: every ENCODED separator form (dash #634,
353
+ * percent, backslash, homoglyph, html-entity, unicode-escape, and any future
354
+ * spelling) collapses to `/Users/bernhardg…` in the canonical string, so ONE
355
+ * rule catches them all instead of a fresh regex per encoding.
356
+ *
357
+ * Finding 1 (HIGH): the USERNAME segment is matched CASE-INSENSITIVELY via an
358
+ * explicit per-letter token (`[bB][eE]…[gG]`) — not the `/i` flag, which would
359
+ * also loosen the host anchor, and not an inline `(?i:…)` group, which Node 20
360
+ * does not reliably support. On case-insensitive APFS, `/Users/Bernhardg.` is a
361
+ * real path identifying the operator, so a capitalized username must still be
362
+ * caught. Two case contracts are DELIBERATELY kept narrow:
363
+ * - the HOST segment stays case-SENSITIVE (`\/Users\/`, capital-U literal) so a
364
+ * lowercased `/users/bernhardg/x` source literal does NOT match;
365
+ * - the CONTINUATION class stays lowercase-only (`[a-z.]*`) so an UPPERCASE
366
+ * letter continuing the segment marks a DIFFERENT user and stops the match:
367
+ * `/Users/bernhardgXfoo` → `[a-z.]*` matches 0 chars, `g`→`X` (both word
368
+ * chars) gives no `\b` → reject (continuation-boundary survives).
369
+ */
370
+ const CP1_CANON = /\/Users\/[bB][eE][rR][nN][hH][aA][rR][dD][gG][a-z.]*(\/|\b)/;
371
+
372
+ /**
373
+ * Self-documentation guard: a line that QUOTES the scanner's own pattern regex
374
+ * (e.g. CHANGELOG / migration-doc prose `…added P9 /-Users-bernhardg[a-z.]*-/…`)
375
+ * contains the username token but is NOT a path — a real macOS home path never
376
+ * embeds a regex character-class `[…]` immediately after the username.
377
+ * This is the same self-reference class that SELF_EXCLUSIONS handles for the
378
+ * scanner's source + test files; here it is line-scoped so a REAL leak elsewhere
379
+ * in the same doc still fails. Without it, the #661 canonical rule (which is now
380
+ * as strict on the dash-form as P1 always was on the slash-form) would newly
381
+ * flag the pre-existing CHANGELOG entries that document the legacy P1/P9 regexes.
382
+ *
383
+ * NOTE: tested against the CANONICAL form, where the dash-run normalization has
384
+ * already turned the regex range `[a-z` into `[a/z`. We therefore anchor on the
385
+ * literal `[` right after `bernhardg` (`bernhardg\[`), which uniquely marks a
386
+ * quoted character-class and never appears in a real path segment. Username is
387
+ * matched case-insensitively to mirror the case-insensitive CP1 username token.
388
+ *
389
+ * Finding 2 (MED): the guard now BLANKS only the matched regex-quote TOKEN
390
+ * (replacing the `bernhardg` username with a sentinel) rather than suppressing
391
+ * the WHOLE line. A real leak that merely SHARES a line with a quoted regex
392
+ * (`Real: /Users/bernhardg/Projects/secret (see regex /Users/bernhardg[a-z.]*)`)
393
+ * is still caught: only the `…bernhardg[` token is neutralized, the real
394
+ * `/Users/bernhardg/…` path on the same line survives the residue re-scan.
395
+ * The `Users.bernhardg\[` anchor (with `.` matching the canonicalized slash)
396
+ * leaves the real-path form `Users/bernhardg/` — username followed by a SLASH,
397
+ * not a `[` — untouched.
398
+ */
399
+ const SCANNER_REGEX_QUOTE_BLANK_G = /(Users.)[bB][eE][rR][nN][hH][aA][rR][dD][gG](\[)/g;
400
+
401
+ /** CP2: private GitLab host */
402
+ const CP2 = /\bgitlab\.gotzendorfer\.at\b/;
403
+
404
+ /** CP3: private events domain */
405
+ const CP3 = /\bevents\.gotzendorfer\.at\b/;
406
+
407
+ /** CP4: private package scope */
408
+ const CP4 = /@goetzendorfer\/[A-Za-z0-9*_-]+/;
409
+
410
+ /** CP5: DEFAULT_GITLAB_HOST on a line with 'gotzendorfer' OR as an exported const */
411
+ const CP5_WITH_GOTZ = /DEFAULT_GITLAB_HOST/;
412
+ const CP5_EXPORT = /\bexport\b.*\bconst\b.*\bDEFAULT_GITLAB_HOST\b/;
413
+
414
+ /**
415
+ * CP6 (tracked-file scan): private project slugs (word-boundary anchored,
416
+ * case-insensitive — #483 W4-Q6 caught "Buchhaltgenie" capitalized).
417
+ * The FULL 7-slug list — runScan uses this so the public-mirror guard is UNCHANGED.
418
+ */
419
+ const CP6_PATTERNS = PRIVATE_SLUGS.map((slug) => new RegExp(`\\b${escapeRegex(slug)}\\b`, 'i'));
420
+
421
+ /**
422
+ * CP6 (in-process vault-namespace guard): PRIVATE_SLUGS minus VAULT_CLEAR_SLUGS
423
+ * (issue #59 owner decision 2026-07-18). isOwnerLeakySegment iterates THIS list,
424
+ * so carved-out slugs resolve to their own vault namespace instead of collapsing
425
+ * to 'redacted-repo', while the tracked-file scanner (CP6_PATTERNS) still blocks
426
+ * all 7 from leaking into the public mirror. Effectively only the 2 retained
427
+ * slugs (Codex-Hackathon, aiat-pmo-module). Case-insensitive: filter lowercases
428
+ * before the VAULT_CLEAR_SLUGS membership test, matching the 'i'-flag regex.
429
+ */
430
+ const CP6_INPROCESS_PATTERNS = PRIVATE_SLUGS
431
+ .filter((slug) => !VAULT_CLEAR_SLUGS.has(slug.toLowerCase()))
432
+ .map((slug) => new RegExp(`\\b${escapeRegex(slug)}\\b`, 'i'));
433
+
434
+ /** CP7: catch-all gotzendorfer.at (must not match allowlist) */
435
+ const CP7 = /gotzendorfer\.at/;
436
+
437
+ /**
438
+ * CP8: full RFC1918 private dotted-quad — internal-IP leak.
439
+ * Matches only literal 4-octet private IPs (10.x.x.x, 192.168.x.x, 172.16-31.x.x).
440
+ * Deliberately does NOT match placeholder `.x` forms (10.x, 192.168.x) or CIDR/range
441
+ * notation used in SSRF-range documentation, nor TEST-NET (192.0.2.x, RFC 5737).
442
+ */
443
+ const CP8 = /(?:\b10(?:\.\d{1,3}){3}|\b192\.168(?:\.\d{1,3}){2}|\b172\.(?:1[6-9]|2\d|3[01])(?:\.\d{1,3}){2})/;
444
+
445
+ /** CP8 allowlist: files where RFC1918 IPs are a legitimate test subject (IP-redaction fixtures). */
446
+ const CP8_ALLOWLIST = new Set(['tests/scripts/export-hw-learnings.test.mjs']);
447
+
448
+ /**
449
+ * Match an owner personal-home-path leak in a line, using canonicalization.
450
+ * Returns the offending class label, or null when clean.
451
+ *
452
+ * Strategy: canonicalize separators/escapes (case preserved), then run the
453
+ * single CP1 rule once. Because canonicalization collapses every known
454
+ * encoding of `/Users/bernhardg…` to one string while preserving letter case,
455
+ * this ONE structural rule replaces the P1-bare-slash + P9-dash regex
456
+ * treadmill AND catches future encodings (percent / backslash / homoglyph /
457
+ * html-entity / unicode-escape / dash) without a new regex per form.
458
+ * @param {string} line
459
+ * @returns {string|null}
460
+ */
461
+ export function matchOwnerPath(line) {
462
+ const canon = canonicalizeLine(line);
463
+ // Self-documentation guard (line-scoped, Finding 2): BLANK only the quoted
464
+ // regex TOKEN (`…bernhardg[`) — replacing the username with a sentinel — then
465
+ // re-scan the RESIDUE. This neutralizes prose that quotes the scanner's own
466
+ // pattern (`…bernhardg[a-z.]*…` — not a path) WITHOUT suppressing a real leak
467
+ // that happens to share the line. Tested against the CANONICAL form because
468
+ // the documented regex source spells separators with `\/` (escaped slash)
469
+ // which only normalizes to `/` after canonicalization. See
470
+ // SCANNER_REGEX_QUOTE_BLANK_G.
471
+ const residue = canon.replace(SCANNER_REGEX_QUOTE_BLANK_G, '$1__REGEXQUOTE__$2');
472
+ if (CP1_CANON.test(residue)) return 'CP1 (personal home path — canonicalized)';
473
+ return null;
474
+ }
475
+
476
+ /** CP10: personal-name owner segment in a ~/Projects/<Name>/ path (#653).
477
+ * Name-denylist driven (CLOSED list, audit-reviewed) — mirrors PRIVATE_SLUGS (CP6).
478
+ * A generic ~/Projects/[A-Z][a-z]+/ would false-positive on legit capitalized
479
+ * project dirs (~/Projects/MyApp/), so we match only known personal names.
480
+ *
481
+ * Matches the tilde form (`~/Projects/Bernhard`) OR an absolute home form
482
+ * (`/Users/<user>/Projects/Bernhard`, `/home/<user>/Projects/Bernhard`), then a
483
+ * trailing-slash OR word boundary. The slash-OR-word-boundary alternation is the
484
+ * Finding-1 fix for the original `…/<Name>/` form, which REQUIRED a slash after the
485
+ * name and so let a bare `~/Projects/Bernhard` (end-of-line, before `&&`) pass
486
+ * undetected — the same blindspot CP1 was patched for (see CP1 doc above). The
487
+ * absolute-home alternation is the Finding-3 defense-in-depth: a leak in a
488
+ * non-owner home (`/Users/alice/Projects/Bernhard/vault`) slipped both CP1 and the
489
+ * old tilde-only CP10. The trailing word boundary keeps the false-positive profile
490
+ * tight: `~/Projects/Bernhardt/` (denylisted name + a continuation letter) does
491
+ * NOT match, so names that merely START with a denylisted name are not flagged. */
492
+ const PERSONAL_NAMES = ['Bernhard'];
493
+ const CP10_PATTERNS = PERSONAL_NAMES.map(
494
+ (name) => new RegExp(`(?:~|/Users/[^/]+|/home/[^/]+)/Projects/${escapeRegex(name)}(\\/|\\b)`),
495
+ );
496
+
497
+ /** CP10 allowlist: files where ~/Projects/Bernhard is a deliberate test subject
498
+ * (migration/drift fixtures) or a legitimate one-shot-migration source/target. */
499
+ const CP10_ALLOWLIST = new Set([
500
+ 'tests/scripts/vault-consolidate.test.mjs',
501
+ 'tests/scripts/migrate-vault-paths.test.mjs',
502
+ 'tests/lib/migrate-vault-paths-pure.test.mjs',
503
+ 'tests/lib/cli-flags.test.mjs',
504
+ 'tests/lib/config/vault-integration.test.mjs',
505
+ 'tests/skills/claude-md-drift-check/checker.test.mjs',
506
+ 'scripts/migrate-vault-paths.mjs',
507
+ 'scripts/vault-consolidate.mjs',
508
+ ]);
509
+
510
+ function escapeRegex(s) {
511
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
512
+ }
513
+
514
+ /**
515
+ * Redact every confidential-name span from `line`, ORDER-INDEPENDENTLY (Fix 1 + Fix 2).
516
+ *
517
+ * This is the single redaction sink for the confidential-names privacy invariant.
518
+ * It is applied at the print choke-point over EVERY violation's lineContent — not
519
+ * only CP11 hits — because this scanner runs in a PUBLIC GitHub-Actions mirror: a
520
+ * confidential customer/repo name that co-occurs with a CP1–CP10 hit on the same
521
+ * line (e.g. a name beside an RFC1918 IP that fails CP8) would otherwise be echoed
522
+ * verbatim to the public CI log (Fix 1).
523
+ *
524
+ * ORDER-INDEPENDENCE (Fix 2): a naïve chain of `.replace()` calls is order-dependent
525
+ * — when one configured name is a PREFIX of another (`['acme','acme-corp-secret']`),
526
+ * redacting the shorter first destroys the longer's match and leaks a suffix residue
527
+ * (`[REDACTED]-corp-secret`). Instead we compute ALL match spans against the ORIGINAL
528
+ * (unmutated) string across every pattern, merge overlapping/adjacent intervals, and
529
+ * splice `[REDACTED]` per merged interval. No pattern ever sees a string another
530
+ * pattern already rewrote, so prefix/suffix overlap cannot leak regardless of list
531
+ * order.
532
+ *
533
+ * @param {string} line — the raw (already-trimmed) violation lineContent.
534
+ * @param {RegExp[]} patterns — confidential-name regexes (word-boundary, case-insensitive).
535
+ * @returns {string} the line with every configured name span replaced by [REDACTED].
536
+ */
537
+ function redactSpans(line, patterns) {
538
+ if (!Array.isArray(patterns) || patterns.length === 0) return line;
539
+
540
+ // 1. Collect [start, end) spans of every match of every pattern against the
541
+ // ORIGINAL line (never a partially-mutated one). Global clone so exec() walks
542
+ // all matches; zero-width guard prevents an infinite loop on a degenerate regex.
543
+ const spans = [];
544
+ for (const re of patterns) {
545
+ const g = new RegExp(re.source, re.flags.includes('g') ? re.flags : re.flags + 'g');
546
+ let m;
547
+ while ((m = g.exec(line)) !== null) {
548
+ if (m[0].length === 0) {
549
+ g.lastIndex += 1;
550
+ continue;
551
+ }
552
+ spans.push([m.index, m.index + m[0].length]);
553
+ }
554
+ }
555
+ if (spans.length === 0) return line;
556
+
557
+ // 2. Merge overlapping / adjacent intervals (sorted by start).
558
+ spans.sort((a, b) => a[0] - b[0]);
559
+ const merged = [];
560
+ for (const [s, e] of spans) {
561
+ const last = merged[merged.length - 1];
562
+ if (last && s <= last[1]) {
563
+ last[1] = Math.max(last[1], e);
564
+ } else {
565
+ merged.push([s, e]);
566
+ }
567
+ }
568
+
569
+ // 3. Splice [REDACTED] per merged interval, left-to-right over the ORIGINAL line.
570
+ let out = '';
571
+ let cursor = 0;
572
+ for (const [s, e] of merged) {
573
+ out += line.slice(cursor, s) + '[REDACTED]';
574
+ cursor = e;
575
+ }
576
+ out += line.slice(cursor);
577
+ return out;
578
+ }
579
+
580
+ /**
581
+ * CP11: build word-boundary, case-insensitive regexes from the host-local
582
+ * confidential-names list (#728a). Mirrors CP6_PATTERNS (private slugs), but the
583
+ * name source is LOADED from a never-committed host-local file instead of a
584
+ * closed in-source array — the confidential customer/repo names must never live
585
+ * in a committed file.
586
+ *
587
+ * LAZY BY CONTRACT: this reads the host-local file (owner.yaml + env), so it MUST
588
+ * be called only from runScan() — NOT at module top-level. This module is
589
+ * dual-mode (CLI entry point AND importable library, `isMain` guard); a top-level
590
+ * fs read would fire on every library import (e.g. pseudonym-map.mjs importing
591
+ * isOwnerLeakySegment). loadConfidentialNames caches per path, so calling this
592
+ * once per scan reads the file at most once.
593
+ *
594
+ * STANDALONE-SAFE: the two host-local helper modules are DYNAMICALLY imported
595
+ * here (not statically at the top of the file) so a single-file copy of this
596
+ * scanner — the documented vendoring pattern (security.md § Owner-Privacy: "Reuse
597
+ * the same scanner"; exercised by tests/husky/pre-commit-owner-leakage.test.mjs,
598
+ * which copies ONLY this file into a tmp repo) — does not crash at module load
599
+ * with ERR_MODULE_NOT_FOUND. When the helpers are unresolvable (or throw), CP11
600
+ * degrades to inert ([] patterns) and CP1–CP10 run unchanged.
601
+ *
602
+ * @returns {Promise<RegExp[]>} one regex per configured name, or [] when
603
+ * unconfigured / unusable / unresolvable (standalone copy).
604
+ */
605
+ async function getConfidentialNamePatterns() {
606
+ try {
607
+ const { loadHostPaths, resolveHostPath } = await import('../config/host-paths.mjs');
608
+ const { loadConfidentialNames } = await import('./confidential-names.mjs');
609
+ const ctx = loadHostPaths();
610
+ const namesPath = resolveHostPath('confidential-names-file', '', ctx);
611
+ const names = loadConfidentialNames({ namesPath });
612
+ if (!names) return [];
613
+ return names.map((name) => new RegExp(`\\b${escapeRegex(name)}\\b`, 'i'));
614
+ } catch {
615
+ // Helper modules absent (standalone single-file vendoring) or a config read
616
+ // failure — CP11 goes silently inert; the CP1–CP10 rules need none of these
617
+ // modules and continue to enforce the scan.
618
+ return [];
619
+ }
620
+ }
621
+
622
+ // ---------------------------------------------------------------------------
623
+ // Text-scan extension allowlist (spec A.2)
624
+ // ---------------------------------------------------------------------------
625
+ const TEXT_EXTS = new Set(['.md', '.mjs', '.js', '.ts', '.json', '.yml', '.yaml', '.sh', '.txt']);
626
+
627
+ // Dotfiles to include (checked by basename, BEFORE the extension gate —
628
+ // extname('.env.example') is '.example' (truthy), so an extension-first check
629
+ // would make that allowlist entry unreachable; W3-P3 finding, 2026-06-10)
630
+ const DOTFILE_ALLOWLIST = new Set(['.env.example', '.nvmrc', '.vault.yaml']);
631
+
632
+ function isTextFile(filePath) {
633
+ const base = basename(filePath);
634
+ if (DOTFILE_ALLOWLIST.has(base)) return true;
635
+ const ext = extname(filePath);
636
+ return ext ? TEXT_EXTS.has(ext) : false;
637
+ }
638
+
639
+ // ---------------------------------------------------------------------------
640
+ // File enumeration: git ls-files primary, recursive fs walk fallback (spec A.2)
641
+ // Exclusions: .git/, node_modules/, .orchestrator/audits/
642
+ // ---------------------------------------------------------------------------
643
+
644
+ function getTrackedFiles() {
645
+ try {
646
+ const output = execFileSync('git', ['ls-files'], { cwd: pluginRoot, encoding: 'utf8' });
647
+ return output
648
+ .split('\n')
649
+ .filter(Boolean)
650
+ .map((f) => join(pluginRoot, f));
651
+ } catch {
652
+ // git unavailable or not a git repo — fall back to recursive fs walk
653
+ return walkDir(pluginRoot);
654
+ }
655
+ }
656
+
657
+ function walkDir(dir, acc = []) {
658
+ let entries;
659
+ try {
660
+ entries = readdirSync(dir);
661
+ } catch {
662
+ return acc;
663
+ }
664
+ for (const entry of entries) {
665
+ const full = join(dir, entry);
666
+ // Exclusions: .git, node_modules, .orchestrator/audits
667
+ if (entry === '.git' || entry === 'node_modules') continue;
668
+ const rel = relative(pluginRoot, full);
669
+ if (rel === join('.orchestrator', 'audits') || rel.startsWith(join('.orchestrator', 'audits') + sep)) continue;
670
+ let s;
671
+ try {
672
+ s = statSync(full);
673
+ } catch {
674
+ continue;
675
+ }
676
+ if (s.isDirectory()) {
677
+ walkDir(full, acc);
678
+ } else {
679
+ acc.push(full);
680
+ }
681
+ }
682
+ return acc;
683
+ }
684
+
685
+ // ---------------------------------------------------------------------------
686
+ // Allowlist helpers (spec A.4)
687
+ // ---------------------------------------------------------------------------
688
+
689
+ /**
690
+ * Return true if this gotzendorfer.at hit is covered by the exclusion allowlist.
691
+ * @param {string} relPath - path relative to pluginRoot (forward slashes)
692
+ * @param {string} line - the raw line content
693
+ * @returns {boolean}
694
+ */
695
+ function isAllowlisted(relPath, line) {
696
+ // Normalize to forward-slash for matching
697
+ const norm = relPath.replace(/\\/g, '/');
698
+
699
+ // A.4 exclusion 5: tests/lib/events-default-url.test.mjs — ONLY the exact JSDoc contract line
700
+ if (norm === 'tests/lib/events-default-url.test.mjs') {
701
+ // The exact doc-comment line: " * - No literal `events.gotzendorfer.at` URL..."
702
+ // Match the literal backtick-quoted pattern in a JSDoc/comment line
703
+ if (/^\s+\*\s+- No literal `events\.gotzendorfer\.at`/.test(line)) {
704
+ return true;
705
+ }
706
+ // Any other gotzendorfer.at in this file is NOT excluded
707
+ return false;
708
+ }
709
+
710
+ // A.4 exclusion 1: lines with office@ or security@ gotzendorfer.at and NO other gotzendorfer.at token
711
+ // (i.e., the only gotzendorfer.at occurrence is an email address — count occurrences)
712
+ const SANCTIONED_EMAILS = /(?:office|security)@gotzendorfer\.at/g;
713
+ const allGotzTokens = [...line.matchAll(/gotzendorfer\.at/g)];
714
+ const sanctionedMatches = [...line.matchAll(SANCTIONED_EMAILS)];
715
+ if (allGotzTokens.length > 0 && allGotzTokens.length === sanctionedMatches.length) {
716
+ // All gotzendorfer.at occurrences are the sanctioned email addresses
717
+ return true;
718
+ }
719
+
720
+ // A.4 exclusion 2 + 3: https://gotzendorfer.at URLs in README.md and the 3 manifest files
721
+ const ALLOWLISTED_URL_PATHS = new Set([
722
+ 'README.md',
723
+ '.claude-plugin/plugin.json',
724
+ '.claude-plugin/marketplace.json',
725
+ '.codex-plugin/plugin.json',
726
+ ]);
727
+ const inAllowlistedFile = ALLOWLISTED_URL_PATHS.has(norm);
728
+
729
+ if (inAllowlistedFile) {
730
+ // Check that the only gotzendorfer.at occurrences on this line are sanctioned URLs or emails
731
+ const SANCTIONED_URL = /https?:\/\/gotzendorfer\.at\b/g;
732
+ const allGotzOnLine = [...line.matchAll(/gotzendorfer\.at/g)];
733
+ const sanctionedUrlMatches = [...line.matchAll(SANCTIONED_URL)];
734
+ const emailMatches = [...line.matchAll(SANCTIONED_EMAILS)];
735
+ const totalSanctioned = sanctionedUrlMatches.length + emailMatches.length;
736
+ if (allGotzOnLine.length > 0 && allGotzOnLine.length === totalSanctioned) {
737
+ return true;
738
+ }
739
+ }
740
+
741
+ return false;
742
+ }
743
+
744
+ // ---------------------------------------------------------------------------
745
+ // Counting
746
+ // ---------------------------------------------------------------------------
747
+
748
+ let passed = 0;
749
+ let failed = 0;
750
+
751
+ function pass(msg) {
752
+ console.log(` PASS: ${msg}`);
753
+ passed++;
754
+ }
755
+
756
+ function fail(msg) {
757
+ console.log(` FAIL: ${msg}`);
758
+ failed++;
759
+ }
760
+
761
+ // ---------------------------------------------------------------------------
762
+ // Main check — only runs when invoked as the CLI entry point (#661). When this
763
+ // module is imported (for unit-testing the canonicalization helpers), the scan
764
+ // and process.exit() below are skipped.
765
+ // ---------------------------------------------------------------------------
766
+
767
+ async function runScan() {
768
+ console.log('--- Check 11: owner-privacy leakage ---');
769
+
770
+ if (!existsSync(pluginRoot)) {
771
+ fail(`plugin root does not exist: ${pluginRoot}`);
772
+ console.log('');
773
+ console.log(`Results: ${passed} passed, ${failed} failed`);
774
+ process.exit(1);
775
+ }
776
+
777
+ const allFiles = getTrackedFiles();
778
+ const textFiles = allFiles.filter(isTextFile);
779
+
780
+ // Exclusions:
781
+ // - .orchestrator/audits/** never scanned (A.2/A.4-5)
782
+ // - This guard's own source file (pattern-doc-comments define the scanner — not leaks).
783
+ // - This guard's own test file (string-literal fixtures exercise the detector — not leaks).
784
+ // - Persona content-lint tests (assert template files don't contain leakage strings;
785
+ // the assertion literals themselves match the scanner regex — fixtures, not leaks).
786
+ // Self-exclusions are the design-time fix for the latent bug exposed when scanner
787
+ // fixture files transition from untracked → tracked in the same commit that tightens
788
+ // detection (commit a68e94f for the original two; commit 95c8237 deep-3 W4 added the
789
+ // case-insensitive P6 regex + introduced content-lint.test.mjs in the same commit,
790
+ // producing a pre-commit false-pass — see pipeline #4365 / housekeeping-2 2026-05-19).
791
+ const SELF_EXCLUSIONS = new Set([
792
+ 'scripts/lib/validate/check-owner-leakage.mjs',
793
+ 'tests/lib/validate/check-owner-leakage.test.mjs',
794
+ 'tests/templates/personas/content-lint.test.mjs',
795
+ 'tests/husky/pre-commit-owner-leakage.test.mjs',
796
+ // #634: encoding-contract fixtures (`-Users-bernhardg-` expected-value literals
797
+ // are load-bearing for the resolveMemoryDir() assertions; P9 would self-flag them)
798
+ 'tests/lib/memory-paths.test.mjs',
799
+ // #660: owner-leakage redaction-test fixtures — namespace.test.mjs feeds the real
800
+ // CP1/CP6/CP10 leak literals into resolveRepoNamespace to prove they redact to
801
+ // 'redacted-repo'; the assertion literals are fixtures, not leaks (same class as
802
+ // check-owner-leakage.test.mjs above).
803
+ 'tests/lib/vault-mirror/namespace.test.mjs',
804
+ // #700: vault-relocation classifier redaction-test fixtures — both files feed the
805
+ // real CP6 private slugs (BuchhaltGenie / aiat-pmo-module) into the classifier to
806
+ // prove they redact to 'redacted-repo'; assertion literals are fixtures, not leaks
807
+ // (same class as namespace.test.mjs above).
808
+ 'tests/lib/vault-relocation-rules.test.mjs',
809
+ 'tests/scripts/relocate-vault-corpus.test.mjs',
810
+ ]);
811
+ const scanFiles = textFiles.filter((f) => {
812
+ const rel = relative(pluginRoot, f).replace(/\\/g, '/');
813
+ if (rel.startsWith('.orchestrator/audits/')) return false;
814
+ if (SELF_EXCLUSIONS.has(rel)) return false;
815
+ return true;
816
+ });
817
+
818
+ // CP11 (#728a): load the host-local confidential-names patterns ONCE per scan.
819
+ // [] when unconfigured (the default) OR when the host-local helper modules are
820
+ // unresolvable (standalone single-file copy) → the CP11 block below is a no-op.
821
+ // Awaited once, before the per-line loop, because the helpers are now dynamically
822
+ // imported (standalone-safe) — CP1–CP10 behaviour is unchanged.
823
+ const cp11Patterns = await getConfidentialNamePatterns();
824
+
825
+ /** @type {Array<{relPath: string, lineNum: number, pattern: string, lineContent: string}>} */
826
+ const violations = [];
827
+
828
+ for (const filePath of scanFiles) {
829
+ let content;
830
+ try {
831
+ content = readFileSync(filePath, 'utf8');
832
+ } catch {
833
+ continue; // unreadable — skip
834
+ }
835
+
836
+ const relPath = relative(pluginRoot, filePath).replace(/\\/g, '/');
837
+ const lines = content.split('\n');
838
+
839
+ lines.forEach((line, idx) => {
840
+ const lineNum = idx + 1;
841
+
842
+ // CP1: personal home path — CANONICALIZED match (#661). One structural rule
843
+ // replaces the slash-form (P1, #631) + dash-encoded (P9, #634) treadmill and
844
+ // catches percent/unicode/backslash/homoglyph encodings of the same path.
845
+ const ownerPathHit = matchOwnerPath(line);
846
+ if (ownerPathHit) {
847
+ violations.push({ relPath, lineNum, pattern: ownerPathHit, lineContent: line.trim() });
848
+ }
849
+
850
+ // CP2: private GitLab host
851
+ if (CP2.test(line)) {
852
+ violations.push({ relPath, lineNum, pattern: 'CP2 (gitlab.gotzendorfer.at)', lineContent: line.trim() });
853
+ }
854
+
855
+ // CP3: private events domain — check exclusion 6 first
856
+ if (CP3.test(line)) {
857
+ if (!isAllowlisted(relPath, line)) {
858
+ violations.push({ relPath, lineNum, pattern: 'CP3 (events.gotzendorfer.at)', lineContent: line.trim() });
859
+ }
860
+ }
861
+
862
+ // CP4: private package scope
863
+ if (CP4.test(line)) {
864
+ violations.push({ relPath, lineNum, pattern: 'CP4 (@goetzendorfer/ scope)', lineContent: line.trim() });
865
+ }
866
+
867
+ // CP5: DEFAULT_GITLAB_HOST with gotzendorfer OR as exported const
868
+ if (CP5_WITH_GOTZ.test(line) && /gotzendorfer/.test(line)) {
869
+ violations.push({ relPath, lineNum, pattern: 'CP5 (DEFAULT_GITLAB_HOST on gotzendorfer line)', lineContent: line.trim() });
870
+ } else if (CP5_EXPORT.test(line)) {
871
+ violations.push({ relPath, lineNum, pattern: 'CP5 (DEFAULT_GITLAB_HOST exported const)', lineContent: line.trim() });
872
+ }
873
+
874
+ // CP6: private project slugs
875
+ for (let i = 0; i < CP6_PATTERNS.length; i++) {
876
+ if (CP6_PATTERNS[i].test(line)) {
877
+ violations.push({ relPath, lineNum, pattern: `CP6 (private slug: ${PRIVATE_SLUGS[i]})`, lineContent: line.trim() });
878
+ break; // one violation per line per slug is enough
879
+ }
880
+ }
881
+
882
+ // CP7: catch-all gotzendorfer.at — check exclusion allowlist
883
+ if (CP7.test(line)) {
884
+ if (!isAllowlisted(relPath, line)) {
885
+ violations.push({ relPath, lineNum, pattern: 'CP7 (gotzendorfer.at catch-all)', lineContent: line.trim() });
886
+ }
887
+ }
888
+
889
+ // CP8: full RFC1918 private dotted-quad — internal IP leak (redaction-test fixtures exempt)
890
+ if (CP8.test(line) && !CP8_ALLOWLIST.has(relPath)) {
891
+ violations.push({ relPath, lineNum, pattern: 'CP8 (RFC1918 private IP)', lineContent: line.trim() });
892
+ }
893
+
894
+ // CP10: personal-name segment in a ~/Projects/<name>/ path (allowlisted migration fixtures exempt) — #653
895
+ if (!CP10_ALLOWLIST.has(relPath)) {
896
+ for (const re of CP10_PATTERNS) {
897
+ if (re.test(line)) {
898
+ violations.push({ relPath, lineNum, pattern: 'CP10 (~/Projects/<name>/ personal segment)', lineContent: line.trim() });
899
+ break;
900
+ }
901
+ }
902
+ }
903
+
904
+ // CP11: host-local confidential customer/repo names (#728a). INACTIVE unless a
905
+ // names file is configured (cp11Patterns is [] by default).
906
+ //
907
+ // DETECTION ONLY here — the confidential-name REDACTION is applied at the print
908
+ // choke-point (redactSpans over EVERY violation's lineContent), so a name that
909
+ // rides in on a CP1–CP10 hit on the same line is scrubbed too (Fix 1), and a
910
+ // name that is a prefix of another configured name cannot leak a suffix residue
911
+ // (Fix 2). cp11Patterns are non-global, so `.test()` is stateless.
912
+ if (cp11Patterns.length > 0 && cp11Patterns.some((re) => re.test(line))) {
913
+ violations.push({ relPath, lineNum, pattern: 'CP11 (confidential name)', lineContent: line.trim() });
914
+ }
915
+ });
916
+ }
917
+
918
+ // Deduplicate: CP7 and CP3 can overlap — de-dup by (relPath, lineNum, pattern)
919
+ // But CP3 and CP7 are distinct patterns so they'd produce separate entries.
920
+ // However, one line could match both CP3 and CP7 — treat as two violations.
921
+ // The spec does not say to deduplicate, so keep as-is.
922
+
923
+ if (violations.length === 0) {
924
+ pass(`no owner-privacy leakage found across ${scanFiles.length} scanned files`);
925
+ } else {
926
+ for (const v of violations) {
927
+ // Choke-point redaction (Fix 1 + Fix 2): scrub every configured confidential
928
+ // name from EVERY violation's lineContent — not only CP11 hits — BEFORE it
929
+ // reaches stdout (the public GitHub-Actions log). redactSpans is order-
930
+ // independent (span-merge, never chained .replace), and is a no-op when no
931
+ // names file is configured (cp11Patterns === []), so the CP1–CP10 default-path
932
+ // output is byte-identical to before. Redact first, THEN truncate, so a name
933
+ // straddling the 120-char boundary can never leak its tail.
934
+ const safeContent = redactSpans(v.lineContent, cp11Patterns);
935
+ fail(`${v.relPath}:${v.lineNum} — ${v.pattern}: ${safeContent.slice(0, 120)}`);
936
+ }
937
+ }
938
+
939
+ console.log('');
940
+ console.log(`Results: ${passed} passed, ${failed} failed`);
941
+ process.exit(failed === 0 ? 0 : 1);
942
+ }
943
+
944
+ // ---------------------------------------------------------------------------
945
+ // Exported helper for in-process leak detection (Issue #660 namespace guard)
946
+ // ---------------------------------------------------------------------------
947
+
948
+ /**
949
+ * Test whether a single value (a repo identifier or sanitised slug segment)
950
+ * matches any owner-privacy leakage pattern.
951
+ *
952
+ * Runs the canonical CP1 check (via canonicalizeLine + CP1_CANON) and the
953
+ * word-boundary CP6 (private project slugs) and CP10 (personal name in a
954
+ * Projects path) checks against the raw value. Returns the matched pattern id
955
+ * string or null when clean.
956
+ *
957
+ * CP6 here uses the NARROWER CP6_INPROCESS_PATTERNS (PRIVATE_SLUGS minus
958
+ * VAULT_CLEAR_SLUGS — issue #59 owner decision 2026-07-18), so carved-out slugs
959
+ * (buchhaltgenie, mail-assistant, wien-forschungsfragen-klima, launchpad-ai-factory,
960
+ * angebotschecker) resolve to their own vault namespace here while the tracked-file
961
+ * scanner (runScan, full CP6_PATTERNS) still blocks them from the public mirror.
962
+ *
963
+ * This is the single source of truth for in-process leak detection — do NOT
964
+ * reimplement these pattern checks outside this module. The `namespace.mjs`
965
+ * resolver uses this to guard vault path segments before any filesystem write.
966
+ *
967
+ * @param {string} value — raw repo identifier or sanitised slug to test.
968
+ * @returns {'CP1'|'CP6'|'CP10'|null}
969
+ */
970
+ export function isOwnerLeakySegment(value) {
971
+ if (typeof value !== 'string' || !value) return null;
972
+
973
+ // CP1: personal home path — canonicalize then match.
974
+ const ownerPathMatch = matchOwnerPath(value);
975
+ if (ownerPathMatch !== null) return 'CP1';
976
+
977
+ // CP1 (bare): the personal-username token standing alone — e.g. the macOS login
978
+ // name surfacing via deriveRepo()'s basename(process.cwd()) fallback when cwd is
979
+ // the home directory. CP1_CANON only fires with a `/Users/` prefix, so the bare
980
+ // segment would otherwise slip through into a committed vault path (#660 Q3-LOW-1).
981
+ if (/^[bB][eE][rR][nN][hH][aA][rR][dD][gG][a-z]*$/.test(value)) return 'CP1';
982
+
983
+ // CP6: private project slugs (word-boundary, case-insensitive). In-process
984
+ // guard uses the VAULT_CLEAR_SLUGS-carved list, NOT the full CP6_PATTERNS the
985
+ // tracked-file scanner uses (issue #59).
986
+ for (const re of CP6_INPROCESS_PATTERNS) {
987
+ if (re.test(value)) return 'CP6';
988
+ }
989
+
990
+ // CP10: personal name in a ~/Projects/<name>/ path.
991
+ for (const re of CP10_PATTERNS) {
992
+ if (re.test(value)) return 'CP10';
993
+ }
994
+
995
+ return null;
996
+ }
997
+
998
+ // Run the scan only when invoked directly as a CLI (#661).
999
+ if (isMain) {
1000
+ // runScan is async (the CP11 confidential-names helpers are now dynamically
1001
+ // imported, standalone-safe) and self-terminates via process.exit(). We do NOT
1002
+ // await it — that would make this a top-level-await module and force every static
1003
+ // importer of isOwnerLeakySegment to become async. Instead we attach a `.catch`
1004
+ // (Fix 4): while runScan's sole async step swallows its own errors today, an
1005
+ // unhandled rejection from ANY future refactor must fail CLOSED with context and a
1006
+ // deterministic exit 1 — never a silent unhandled-rejection warning + exit 0.
1007
+ runScan().catch((err) => {
1008
+ console.error('check-owner-leakage crashed:', err);
1009
+ process.exit(1);
1010
+ });
1011
+ }