gentyr 1.3.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 (599) hide show
  1. package/.claude/agents/antipattern-hunter.md +176 -0
  2. package/.claude/agents/code-reviewer.md +205 -0
  3. package/.claude/agents/code-writer.md +154 -0
  4. package/.claude/agents/deputy-cto.md +309 -0
  5. package/.claude/agents/feedback-agent.md +101 -0
  6. package/.claude/agents/investigator.md +136 -0
  7. package/.claude/agents/product-manager.md +97 -0
  8. package/.claude/agents/project-manager.md +116 -0
  9. package/.claude/agents/repo-hygiene-expert.md +626 -0
  10. package/.claude/agents/secret-manager.md +324 -0
  11. package/.claude/agents/test-writer.md +354 -0
  12. package/.claude/commands/configure-personas.md +144 -0
  13. package/.claude/commands/cto-report.md +36 -0
  14. package/.claude/commands/demo.md +89 -0
  15. package/.claude/commands/deputy-cto.md +345 -0
  16. package/.claude/commands/hotfix.md +31 -0
  17. package/.claude/commands/overdrive-gentyr.md +167 -0
  18. package/.claude/commands/product-manager.md +32 -0
  19. package/.claude/commands/push-migrations.md +86 -0
  20. package/.claude/commands/push-secrets.md +97 -0
  21. package/.claude/commands/services.json.example +30 -0
  22. package/.claude/commands/setup-gentyr.md +396 -0
  23. package/.claude/commands/show.md +42 -0
  24. package/.claude/commands/spawn-tasks.md +79 -0
  25. package/.claude/commands/toggle-automation-gentyr.md +75 -0
  26. package/.claude/commands/toggle-product-manager.md +19 -0
  27. package/.claude/commands/triage.md +69 -0
  28. package/.claude/hooks/README.md +686 -0
  29. package/.claude/hooks/__tests__/README.md +129 -0
  30. package/.claude/hooks/agent-tracker.js +434 -0
  31. package/.claude/hooks/antipattern-hunter-hook.js +401 -0
  32. package/.claude/hooks/api-key-watcher.js +289 -0
  33. package/.claude/hooks/block-no-verify.js +301 -0
  34. package/.claude/hooks/bypass-approval-hook.js +313 -0
  35. package/.claude/hooks/compliance-checker.js +1309 -0
  36. package/.claude/hooks/config-reader.js +143 -0
  37. package/.claude/hooks/credential-file-guard.js +1139 -0
  38. package/.claude/hooks/credential-health-check.js +168 -0
  39. package/.claude/hooks/credential-sync-hook.js +79 -0
  40. package/.claude/hooks/cto-notification-hook.js +656 -0
  41. package/.claude/hooks/feedback-launcher.js +424 -0
  42. package/.claude/hooks/feedback-orchestrator.js +367 -0
  43. package/.claude/hooks/gentyr-splash.js +47 -0
  44. package/.claude/hooks/gentyr-sync.js +389 -0
  45. package/.claude/hooks/hourly-automation.js +3340 -0
  46. package/.claude/hooks/key-sync.js +899 -0
  47. package/.claude/hooks/lib/approval-utils.js +731 -0
  48. package/.claude/hooks/lib/feature-branch-helper.js +102 -0
  49. package/.claude/hooks/lib/worktree-manager.js +330 -0
  50. package/.claude/hooks/mapping-validator.js +285 -0
  51. package/.claude/hooks/plan-executor.js +398 -0
  52. package/.claude/hooks/playwright-cli-guard.js +104 -0
  53. package/.claude/hooks/playwright-health-check.js +71 -0
  54. package/.claude/hooks/pre-commit-review.js +725 -0
  55. package/.claude/hooks/prompts/local-spec-enforcement.md +310 -0
  56. package/.claude/hooks/prompts/mapping-fix.md +92 -0
  57. package/.claude/hooks/prompts/mapping-review.md +140 -0
  58. package/.claude/hooks/prompts/schema-mapper.md +185 -0
  59. package/.claude/hooks/prompts/spec-enforcement.md +233 -0
  60. package/.claude/hooks/protected-action-approval-hook.js +336 -0
  61. package/.claude/hooks/protected-action-gate.js +562 -0
  62. package/.claude/hooks/protected-actions.json +208 -0
  63. package/.claude/hooks/protected-actions.json.template +122 -0
  64. package/.claude/hooks/quota-monitor.js +490 -0
  65. package/.claude/hooks/reporters/jest-failure-reporter.js +401 -0
  66. package/.claude/hooks/reporters/playwright-failure-reporter.js +446 -0
  67. package/.claude/hooks/reporters/vitest-failure-reporter.js +443 -0
  68. package/.claude/hooks/schema-mapper-hook.js +544 -0
  69. package/.claude/hooks/secret-leak-detector.js +216 -0
  70. package/.claude/hooks/session-reviver.js +514 -0
  71. package/.claude/hooks/slash-command-prefetch.js +1145 -0
  72. package/.claude/hooks/stale-work-detector.js +205 -0
  73. package/.claude/hooks/stop-continue-hook.js +414 -0
  74. package/.claude/hooks/todo-maintenance.js +522 -0
  75. package/.claude/hooks/todo-processing-prompt.md +75 -0
  76. package/.claude/hooks/usage-optimizer.js +791 -0
  77. package/.claude/mcp/README.md +246 -0
  78. package/.claude/settings.json.template +168 -0
  79. package/.mcp.json.template +207 -0
  80. package/CLAUDE.md +340 -0
  81. package/CLAUDE.md.gentyr-section +89 -0
  82. package/LICENSE +21 -0
  83. package/README.md +297 -0
  84. package/cli/commands/init.js +471 -0
  85. package/cli/commands/migrate.js +132 -0
  86. package/cli/commands/protect.js +271 -0
  87. package/cli/commands/scaffold.js +48 -0
  88. package/cli/commands/status.js +133 -0
  89. package/cli/commands/sync.js +101 -0
  90. package/cli/commands/uninstall.js +207 -0
  91. package/cli/index.js +111 -0
  92. package/cli/lib/config-gen.js +214 -0
  93. package/cli/lib/resolve-framework.js +97 -0
  94. package/cli/lib/state.js +140 -0
  95. package/cli/lib/symlinks.js +260 -0
  96. package/docs/AUTOMATION-SYSTEMS.md +484 -0
  97. package/docs/BINARY-PATCHING.md +212 -0
  98. package/docs/CHANGELOG.md +2830 -0
  99. package/docs/CREDENTIAL-DETECTION.md +151 -0
  100. package/docs/CTO-DASHBOARD.md +476 -0
  101. package/docs/DEPLOYMENT-FLOW.md +477 -0
  102. package/docs/DEVELOPER.md +116 -0
  103. package/docs/Executive.md +372 -0
  104. package/docs/SECRET-PATHS.md +77 -0
  105. package/docs/SETUP-GUIDE.md +419 -0
  106. package/docs/STACK.md +109 -0
  107. package/docs/TESTING.md +440 -0
  108. package/docs/assets/claude-logo.svg +3 -0
  109. package/docs/sessions/2026-01-24-spec-suite-implementation.md +190 -0
  110. package/docs/sessions/2026-02-15-feedback-e2e-audit.md +484 -0
  111. package/docs/sessions/2026-02-20-credential-rotation-experiments.md +340 -0
  112. package/docs/sessions/TEST-COVERAGE-REPORT-2026-02-20.md +168 -0
  113. package/docs/shared/EPHEMERAL-STATE-FILES.md +115 -0
  114. package/docs/shared/PROTECTION-SYSTEM.md +341 -0
  115. package/husky/post-commit +10 -0
  116. package/husky/pre-commit +40 -0
  117. package/husky/pre-push +94 -0
  118. package/package.json +43 -0
  119. package/packages/cto-dashboard/package-lock.json +3510 -0
  120. package/packages/cto-dashboard/package.json +41 -0
  121. package/packages/cto-dashboard/pnpm-lock.yaml +2168 -0
  122. package/packages/mcp-servers/dist/__testUtils__/fixtures.d.ts +220 -0
  123. package/packages/mcp-servers/dist/__testUtils__/fixtures.d.ts.map +1 -0
  124. package/packages/mcp-servers/dist/__testUtils__/fixtures.js +376 -0
  125. package/packages/mcp-servers/dist/__testUtils__/fixtures.js.map +1 -0
  126. package/packages/mcp-servers/dist/__testUtils__/index.d.ts +121 -0
  127. package/packages/mcp-servers/dist/__testUtils__/index.d.ts.map +1 -0
  128. package/packages/mcp-servers/dist/__testUtils__/index.js +180 -0
  129. package/packages/mcp-servers/dist/__testUtils__/index.js.map +1 -0
  130. package/packages/mcp-servers/dist/__testUtils__/schemas.d.ts +84 -0
  131. package/packages/mcp-servers/dist/__testUtils__/schemas.d.ts.map +1 -0
  132. package/packages/mcp-servers/dist/__testUtils__/schemas.js +309 -0
  133. package/packages/mcp-servers/dist/__testUtils__/schemas.js.map +1 -0
  134. package/packages/mcp-servers/dist/agent-reports/index.d.ts +7 -0
  135. package/packages/mcp-servers/dist/agent-reports/index.d.ts.map +1 -0
  136. package/packages/mcp-servers/dist/agent-reports/index.js +8 -0
  137. package/packages/mcp-servers/dist/agent-reports/index.js.map +1 -0
  138. package/packages/mcp-servers/dist/agent-reports/server.d.ts +22 -0
  139. package/packages/mcp-servers/dist/agent-reports/server.d.ts.map +1 -0
  140. package/packages/mcp-servers/dist/agent-reports/server.js +535 -0
  141. package/packages/mcp-servers/dist/agent-reports/server.js.map +1 -0
  142. package/packages/mcp-servers/dist/agent-reports/types.d.ts +258 -0
  143. package/packages/mcp-servers/dist/agent-reports/types.d.ts.map +1 -0
  144. package/packages/mcp-servers/dist/agent-reports/types.js +81 -0
  145. package/packages/mcp-servers/dist/agent-reports/types.js.map +1 -0
  146. package/packages/mcp-servers/dist/agent-tracker/index.d.ts +5 -0
  147. package/packages/mcp-servers/dist/agent-tracker/index.d.ts.map +1 -0
  148. package/packages/mcp-servers/dist/agent-tracker/index.js +5 -0
  149. package/packages/mcp-servers/dist/agent-tracker/index.js.map +1 -0
  150. package/packages/mcp-servers/dist/agent-tracker/server.d.ts +12 -0
  151. package/packages/mcp-servers/dist/agent-tracker/server.d.ts.map +1 -0
  152. package/packages/mcp-servers/dist/agent-tracker/server.js +919 -0
  153. package/packages/mcp-servers/dist/agent-tracker/server.js.map +1 -0
  154. package/packages/mcp-servers/dist/agent-tracker/types.d.ts +328 -0
  155. package/packages/mcp-servers/dist/agent-tracker/types.d.ts.map +1 -0
  156. package/packages/mcp-servers/dist/agent-tracker/types.js +128 -0
  157. package/packages/mcp-servers/dist/agent-tracker/types.js.map +1 -0
  158. package/packages/mcp-servers/dist/chrome-bridge/browser-tips.d.ts +27 -0
  159. package/packages/mcp-servers/dist/chrome-bridge/browser-tips.d.ts.map +1 -0
  160. package/packages/mcp-servers/dist/chrome-bridge/browser-tips.js +167 -0
  161. package/packages/mcp-servers/dist/chrome-bridge/browser-tips.js.map +1 -0
  162. package/packages/mcp-servers/dist/chrome-bridge/index.d.ts +6 -0
  163. package/packages/mcp-servers/dist/chrome-bridge/index.d.ts.map +1 -0
  164. package/packages/mcp-servers/dist/chrome-bridge/index.js +6 -0
  165. package/packages/mcp-servers/dist/chrome-bridge/index.js.map +1 -0
  166. package/packages/mcp-servers/dist/chrome-bridge/server.d.ts +13 -0
  167. package/packages/mcp-servers/dist/chrome-bridge/server.d.ts.map +1 -0
  168. package/packages/mcp-servers/dist/chrome-bridge/server.js +959 -0
  169. package/packages/mcp-servers/dist/chrome-bridge/server.js.map +1 -0
  170. package/packages/mcp-servers/dist/chrome-bridge/types.d.ts +41 -0
  171. package/packages/mcp-servers/dist/chrome-bridge/types.d.ts.map +1 -0
  172. package/packages/mcp-servers/dist/chrome-bridge/types.js +8 -0
  173. package/packages/mcp-servers/dist/chrome-bridge/types.js.map +1 -0
  174. package/packages/mcp-servers/dist/cloudflare/index.d.ts +8 -0
  175. package/packages/mcp-servers/dist/cloudflare/index.d.ts.map +1 -0
  176. package/packages/mcp-servers/dist/cloudflare/index.js +8 -0
  177. package/packages/mcp-servers/dist/cloudflare/index.js.map +1 -0
  178. package/packages/mcp-servers/dist/cloudflare/server.d.ts +16 -0
  179. package/packages/mcp-servers/dist/cloudflare/server.d.ts.map +1 -0
  180. package/packages/mcp-servers/dist/cloudflare/server.js +253 -0
  181. package/packages/mcp-servers/dist/cloudflare/server.js.map +1 -0
  182. package/packages/mcp-servers/dist/cloudflare/types.d.ts +141 -0
  183. package/packages/mcp-servers/dist/cloudflare/types.d.ts.map +1 -0
  184. package/packages/mcp-servers/dist/cloudflare/types.js +53 -0
  185. package/packages/mcp-servers/dist/cloudflare/types.js.map +1 -0
  186. package/packages/mcp-servers/dist/codecov/index.d.ts +7 -0
  187. package/packages/mcp-servers/dist/codecov/index.d.ts.map +1 -0
  188. package/packages/mcp-servers/dist/codecov/index.js +7 -0
  189. package/packages/mcp-servers/dist/codecov/index.js.map +1 -0
  190. package/packages/mcp-servers/dist/codecov/server.d.ts +21 -0
  191. package/packages/mcp-servers/dist/codecov/server.d.ts.map +1 -0
  192. package/packages/mcp-servers/dist/codecov/server.js +376 -0
  193. package/packages/mcp-servers/dist/codecov/server.js.map +1 -0
  194. package/packages/mcp-servers/dist/codecov/types.d.ts +269 -0
  195. package/packages/mcp-servers/dist/codecov/types.d.ts.map +1 -0
  196. package/packages/mcp-servers/dist/codecov/types.js +128 -0
  197. package/packages/mcp-servers/dist/codecov/types.js.map +1 -0
  198. package/packages/mcp-servers/dist/cto-report/index.d.ts +9 -0
  199. package/packages/mcp-servers/dist/cto-report/index.d.ts.map +1 -0
  200. package/packages/mcp-servers/dist/cto-report/index.js +9 -0
  201. package/packages/mcp-servers/dist/cto-report/index.js.map +1 -0
  202. package/packages/mcp-servers/dist/cto-report/server.d.ts +14 -0
  203. package/packages/mcp-servers/dist/cto-report/server.d.ts.map +1 -0
  204. package/packages/mcp-servers/dist/cto-report/server.js +859 -0
  205. package/packages/mcp-servers/dist/cto-report/server.js.map +1 -0
  206. package/packages/mcp-servers/dist/cto-report/types.d.ts +213 -0
  207. package/packages/mcp-servers/dist/cto-report/types.d.ts.map +1 -0
  208. package/packages/mcp-servers/dist/cto-report/types.js +29 -0
  209. package/packages/mcp-servers/dist/cto-report/types.js.map +1 -0
  210. package/packages/mcp-servers/dist/cto-reports/index.d.ts +7 -0
  211. package/packages/mcp-servers/dist/cto-reports/index.d.ts.map +1 -0
  212. package/packages/mcp-servers/dist/cto-reports/index.js +8 -0
  213. package/packages/mcp-servers/dist/cto-reports/index.js.map +1 -0
  214. package/packages/mcp-servers/dist/cto-reports/server.d.ts +20 -0
  215. package/packages/mcp-servers/dist/cto-reports/server.d.ts.map +1 -0
  216. package/packages/mcp-servers/dist/cto-reports/server.js +538 -0
  217. package/packages/mcp-servers/dist/cto-reports/server.js.map +1 -0
  218. package/packages/mcp-servers/dist/cto-reports/types.d.ts +236 -0
  219. package/packages/mcp-servers/dist/cto-reports/types.d.ts.map +1 -0
  220. package/packages/mcp-servers/dist/cto-reports/types.js +77 -0
  221. package/packages/mcp-servers/dist/cto-reports/types.js.map +1 -0
  222. package/packages/mcp-servers/dist/deputy-cto/index.d.ts +7 -0
  223. package/packages/mcp-servers/dist/deputy-cto/index.d.ts.map +1 -0
  224. package/packages/mcp-servers/dist/deputy-cto/index.js +8 -0
  225. package/packages/mcp-servers/dist/deputy-cto/index.js.map +1 -0
  226. package/packages/mcp-servers/dist/deputy-cto/server.d.ts +23 -0
  227. package/packages/mcp-servers/dist/deputy-cto/server.d.ts.map +1 -0
  228. package/packages/mcp-servers/dist/deputy-cto/server.js +1700 -0
  229. package/packages/mcp-servers/dist/deputy-cto/server.js.map +1 -0
  230. package/packages/mcp-servers/dist/deputy-cto/types.d.ts +439 -0
  231. package/packages/mcp-servers/dist/deputy-cto/types.d.ts.map +1 -0
  232. package/packages/mcp-servers/dist/deputy-cto/types.js +102 -0
  233. package/packages/mcp-servers/dist/deputy-cto/types.js.map +1 -0
  234. package/packages/mcp-servers/dist/elastic-logs/index.d.ts +5 -0
  235. package/packages/mcp-servers/dist/elastic-logs/index.d.ts.map +1 -0
  236. package/packages/mcp-servers/dist/elastic-logs/index.js +5 -0
  237. package/packages/mcp-servers/dist/elastic-logs/index.js.map +1 -0
  238. package/packages/mcp-servers/dist/elastic-logs/server.d.ts +18 -0
  239. package/packages/mcp-servers/dist/elastic-logs/server.d.ts.map +1 -0
  240. package/packages/mcp-servers/dist/elastic-logs/server.js +259 -0
  241. package/packages/mcp-servers/dist/elastic-logs/server.js.map +1 -0
  242. package/packages/mcp-servers/dist/elastic-logs/types.d.ts +107 -0
  243. package/packages/mcp-servers/dist/elastic-logs/types.d.ts.map +1 -0
  244. package/packages/mcp-servers/dist/elastic-logs/types.js +31 -0
  245. package/packages/mcp-servers/dist/elastic-logs/types.js.map +1 -0
  246. package/packages/mcp-servers/dist/feedback-explorer/index.d.ts +2 -0
  247. package/packages/mcp-servers/dist/feedback-explorer/index.d.ts.map +1 -0
  248. package/packages/mcp-servers/dist/feedback-explorer/index.js +2 -0
  249. package/packages/mcp-servers/dist/feedback-explorer/index.js.map +1 -0
  250. package/packages/mcp-servers/dist/feedback-explorer/server.d.ts +21 -0
  251. package/packages/mcp-servers/dist/feedback-explorer/server.d.ts.map +1 -0
  252. package/packages/mcp-servers/dist/feedback-explorer/server.js +580 -0
  253. package/packages/mcp-servers/dist/feedback-explorer/server.js.map +1 -0
  254. package/packages/mcp-servers/dist/feedback-explorer/types.d.ts +331 -0
  255. package/packages/mcp-servers/dist/feedback-explorer/types.d.ts.map +1 -0
  256. package/packages/mcp-servers/dist/feedback-explorer/types.js +40 -0
  257. package/packages/mcp-servers/dist/feedback-explorer/types.js.map +1 -0
  258. package/packages/mcp-servers/dist/feedback-reporter/index.d.ts +9 -0
  259. package/packages/mcp-servers/dist/feedback-reporter/index.d.ts.map +1 -0
  260. package/packages/mcp-servers/dist/feedback-reporter/index.js +9 -0
  261. package/packages/mcp-servers/dist/feedback-reporter/index.js.map +1 -0
  262. package/packages/mcp-servers/dist/feedback-reporter/server.d.ts +36 -0
  263. package/packages/mcp-servers/dist/feedback-reporter/server.d.ts.map +1 -0
  264. package/packages/mcp-servers/dist/feedback-reporter/server.js +392 -0
  265. package/packages/mcp-servers/dist/feedback-reporter/server.js.map +1 -0
  266. package/packages/mcp-servers/dist/feedback-reporter/types.d.ts +152 -0
  267. package/packages/mcp-servers/dist/feedback-reporter/types.d.ts.map +1 -0
  268. package/packages/mcp-servers/dist/feedback-reporter/types.js +67 -0
  269. package/packages/mcp-servers/dist/feedback-reporter/types.js.map +1 -0
  270. package/packages/mcp-servers/dist/github/index.d.ts +7 -0
  271. package/packages/mcp-servers/dist/github/index.d.ts.map +1 -0
  272. package/packages/mcp-servers/dist/github/index.js +7 -0
  273. package/packages/mcp-servers/dist/github/index.js.map +1 -0
  274. package/packages/mcp-servers/dist/github/server.d.ts +15 -0
  275. package/packages/mcp-servers/dist/github/server.d.ts.map +1 -0
  276. package/packages/mcp-servers/dist/github/server.js +686 -0
  277. package/packages/mcp-servers/dist/github/server.js.map +1 -0
  278. package/packages/mcp-servers/dist/github/types.d.ts +660 -0
  279. package/packages/mcp-servers/dist/github/types.d.ts.map +1 -0
  280. package/packages/mcp-servers/dist/github/types.js +209 -0
  281. package/packages/mcp-servers/dist/github/types.js.map +1 -0
  282. package/packages/mcp-servers/dist/index.d.ts +30 -0
  283. package/packages/mcp-servers/dist/index.d.ts.map +1 -0
  284. package/packages/mcp-servers/dist/index.js +32 -0
  285. package/packages/mcp-servers/dist/index.js.map +1 -0
  286. package/packages/mcp-servers/dist/makerkit-docs/index.d.ts +5 -0
  287. package/packages/mcp-servers/dist/makerkit-docs/index.d.ts.map +1 -0
  288. package/packages/mcp-servers/dist/makerkit-docs/index.js +5 -0
  289. package/packages/mcp-servers/dist/makerkit-docs/index.js.map +1 -0
  290. package/packages/mcp-servers/dist/makerkit-docs/server.d.ts +15 -0
  291. package/packages/mcp-servers/dist/makerkit-docs/server.d.ts.map +1 -0
  292. package/packages/mcp-servers/dist/makerkit-docs/server.js +252 -0
  293. package/packages/mcp-servers/dist/makerkit-docs/server.js.map +1 -0
  294. package/packages/mcp-servers/dist/makerkit-docs/types.d.ts +74 -0
  295. package/packages/mcp-servers/dist/makerkit-docs/types.d.ts.map +1 -0
  296. package/packages/mcp-servers/dist/makerkit-docs/types.js +20 -0
  297. package/packages/mcp-servers/dist/makerkit-docs/types.js.map +1 -0
  298. package/packages/mcp-servers/dist/onepassword/index.d.ts +2 -0
  299. package/packages/mcp-servers/dist/onepassword/index.d.ts.map +1 -0
  300. package/packages/mcp-servers/dist/onepassword/index.js +2 -0
  301. package/packages/mcp-servers/dist/onepassword/index.js.map +1 -0
  302. package/packages/mcp-servers/dist/onepassword/server.d.ts +2 -0
  303. package/packages/mcp-servers/dist/onepassword/server.d.ts.map +1 -0
  304. package/packages/mcp-servers/dist/onepassword/server.js +159 -0
  305. package/packages/mcp-servers/dist/onepassword/server.js.map +1 -0
  306. package/packages/mcp-servers/dist/onepassword/types.d.ts +55 -0
  307. package/packages/mcp-servers/dist/onepassword/types.d.ts.map +1 -0
  308. package/packages/mcp-servers/dist/onepassword/types.js +22 -0
  309. package/packages/mcp-servers/dist/onepassword/types.js.map +1 -0
  310. package/packages/mcp-servers/dist/playwright/helpers.d.ts +20 -0
  311. package/packages/mcp-servers/dist/playwright/helpers.d.ts.map +1 -0
  312. package/packages/mcp-servers/dist/playwright/helpers.js +31 -0
  313. package/packages/mcp-servers/dist/playwright/helpers.js.map +1 -0
  314. package/packages/mcp-servers/dist/playwright/index.d.ts +5 -0
  315. package/packages/mcp-servers/dist/playwright/index.d.ts.map +1 -0
  316. package/packages/mcp-servers/dist/playwright/index.js +5 -0
  317. package/packages/mcp-servers/dist/playwright/index.js.map +1 -0
  318. package/packages/mcp-servers/dist/playwright/server.d.ts +13 -0
  319. package/packages/mcp-servers/dist/playwright/server.d.ts.map +1 -0
  320. package/packages/mcp-servers/dist/playwright/server.js +1201 -0
  321. package/packages/mcp-servers/dist/playwright/server.js.map +1 -0
  322. package/packages/mcp-servers/dist/playwright/types.d.ts +216 -0
  323. package/packages/mcp-servers/dist/playwright/types.d.ts.map +1 -0
  324. package/packages/mcp-servers/dist/playwright/types.js +172 -0
  325. package/packages/mcp-servers/dist/playwright/types.js.map +1 -0
  326. package/packages/mcp-servers/dist/playwright-feedback/browser-manager.d.ts +39 -0
  327. package/packages/mcp-servers/dist/playwright-feedback/browser-manager.d.ts.map +1 -0
  328. package/packages/mcp-servers/dist/playwright-feedback/browser-manager.js +71 -0
  329. package/packages/mcp-servers/dist/playwright-feedback/browser-manager.js.map +1 -0
  330. package/packages/mcp-servers/dist/playwright-feedback/index.d.ts +5 -0
  331. package/packages/mcp-servers/dist/playwright-feedback/index.d.ts.map +1 -0
  332. package/packages/mcp-servers/dist/playwright-feedback/index.js +5 -0
  333. package/packages/mcp-servers/dist/playwright-feedback/index.js.map +1 -0
  334. package/packages/mcp-servers/dist/playwright-feedback/server.d.ts +34 -0
  335. package/packages/mcp-servers/dist/playwright-feedback/server.d.ts.map +1 -0
  336. package/packages/mcp-servers/dist/playwright-feedback/server.js +538 -0
  337. package/packages/mcp-servers/dist/playwright-feedback/server.js.map +1 -0
  338. package/packages/mcp-servers/dist/playwright-feedback/types.d.ts +305 -0
  339. package/packages/mcp-servers/dist/playwright-feedback/types.d.ts.map +1 -0
  340. package/packages/mcp-servers/dist/playwright-feedback/types.js +123 -0
  341. package/packages/mcp-servers/dist/playwright-feedback/types.js.map +1 -0
  342. package/packages/mcp-servers/dist/product-manager/server.d.ts +17 -0
  343. package/packages/mcp-servers/dist/product-manager/server.d.ts.map +1 -0
  344. package/packages/mcp-servers/dist/product-manager/server.js +690 -0
  345. package/packages/mcp-servers/dist/product-manager/server.js.map +1 -0
  346. package/packages/mcp-servers/dist/product-manager/types.d.ts +286 -0
  347. package/packages/mcp-servers/dist/product-manager/types.d.ts.map +1 -0
  348. package/packages/mcp-servers/dist/product-manager/types.js +99 -0
  349. package/packages/mcp-servers/dist/product-manager/types.js.map +1 -0
  350. package/packages/mcp-servers/dist/programmatic-feedback/index.d.ts +7 -0
  351. package/packages/mcp-servers/dist/programmatic-feedback/index.d.ts.map +1 -0
  352. package/packages/mcp-servers/dist/programmatic-feedback/index.js +7 -0
  353. package/packages/mcp-servers/dist/programmatic-feedback/index.js.map +1 -0
  354. package/packages/mcp-servers/dist/programmatic-feedback/sandbox.d.ts +19 -0
  355. package/packages/mcp-servers/dist/programmatic-feedback/sandbox.d.ts.map +1 -0
  356. package/packages/mcp-servers/dist/programmatic-feedback/sandbox.js +174 -0
  357. package/packages/mcp-servers/dist/programmatic-feedback/sandbox.js.map +1 -0
  358. package/packages/mcp-servers/dist/programmatic-feedback/server.d.ts +35 -0
  359. package/packages/mcp-servers/dist/programmatic-feedback/server.d.ts.map +1 -0
  360. package/packages/mcp-servers/dist/programmatic-feedback/server.js +465 -0
  361. package/packages/mcp-servers/dist/programmatic-feedback/server.js.map +1 -0
  362. package/packages/mcp-servers/dist/programmatic-feedback/types.d.ts +127 -0
  363. package/packages/mcp-servers/dist/programmatic-feedback/types.d.ts.map +1 -0
  364. package/packages/mcp-servers/dist/programmatic-feedback/types.js +80 -0
  365. package/packages/mcp-servers/dist/programmatic-feedback/types.js.map +1 -0
  366. package/packages/mcp-servers/dist/render/index.d.ts +8 -0
  367. package/packages/mcp-servers/dist/render/index.d.ts.map +1 -0
  368. package/packages/mcp-servers/dist/render/index.js +8 -0
  369. package/packages/mcp-servers/dist/render/index.js.map +1 -0
  370. package/packages/mcp-servers/dist/render/server.d.ts +15 -0
  371. package/packages/mcp-servers/dist/render/server.d.ts.map +1 -0
  372. package/packages/mcp-servers/dist/render/server.js +428 -0
  373. package/packages/mcp-servers/dist/render/server.js.map +1 -0
  374. package/packages/mcp-servers/dist/render/types.d.ts +273 -0
  375. package/packages/mcp-servers/dist/render/types.d.ts.map +1 -0
  376. package/packages/mcp-servers/dist/render/types.js +102 -0
  377. package/packages/mcp-servers/dist/render/types.js.map +1 -0
  378. package/packages/mcp-servers/dist/resend/index.d.ts +7 -0
  379. package/packages/mcp-servers/dist/resend/index.d.ts.map +1 -0
  380. package/packages/mcp-servers/dist/resend/index.js +7 -0
  381. package/packages/mcp-servers/dist/resend/index.js.map +1 -0
  382. package/packages/mcp-servers/dist/resend/server.d.ts +15 -0
  383. package/packages/mcp-servers/dist/resend/server.d.ts.map +1 -0
  384. package/packages/mcp-servers/dist/resend/server.js +298 -0
  385. package/packages/mcp-servers/dist/resend/server.js.map +1 -0
  386. package/packages/mcp-servers/dist/resend/types.d.ts +222 -0
  387. package/packages/mcp-servers/dist/resend/types.d.ts.map +1 -0
  388. package/packages/mcp-servers/dist/resend/types.js +58 -0
  389. package/packages/mcp-servers/dist/resend/types.js.map +1 -0
  390. package/packages/mcp-servers/dist/review-queue/index.d.ts +6 -0
  391. package/packages/mcp-servers/dist/review-queue/index.d.ts.map +1 -0
  392. package/packages/mcp-servers/dist/review-queue/index.js +6 -0
  393. package/packages/mcp-servers/dist/review-queue/index.js.map +1 -0
  394. package/packages/mcp-servers/dist/review-queue/server.d.ts +17 -0
  395. package/packages/mcp-servers/dist/review-queue/server.d.ts.map +1 -0
  396. package/packages/mcp-servers/dist/review-queue/server.js +348 -0
  397. package/packages/mcp-servers/dist/review-queue/server.js.map +1 -0
  398. package/packages/mcp-servers/dist/review-queue/types.d.ts +162 -0
  399. package/packages/mcp-servers/dist/review-queue/types.d.ts.map +1 -0
  400. package/packages/mcp-servers/dist/review-queue/types.js +56 -0
  401. package/packages/mcp-servers/dist/review-queue/types.js.map +1 -0
  402. package/packages/mcp-servers/dist/secret-sync/server.d.ts +19 -0
  403. package/packages/mcp-servers/dist/secret-sync/server.d.ts.map +1 -0
  404. package/packages/mcp-servers/dist/secret-sync/server.js +1139 -0
  405. package/packages/mcp-servers/dist/secret-sync/server.js.map +1 -0
  406. package/packages/mcp-servers/dist/secret-sync/types.d.ts +442 -0
  407. package/packages/mcp-servers/dist/secret-sync/types.d.ts.map +1 -0
  408. package/packages/mcp-servers/dist/secret-sync/types.js +113 -0
  409. package/packages/mcp-servers/dist/secret-sync/types.js.map +1 -0
  410. package/packages/mcp-servers/dist/session-events/index.d.ts +5 -0
  411. package/packages/mcp-servers/dist/session-events/index.d.ts.map +1 -0
  412. package/packages/mcp-servers/dist/session-events/index.js +5 -0
  413. package/packages/mcp-servers/dist/session-events/index.js.map +1 -0
  414. package/packages/mcp-servers/dist/session-events/server.d.ts +11 -0
  415. package/packages/mcp-servers/dist/session-events/server.d.ts.map +1 -0
  416. package/packages/mcp-servers/dist/session-events/server.js +290 -0
  417. package/packages/mcp-servers/dist/session-events/server.js.map +1 -0
  418. package/packages/mcp-servers/dist/session-events/types.d.ts +213 -0
  419. package/packages/mcp-servers/dist/session-events/types.d.ts.map +1 -0
  420. package/packages/mcp-servers/dist/session-events/types.js +69 -0
  421. package/packages/mcp-servers/dist/session-events/types.js.map +1 -0
  422. package/packages/mcp-servers/dist/session-restart/index.d.ts +9 -0
  423. package/packages/mcp-servers/dist/session-restart/index.d.ts.map +1 -0
  424. package/packages/mcp-servers/dist/session-restart/index.js +9 -0
  425. package/packages/mcp-servers/dist/session-restart/index.js.map +1 -0
  426. package/packages/mcp-servers/dist/session-restart/server.d.ts +20 -0
  427. package/packages/mcp-servers/dist/session-restart/server.d.ts.map +1 -0
  428. package/packages/mcp-servers/dist/session-restart/server.js +411 -0
  429. package/packages/mcp-servers/dist/session-restart/server.js.map +1 -0
  430. package/packages/mcp-servers/dist/session-restart/types.d.ts +26 -0
  431. package/packages/mcp-servers/dist/session-restart/types.d.ts.map +1 -0
  432. package/packages/mcp-servers/dist/session-restart/types.js +16 -0
  433. package/packages/mcp-servers/dist/session-restart/types.js.map +1 -0
  434. package/packages/mcp-servers/dist/setup-helper/index.d.ts +5 -0
  435. package/packages/mcp-servers/dist/setup-helper/index.d.ts.map +1 -0
  436. package/packages/mcp-servers/dist/setup-helper/index.js +5 -0
  437. package/packages/mcp-servers/dist/setup-helper/index.js.map +1 -0
  438. package/packages/mcp-servers/dist/setup-helper/server.d.ts +14 -0
  439. package/packages/mcp-servers/dist/setup-helper/server.d.ts.map +1 -0
  440. package/packages/mcp-servers/dist/setup-helper/server.js +454 -0
  441. package/packages/mcp-servers/dist/setup-helper/server.js.map +1 -0
  442. package/packages/mcp-servers/dist/setup-helper/types.d.ts +81 -0
  443. package/packages/mcp-servers/dist/setup-helper/types.d.ts.map +1 -0
  444. package/packages/mcp-servers/dist/setup-helper/types.js +41 -0
  445. package/packages/mcp-servers/dist/setup-helper/types.js.map +1 -0
  446. package/packages/mcp-servers/dist/shared/audited-server.d.ts +31 -0
  447. package/packages/mcp-servers/dist/shared/audited-server.d.ts.map +1 -0
  448. package/packages/mcp-servers/dist/shared/audited-server.js +126 -0
  449. package/packages/mcp-servers/dist/shared/audited-server.js.map +1 -0
  450. package/packages/mcp-servers/dist/shared/constants.d.ts +26 -0
  451. package/packages/mcp-servers/dist/shared/constants.d.ts.map +1 -0
  452. package/packages/mcp-servers/dist/shared/constants.js +41 -0
  453. package/packages/mcp-servers/dist/shared/constants.js.map +1 -0
  454. package/packages/mcp-servers/dist/shared/index.d.ts +6 -0
  455. package/packages/mcp-servers/dist/shared/index.d.ts.map +1 -0
  456. package/packages/mcp-servers/dist/shared/index.js +6 -0
  457. package/packages/mcp-servers/dist/shared/index.js.map +1 -0
  458. package/packages/mcp-servers/dist/shared/readonly-db.d.ts +11 -0
  459. package/packages/mcp-servers/dist/shared/readonly-db.d.ts.map +1 -0
  460. package/packages/mcp-servers/dist/shared/readonly-db.js +47 -0
  461. package/packages/mcp-servers/dist/shared/readonly-db.js.map +1 -0
  462. package/packages/mcp-servers/dist/shared/resolve-framework.d.ts +20 -0
  463. package/packages/mcp-servers/dist/shared/resolve-framework.d.ts.map +1 -0
  464. package/packages/mcp-servers/dist/shared/resolve-framework.js +65 -0
  465. package/packages/mcp-servers/dist/shared/resolve-framework.js.map +1 -0
  466. package/packages/mcp-servers/dist/shared/server.d.ts +86 -0
  467. package/packages/mcp-servers/dist/shared/server.d.ts.map +1 -0
  468. package/packages/mcp-servers/dist/shared/server.js +291 -0
  469. package/packages/mcp-servers/dist/shared/server.js.map +1 -0
  470. package/packages/mcp-servers/dist/shared/types.d.ts +113 -0
  471. package/packages/mcp-servers/dist/shared/types.d.ts.map +1 -0
  472. package/packages/mcp-servers/dist/shared/types.js +36 -0
  473. package/packages/mcp-servers/dist/shared/types.js.map +1 -0
  474. package/packages/mcp-servers/dist/show/server.d.ts +12 -0
  475. package/packages/mcp-servers/dist/show/server.d.ts.map +1 -0
  476. package/packages/mcp-servers/dist/show/server.js +97 -0
  477. package/packages/mcp-servers/dist/show/server.js.map +1 -0
  478. package/packages/mcp-servers/dist/show/types.d.ts +19 -0
  479. package/packages/mcp-servers/dist/show/types.d.ts.map +1 -0
  480. package/packages/mcp-servers/dist/show/types.js +32 -0
  481. package/packages/mcp-servers/dist/show/types.js.map +1 -0
  482. package/packages/mcp-servers/dist/specs-browser/index.d.ts +5 -0
  483. package/packages/mcp-servers/dist/specs-browser/index.d.ts.map +1 -0
  484. package/packages/mcp-servers/dist/specs-browser/index.js +5 -0
  485. package/packages/mcp-servers/dist/specs-browser/index.js.map +1 -0
  486. package/packages/mcp-servers/dist/specs-browser/server.d.ts +13 -0
  487. package/packages/mcp-servers/dist/specs-browser/server.d.ts.map +1 -0
  488. package/packages/mcp-servers/dist/specs-browser/server.js +692 -0
  489. package/packages/mcp-servers/dist/specs-browser/server.js.map +1 -0
  490. package/packages/mcp-servers/dist/specs-browser/types.d.ts +337 -0
  491. package/packages/mcp-servers/dist/specs-browser/types.d.ts.map +1 -0
  492. package/packages/mcp-servers/dist/specs-browser/types.js +134 -0
  493. package/packages/mcp-servers/dist/specs-browser/types.js.map +1 -0
  494. package/packages/mcp-servers/dist/supabase/index.d.ts +10 -0
  495. package/packages/mcp-servers/dist/supabase/index.d.ts.map +1 -0
  496. package/packages/mcp-servers/dist/supabase/index.js +10 -0
  497. package/packages/mcp-servers/dist/supabase/index.js.map +1 -0
  498. package/packages/mcp-servers/dist/supabase/server.d.ts +20 -0
  499. package/packages/mcp-servers/dist/supabase/server.d.ts.map +1 -0
  500. package/packages/mcp-servers/dist/supabase/server.js +451 -0
  501. package/packages/mcp-servers/dist/supabase/server.js.map +1 -0
  502. package/packages/mcp-servers/dist/supabase/types.d.ts +196 -0
  503. package/packages/mcp-servers/dist/supabase/types.d.ts.map +1 -0
  504. package/packages/mcp-servers/dist/supabase/types.js +76 -0
  505. package/packages/mcp-servers/dist/supabase/types.js.map +1 -0
  506. package/packages/mcp-servers/dist/todo-db/index.d.ts +5 -0
  507. package/packages/mcp-servers/dist/todo-db/index.d.ts.map +1 -0
  508. package/packages/mcp-servers/dist/todo-db/index.js +5 -0
  509. package/packages/mcp-servers/dist/todo-db/index.js.map +1 -0
  510. package/packages/mcp-servers/dist/todo-db/server.d.ts +13 -0
  511. package/packages/mcp-servers/dist/todo-db/server.d.ts.map +1 -0
  512. package/packages/mcp-servers/dist/todo-db/server.js +649 -0
  513. package/packages/mcp-servers/dist/todo-db/server.js.map +1 -0
  514. package/packages/mcp-servers/dist/todo-db/types.d.ts +225 -0
  515. package/packages/mcp-servers/dist/todo-db/types.d.ts.map +1 -0
  516. package/packages/mcp-servers/dist/todo-db/types.js +69 -0
  517. package/packages/mcp-servers/dist/todo-db/types.js.map +1 -0
  518. package/packages/mcp-servers/dist/user-feedback/index.d.ts +7 -0
  519. package/packages/mcp-servers/dist/user-feedback/index.d.ts.map +1 -0
  520. package/packages/mcp-servers/dist/user-feedback/index.js +8 -0
  521. package/packages/mcp-servers/dist/user-feedback/index.js.map +1 -0
  522. package/packages/mcp-servers/dist/user-feedback/server.d.ts +25 -0
  523. package/packages/mcp-servers/dist/user-feedback/server.d.ts.map +1 -0
  524. package/packages/mcp-servers/dist/user-feedback/server.js +914 -0
  525. package/packages/mcp-servers/dist/user-feedback/server.js.map +1 -0
  526. package/packages/mcp-servers/dist/user-feedback/types.d.ts +415 -0
  527. package/packages/mcp-servers/dist/user-feedback/types.d.ts.map +1 -0
  528. package/packages/mcp-servers/dist/user-feedback/types.js +132 -0
  529. package/packages/mcp-servers/dist/user-feedback/types.js.map +1 -0
  530. package/packages/mcp-servers/dist/vercel/index.d.ts +9 -0
  531. package/packages/mcp-servers/dist/vercel/index.d.ts.map +1 -0
  532. package/packages/mcp-servers/dist/vercel/index.js +9 -0
  533. package/packages/mcp-servers/dist/vercel/index.js.map +1 -0
  534. package/packages/mcp-servers/dist/vercel/server.d.ts +17 -0
  535. package/packages/mcp-servers/dist/vercel/server.d.ts.map +1 -0
  536. package/packages/mcp-servers/dist/vercel/server.js +265 -0
  537. package/packages/mcp-servers/dist/vercel/server.js.map +1 -0
  538. package/packages/mcp-servers/dist/vercel/types.d.ts +189 -0
  539. package/packages/mcp-servers/dist/vercel/types.d.ts.map +1 -0
  540. package/packages/mcp-servers/dist/vercel/types.js +65 -0
  541. package/packages/mcp-servers/dist/vercel/types.js.map +1 -0
  542. package/packages/mcp-servers/package-lock.json +3765 -0
  543. package/packages/mcp-servers/package.json +64 -0
  544. package/packages/mcp-servers/test/reporters/test-failure-reporter.ts +372 -0
  545. package/packages/mcp-servers/vitest.config.ts +27 -0
  546. package/scripts/__tests__/README.md +163 -0
  547. package/scripts/apply-credential-hardening.sh +271 -0
  548. package/scripts/credential-providers/manual.js +56 -0
  549. package/scripts/credential-providers/onepassword.js +85 -0
  550. package/scripts/credential-providers/provider-interface.js +104 -0
  551. package/scripts/encrypt-credential.js +337 -0
  552. package/scripts/feedback-launcher.js +338 -0
  553. package/scripts/feedback-orchestrator.js +373 -0
  554. package/scripts/fix-mcp-launcher-issues.sh +97 -0
  555. package/scripts/force-spawn-tasks.js +651 -0
  556. package/scripts/force-triage-reports.js +560 -0
  557. package/scripts/generate-protected-actions-spec.js +142 -0
  558. package/scripts/generate-proxy-certs.sh +158 -0
  559. package/scripts/grant-chrome-ext-permissions.sh +242 -0
  560. package/scripts/mcp-launcher.js +125 -0
  561. package/scripts/merge-settings.cjs +167 -0
  562. package/scripts/patch-clawd.py +844 -0
  563. package/scripts/patch-credential-cache.py +313 -0
  564. package/scripts/patches/credential-file-guard-patched.mjs +573 -0
  565. package/scripts/patches/credential-file-guard.js.patched +573 -0
  566. package/scripts/patches/verify-tokenizer.mjs +132 -0
  567. package/scripts/protect-framework.sh +478 -0
  568. package/scripts/readme-chrome.template +12 -0
  569. package/scripts/reap-completed-agents.js +439 -0
  570. package/scripts/reinstall.sh +86 -0
  571. package/scripts/resign-node.sh +185 -0
  572. package/scripts/rotation-proxy.js +656 -0
  573. package/scripts/rotation-stress-monitor.mjs +862 -0
  574. package/scripts/setup-automation-service.sh +648 -0
  575. package/scripts/setup-check.js +251 -0
  576. package/scripts/watch-claude-version.js +142 -0
  577. package/specs/framework/CORE-INVARIANTS.md +161 -0
  578. package/specs/patterns/AGENT-PATTERNS.md +223 -0
  579. package/specs/patterns/HOOK-PATTERNS.md +242 -0
  580. package/specs/patterns/MCP-SERVER-PATTERNS.md +144 -0
  581. package/templates/config/gitignore.template +14 -0
  582. package/templates/config/merge-chain-check.yml.template +51 -0
  583. package/templates/config/package.json.template +18 -0
  584. package/templates/config/pnpm-workspace.yaml +5 -0
  585. package/templates/config/services.json.template +18 -0
  586. package/templates/config/tsconfig.base.json +17 -0
  587. package/templates/scaffold/integrations/_template/.gitkeep +0 -0
  588. package/templates/scaffold/packages/logger/package.json +17 -0
  589. package/templates/scaffold/packages/logger/src/logger.ts +44 -0
  590. package/templates/scaffold/packages/shared/package.json +17 -0
  591. package/templates/scaffold/packages/shared/src/errors.ts +43 -0
  592. package/templates/scaffold/products/_product/apps/backend/package.json +21 -0
  593. package/templates/scaffold/products/_product/apps/backend/src/index.ts +17 -0
  594. package/templates/scaffold/products/_product/apps/extension/.gitkeep +0 -0
  595. package/templates/scaffold/products/_product/apps/web/.gitkeep +0 -0
  596. package/templates/scaffold/specs/global/.gitkeep +0 -0
  597. package/templates/scaffold/specs/local/.gitkeep +0 -0
  598. package/templates/scaffold/specs/reference/.gitkeep +0 -0
  599. package/version.json +15 -0
@@ -0,0 +1,1309 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Compliance Checker System for ODIN
5
+ *
6
+ * Orchestrates batch compliance checking with DUAL ENFORCEMENT MODES:
7
+ *
8
+ * GLOBAL ENFORCEMENT (spec-file-mappings.json):
9
+ * - Validates mapping file
10
+ * - Checks specific files against global specs
11
+ * - Per-file cooldown (7 days default)
12
+ * - Daily agent cap (22 agents default)
13
+ *
14
+ * LOCAL ENFORCEMENT (specs/local/*.md):
15
+ * - No file mappings required
16
+ * - Agent explores codebase freely using Glob/Grep
17
+ * - Per-spec cooldown (7 days default)
18
+ * - Daily agent cap (3 agents default)
19
+ * - One agent per spec file
20
+ *
21
+ * Key Features:
22
+ * - Dual rate limiting (global + local separate budgets)
23
+ * - Mapping validation with auto-fix/review (global only)
24
+ * - Fire-and-forget post-commit integration
25
+ * - Full enforcement history tracking per mode
26
+ *
27
+ * Exit codes:
28
+ * - 0: Success
29
+ * - 1: Error
30
+ * - 2: Validation failed (mapping file issues)
31
+ *
32
+ * Usage:
33
+ * node compliance-checker.js [--status] [--dry-run]
34
+ * node compliance-checker.js [--global-only] [--local-only]
35
+ * node compliance-checker.js [--history] [--history-global] [--history-local]
36
+ *
37
+ * @author Claude Code Hooks
38
+ * @version 2.0.0
39
+ */
40
+
41
+ import fs from 'fs';
42
+ import path from 'path';
43
+ import { spawn } from 'child_process';
44
+ import { validateMappings, formatValidationResult } from './mapping-validator.js';
45
+ import { registerSpawn, registerHookExecution, AGENT_TYPES, HOOK_TYPES } from './agent-tracker.js';
46
+ import { getCooldown } from './config-reader.js';
47
+
48
+ // Project directory
49
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
50
+
51
+ // Suites config path (optional feature)
52
+ const SUITES_CONFIG_PATH = path.join(projectDir, '.claude/hooks/suites-config.json');
53
+
54
+ // Load configuration
55
+ const configPath = path.join(projectDir, '.claude/hooks/compliance-config.json');
56
+ let CONFIG;
57
+ try {
58
+ const rawConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
59
+ // Backwards compatibility: migrate old flat config to nested structure
60
+ if (!rawConfig.global && !rawConfig.local) {
61
+ CONFIG = {
62
+ global: {
63
+ maxAgentsPerDay: rawConfig.maxAgentsPerDay || 22,
64
+ fileVerificationCooldownDays: rawConfig.fileVerificationCooldownDays || 7,
65
+ mappingReviewCooldownDays: rawConfig.mappingReviewCooldownDays || 7,
66
+ mappingFixCooldownHours: rawConfig.mappingFixCooldownHours || 3
67
+ },
68
+ local: {
69
+ maxAgentsPerDay: rawConfig.local?.maxAgentsPerDay || 3,
70
+ specCooldownDays: rawConfig.local?.specCooldownDays || 7
71
+ },
72
+ autoRunIntervalDays: rawConfig.autoRunIntervalDays || 7,
73
+ concurrency: rawConfig.concurrency || 5,
74
+ mappingFile: rawConfig.mappingFile || '.claude/hooks/spec-file-mappings.json',
75
+ stateFile: rawConfig.stateFile || '.claude/hooks/compliance-state.json',
76
+ logFile: rawConfig.logFile || '.claude/hooks/compliance-log.json',
77
+ specsGlobalDir: rawConfig.specsGlobalDir || 'specs/global',
78
+ specsLocalDir: rawConfig.specsLocalDir || 'specs/local'
79
+ };
80
+ } else {
81
+ CONFIG = rawConfig;
82
+ }
83
+ } catch (err) {
84
+ console.error(`Failed to load compliance-config.json: ${err.message}`);
85
+ process.exit(1);
86
+ }
87
+
88
+ // Overlay dynamic cooldowns from usage-optimizer (allows overdrive to scale these)
89
+ CONFIG.global.fileVerificationCooldownDays = getCooldown('compliance_checker_file', 10080) / (60 * 24);
90
+ CONFIG.local.specCooldownDays = getCooldown('compliance_checker_spec', 10080) / (60 * 24);
91
+
92
+ // State file paths
93
+ const STATE_FILE = path.join(projectDir, CONFIG.stateFile);
94
+ const LOG_FILE = path.join(projectDir, CONFIG.logFile);
95
+ const MAPPING_FILE = path.join(projectDir, CONFIG.mappingFile);
96
+
97
+ // ============================================================================
98
+ // Suite Config Support (Optional Feature)
99
+ // ============================================================================
100
+
101
+ /**
102
+ * Load suites config from suites-config.json
103
+ * Returns null if file doesn't exist (feature is optional)
104
+ */
105
+ function loadSuitesConfig() {
106
+ if (!fs.existsSync(SUITES_CONFIG_PATH)) {
107
+ return null;
108
+ }
109
+ try {
110
+ return JSON.parse(fs.readFileSync(SUITES_CONFIG_PATH, 'utf8'));
111
+ } catch (err) {
112
+ console.error(`Failed to load suites-config.json: ${err.message}`);
113
+ return null;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Simple glob pattern matching (minimatch-like)
119
+ * Supports: *, **, ?
120
+ */
121
+ function matchesGlob(filePath, pattern) {
122
+ // Convert glob pattern to regex
123
+ let regexStr = pattern
124
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape special regex chars
125
+ .replace(/\*\*/g, '{{DOUBLESTAR}}') // Placeholder for **
126
+ .replace(/\*/g, '[^/]*') // * matches anything except /
127
+ .replace(/\?/g, '[^/]') // ? matches single char except /
128
+ .replace(/\{\{DOUBLESTAR\}\}/g, '.*'); // ** matches everything
129
+
130
+ const regex = new RegExp(`^${regexStr}$`);
131
+ return regex.test(filePath);
132
+ }
133
+
134
+ /**
135
+ * Get all suites that match a file path
136
+ * @param {string} filePath - Relative path to check
137
+ * @param {object} suitesConfig - Loaded suites config (or null)
138
+ * @returns {Array<{id: string, ...suite}>} Array of matching suites
139
+ */
140
+ function getSuitesForFile(filePath, suitesConfig) {
141
+ if (!suitesConfig) return [];
142
+
143
+ const matches = [];
144
+ for (const [suiteId, suite] of Object.entries(suitesConfig.suites)) {
145
+ if (!suite.enabled) continue;
146
+ if (matchesGlob(filePath, suite.scope)) {
147
+ matches.push({ id: suiteId, ...suite });
148
+ }
149
+ }
150
+ return matches;
151
+ }
152
+
153
+ /**
154
+ * Get ALL specs that apply to a file (main specs + subspecs)
155
+ * Single function used by enforcement - DRY principle
156
+ *
157
+ * @param {string} filePath - Relative path to check
158
+ * @param {object} mappings - Loaded spec-file-mappings.json
159
+ * @param {object} suitesConfig - Loaded suites config (or null)
160
+ * @returns {Array<{name: string, dir: string, priority: string, lastVerified: string|null, suiteId: string|null, suiteScope: string|null}>}
161
+ */
162
+ function getAllApplicableSpecs(filePath, mappings, suitesConfig) {
163
+ const specs = [];
164
+
165
+ // 1. Main specs from spec-file-mappings.json (existing behavior)
166
+ for (const [specName, specData] of Object.entries(mappings.specs || {})) {
167
+ const fileEntry = specData.files?.find(f => f.path === filePath);
168
+ if (fileEntry) {
169
+ specs.push({
170
+ name: specName,
171
+ dir: CONFIG.specsGlobalDir, // specs/global by default
172
+ priority: specData.priority,
173
+ lastVerified: fileEntry.lastVerified,
174
+ suiteId: null, // null = main spec
175
+ suiteScope: null
176
+ });
177
+ }
178
+ }
179
+
180
+ // 2. Subspecs from matching suites (extension)
181
+ if (suitesConfig) {
182
+ const matchingSuites = getSuitesForFile(filePath, suitesConfig);
183
+ for (const suite of matchingSuites) {
184
+ if (!suite.mappedSpecs) continue;
185
+ const specsDir = path.join(projectDir, suite.mappedSpecs.dir);
186
+ if (!fs.existsSync(specsDir)) continue;
187
+
188
+ const pattern = suite.mappedSpecs.pattern || '*.md';
189
+ const specFiles = fs.readdirSync(specsDir).filter(f => {
190
+ if (!f.endsWith('.md')) return false;
191
+ return matchesGlob(f, pattern);
192
+ });
193
+
194
+ for (const specFile of specFiles) {
195
+ specs.push({
196
+ name: specFile,
197
+ dir: suite.mappedSpecs.dir,
198
+ priority: suite.priority || 'medium',
199
+ lastVerified: null, // TODO: track per-suite cooldowns
200
+ suiteId: suite.id,
201
+ suiteScope: suite.scope
202
+ });
203
+ }
204
+ }
205
+ }
206
+
207
+ return specs;
208
+ }
209
+
210
+ /**
211
+ * Get ALL exploratory specs (main local specs + suite exploratory specs)
212
+ * Single function used by local enforcement - DRY principle
213
+ *
214
+ * @param {object} suitesConfig - Loaded suites config (or null)
215
+ * @returns {Array<{name: string, dir: string, suiteId: string|null, suiteScope: string}>}
216
+ */
217
+ function getAllExploratorySpecs(suitesConfig) {
218
+ const specs = [];
219
+
220
+ // 1. Main local specs (existing behavior)
221
+ const mainLocalDir = path.join(projectDir, CONFIG.specsLocalDir);
222
+ if (fs.existsSync(mainLocalDir)) {
223
+ const files = fs.readdirSync(mainLocalDir).filter(f => f.endsWith('.md'));
224
+ for (const f of files) {
225
+ specs.push({
226
+ name: f,
227
+ dir: CONFIG.specsLocalDir,
228
+ suiteId: null,
229
+ suiteScope: '**/*' // main local specs explore entire codebase
230
+ });
231
+ }
232
+ }
233
+
234
+ // 2. Suite exploratory specs (extension)
235
+ if (suitesConfig) {
236
+ for (const [suiteId, suite] of Object.entries(suitesConfig.suites)) {
237
+ if (!suite.enabled || !suite.exploratorySpecs) continue;
238
+ const specsDir = path.join(projectDir, suite.exploratorySpecs.dir);
239
+ if (!fs.existsSync(specsDir)) continue;
240
+
241
+ const pattern = suite.exploratorySpecs.pattern || '*.md';
242
+ const specFiles = fs.readdirSync(specsDir).filter(f => {
243
+ if (!f.endsWith('.md')) return false;
244
+ return matchesGlob(f, pattern);
245
+ });
246
+
247
+ for (const specFile of specFiles) {
248
+ specs.push({
249
+ name: specFile,
250
+ dir: suite.exploratorySpecs.dir,
251
+ suiteId: suiteId,
252
+ suiteScope: suite.scope // agent explores only within this scope
253
+ });
254
+ }
255
+ }
256
+ }
257
+
258
+ return specs;
259
+ }
260
+
261
+ // ============================================================================
262
+ // Command Line Parsing
263
+ // ============================================================================
264
+
265
+ /**
266
+ * Parse command line arguments
267
+ * @param {string[]} args
268
+ * @returns {object}
269
+ */
270
+ function parseArgs(args) {
271
+ return {
272
+ status: args.includes('--status'),
273
+ dryRun: args.includes('--dry-run'),
274
+ globalOnly: args.includes('--global-only'),
275
+ localOnly: args.includes('--local-only'),
276
+ postCommit: args.includes('--post-commit'),
277
+ history: args.includes('--history'),
278
+ historyGlobal: args.includes('--history-global'),
279
+ historyLocal: args.includes('--history-local')
280
+ };
281
+ }
282
+
283
+ /**
284
+ * Read state file
285
+ * @returns {object}
286
+ */
287
+ function readState() {
288
+ try {
289
+ return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
290
+ } catch {
291
+ // Return default state if file doesn't exist
292
+ return {
293
+ version: 1,
294
+ globalSpecs: { lastRun: null, nextEligible: null },
295
+ localSpecs: { lastRun: null, nextEligible: null, perSpecLastRun: {} }
296
+ };
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Write state file
302
+ * @param {object} state
303
+ */
304
+ function writeState(state) {
305
+ fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), 'utf8');
306
+ }
307
+
308
+ /**
309
+ * Read daily spawn log
310
+ * @returns {object}
311
+ */
312
+ function readLog() {
313
+ try {
314
+ return JSON.parse(fs.readFileSync(LOG_FILE, 'utf8'));
315
+ } catch {
316
+ // Return default log if file doesn't exist
317
+ return {
318
+ version: 1,
319
+ dailySpawns: {},
320
+ history: []
321
+ };
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Write daily spawn log
327
+ * @param {object} log
328
+ */
329
+ function writeLog(log) {
330
+ fs.writeFileSync(LOG_FILE, JSON.stringify(log, null, 2), 'utf8');
331
+ }
332
+
333
+ /**
334
+ * Get date string in YYYY-MM-DD format
335
+ * @param {Date} [date] - Optional date to format (defaults to today)
336
+ * @returns {string}
337
+ */
338
+ function getTodayString(date = null) {
339
+ const now = date || new Date();
340
+ const year = now.getFullYear();
341
+ const month = String(now.getMonth() + 1).padStart(2, '0');
342
+ const day = String(now.getDate()).padStart(2, '0');
343
+ return `${year}-${month}-${day}`;
344
+ }
345
+
346
+ /**
347
+ * Check if daily agent cap has been reached for a specific mode
348
+ * @param {string} mode - 'global' or 'local'
349
+ * @returns {{ allowed: boolean, used: number, limit: number, remaining: number }}
350
+ */
351
+ function checkDailyAgentCap(mode = 'global') {
352
+ const log = readLog();
353
+ const today = getTodayString();
354
+
355
+ // Count spawns for this mode today
356
+ const todaySpawns = log.history.filter(h => {
357
+ const spawnDate = getTodayString(new Date(h.date));
358
+ return spawnDate === today && h.mode === mode;
359
+ });
360
+
361
+ const used = todaySpawns.reduce((sum, h) => sum + h.count, 0);
362
+ const limit = mode === 'global' ? CONFIG.global.maxAgentsPerDay : CONFIG.local.maxAgentsPerDay;
363
+
364
+ return {
365
+ allowed: used < limit,
366
+ used,
367
+ limit,
368
+ remaining: Math.max(0, limit - used)
369
+ };
370
+ }
371
+
372
+ /**
373
+ * Record agent spawns for today
374
+ * @param {string} mode - 'global' or 'local' enforcement mode
375
+ * @param {Array<{spec: string, file: string, priority: string}>} agents - Array of agent details
376
+ */
377
+ function recordAgentSpawns(mode, agents) {
378
+ const log = readLog();
379
+ const today = getTodayString();
380
+
381
+ // Update daily count
382
+ log.dailySpawns[today] = (log.dailySpawns[today] || 0) + agents.length;
383
+
384
+ // Add to history with full details per agent
385
+ log.history.push({
386
+ date: new Date().toISOString(),
387
+ mode,
388
+ count: agents.length,
389
+ agents: agents.map(a => ({
390
+ spec: a.spec,
391
+ file: a.file,
392
+ priority: a.priority
393
+ }))
394
+ });
395
+
396
+ // Keep only last 30 days of daily spawns
397
+ const thirtyDaysAgo = new Date();
398
+ thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
399
+ const cutoffDate = getTodayString(thirtyDaysAgo);
400
+
401
+ const newDailySpawns = {};
402
+ for (const [date, count] of Object.entries(log.dailySpawns)) {
403
+ if (date >= cutoffDate) {
404
+ newDailySpawns[date] = count;
405
+ }
406
+ }
407
+ log.dailySpawns = newDailySpawns;
408
+
409
+ // Keep only last 1000 history entries
410
+ if (log.history.length > 1000) {
411
+ log.history = log.history.slice(-1000);
412
+ }
413
+
414
+ writeLog(log);
415
+ }
416
+
417
+ /**
418
+ * Check if we're within the per-file verification cooldown
419
+ * @param {string} lastVerified - ISO timestamp or null
420
+ * @returns {boolean} true if within cooldown (should skip)
421
+ */
422
+ function isWithinFileCooldown(lastVerified) {
423
+ if (!lastVerified) return false;
424
+
425
+ const lastVerifiedDate = new Date(lastVerified);
426
+ const now = new Date();
427
+ const daysSince = (now - lastVerifiedDate) / (1000 * 60 * 60 * 24);
428
+
429
+ return daysSince < CONFIG.global.fileVerificationCooldownDays;
430
+ }
431
+
432
+ /**
433
+ * Check if we're within the per-spec enforcement cooldown (for local specs)
434
+ * @param {string} specName - Name of the spec file (e.g., 'THOR.md')
435
+ * @param {object} state - Current state object
436
+ * @returns {boolean} true if within cooldown (should skip)
437
+ */
438
+ function isWithinSpecCooldown(specName, state) {
439
+ if (!state.localSpecs.perSpecLastRun) {
440
+ state.localSpecs.perSpecLastRun = {};
441
+ }
442
+
443
+ const lastRun = state.localSpecs.perSpecLastRun[specName];
444
+ if (!lastRun) return false;
445
+
446
+ const lastRunDate = new Date(lastRun);
447
+ const now = new Date();
448
+ const daysSince = (now - lastRunDate) / (1000 * 60 * 60 * 24);
449
+
450
+ return daysSince < CONFIG.local.specCooldownDays;
451
+ }
452
+
453
+ /**
454
+ * Update the last run timestamp for a local spec
455
+ * @param {string} specName - Name of the spec file
456
+ * @param {object} state - Current state object
457
+ */
458
+ function updateSpecCooldown(specName, state) {
459
+ if (!state.localSpecs.perSpecLastRun) {
460
+ state.localSpecs.perSpecLastRun = {};
461
+ }
462
+
463
+ state.localSpecs.perSpecLastRun[specName] = new Date().toISOString();
464
+ writeState(state);
465
+ }
466
+
467
+ /**
468
+ * Update lastVerified timestamp for a file in the mapping
469
+ * @param {string} specName
470
+ * @param {string} filePath
471
+ */
472
+ function updateFileVerificationTimestamp(specName, filePath) {
473
+ try {
474
+ const mappings = JSON.parse(fs.readFileSync(MAPPING_FILE, 'utf8'));
475
+
476
+ if (mappings.specs[specName]) {
477
+ const fileEntry = mappings.specs[specName].files.find(f => f.path === filePath);
478
+ if (fileEntry) {
479
+ fileEntry.lastVerified = new Date().toISOString();
480
+ fs.writeFileSync(MAPPING_FILE, JSON.stringify(mappings, null, 2), 'utf8');
481
+ }
482
+ }
483
+ } catch (err) {
484
+ console.error(`Warning: Failed to update lastVerified for ${filePath}: ${err.message}`);
485
+ }
486
+ }
487
+
488
+ /**
489
+ * Spawn Claude instance (fire-and-forget)
490
+ * @param {string} prompt - Prompt to send
491
+ * @param {object} env - Additional environment variables
492
+ * @param {object} trackingInfo - Agent tracking info (type, description, metadata)
493
+ * @returns {number} PID of spawned process
494
+ */
495
+ function spawnClaudeInstance(prompt, env = {}, trackingInfo = null) {
496
+ // Use type from trackingInfo for [Task][type] format, fallback to 'compliance' for untyped spawns
497
+ const taskType = trackingInfo?.type || 'compliance';
498
+ const taggedPrompt = `[Task][${taskType}] ${prompt}`;
499
+
500
+ // Register spawn with agent tracker if tracking info provided
501
+ if (trackingInfo) {
502
+ registerSpawn({
503
+ type: trackingInfo.type,
504
+ hookType: HOOK_TYPES.COMPLIANCE_CHECKER,
505
+ description: trackingInfo.description,
506
+ prompt: taggedPrompt,
507
+ metadata: trackingInfo.metadata || {},
508
+ projectDir
509
+ });
510
+ }
511
+
512
+ const claude = spawn('claude', [
513
+ '--dangerously-skip-permissions',
514
+ '-p',
515
+ taggedPrompt
516
+ ], {
517
+ detached: true,
518
+ stdio: 'ignore',
519
+ cwd: projectDir,
520
+ env: {
521
+ ...process.env,
522
+ CLAUDE_PROJECT_DIR: projectDir,
523
+ CLAUDE_SPAWNED_SESSION: 'true',
524
+ ...env
525
+ }
526
+ });
527
+
528
+ claude.unref();
529
+ return claude.pid;
530
+ }
531
+
532
+ /**
533
+ * Build prompt from template with variable substitution
534
+ * @param {string} templatePath
535
+ * @param {object} variables
536
+ * @returns {string}
537
+ */
538
+ function buildPrompt(templatePath, variables) {
539
+ let template = fs.readFileSync(templatePath, 'utf8');
540
+
541
+ for (const [key, value] of Object.entries(variables)) {
542
+ const regex = new RegExp(`{{${key}}}`, 'g');
543
+ template = template.replace(regex, value);
544
+ }
545
+
546
+ return template;
547
+ }
548
+
549
+ /**
550
+ * Check if mapping fix spawn is rate limited
551
+ * @returns {{ allowed: boolean, nextEligible: string|null }}
552
+ */
553
+ function checkMappingFixRateLimit() {
554
+ const state = readState();
555
+ const cooldownHours = CONFIG.global.mappingFixCooldownHours || 3;
556
+
557
+ if (!state.mappingFix?.lastSpawn) {
558
+ return { allowed: true, nextEligible: null };
559
+ }
560
+
561
+ const lastSpawn = new Date(state.mappingFix.lastSpawn);
562
+ const now = new Date();
563
+ const hoursSinceSpawn = (now - lastSpawn) / (1000 * 60 * 60);
564
+
565
+ if (hoursSinceSpawn >= cooldownHours) {
566
+ return { allowed: true, nextEligible: null };
567
+ }
568
+
569
+ const nextEligible = new Date(lastSpawn.getTime() + cooldownHours * 60 * 60 * 1000);
570
+ return {
571
+ allowed: false,
572
+ nextEligible: nextEligible.toISOString(),
573
+ hoursRemaining: Math.ceil(cooldownHours - hoursSinceSpawn)
574
+ };
575
+ }
576
+
577
+ /**
578
+ * Record mapping fix spawn
579
+ */
580
+ function recordMappingFixSpawn() {
581
+ const state = readState();
582
+ const now = new Date();
583
+ const cooldownHours = CONFIG.global.mappingFixCooldownHours || 3;
584
+ const nextEligible = new Date(now.getTime() + cooldownHours * 60 * 60 * 1000);
585
+
586
+ state.mappingFix = {
587
+ lastSpawn: now.toISOString(),
588
+ nextEligible: nextEligible.toISOString()
589
+ };
590
+
591
+ writeState(state);
592
+ }
593
+
594
+ /**
595
+ * Handle mapping validation failure - spawn Claude to fix it (rate limited)
596
+ * @param {ValidationResult} result
597
+ */
598
+ function handleMappingValidationFailure(result) {
599
+ console.error('\n' + formatValidationResult(result));
600
+
601
+ // Check rate limit before spawning
602
+ const rateLimit = checkMappingFixRateLimit();
603
+ if (!rateLimit.allowed) {
604
+ console.error('\n╔═══════════════════════════════════════════════════════════════╗');
605
+ console.error('║ MAPPING FIX RATE LIMITED ║');
606
+ console.error('╠═══════════════════════════════════════════════════════════════╣');
607
+ console.error(`║ Cannot spawn Claude to fix mapping (cooldown: ${CONFIG.global.mappingFixCooldownHours || 3}h) ║`);
608
+ console.error(`║ Next eligible: ${rateLimit.nextEligible.substring(0, 19).replace('T', ' ')} ║`);
609
+ console.error(`║ Hours remaining: ${rateLimit.hoursRemaining} ║`);
610
+ console.error('╠═══════════════════════════════════════════════════════════════╣');
611
+ console.error('║ Fix the mapping file manually or wait for cooldown to expire. ║');
612
+ console.error('╚═══════════════════════════════════════════════════════════════╝');
613
+ return;
614
+ }
615
+
616
+ console.error('\nSpawning Claude to fix mapping file...\n');
617
+
618
+ const promptPath = path.join(projectDir, '.claude/hooks/prompts/mapping-fix.md');
619
+
620
+ if (!fs.existsSync(promptPath)) {
621
+ console.error(`Error: Prompt template not found at ${promptPath}`);
622
+ process.exit(2);
623
+ }
624
+
625
+ // Read current mappings
626
+ let currentMappings = '{}';
627
+ try {
628
+ currentMappings = fs.readFileSync(MAPPING_FILE, 'utf8');
629
+ } catch {
630
+ currentMappings = '{}';
631
+ }
632
+
633
+ // Read schema
634
+ const schemaPath = path.join(projectDir, '.claude/hooks/spec-file-mappings-schema.json');
635
+ const schema = fs.readFileSync(schemaPath, 'utf8');
636
+
637
+ // Format errors for prompt
638
+ const errorsOutput = result.errors.map(e =>
639
+ `[${e.severity.toUpperCase()}] ${e.code}\n ${e.message}\n Suggestion: ${e.suggestion}`
640
+ ).join('\n\n');
641
+
642
+ const prompt = buildPrompt(promptPath, {
643
+ VALIDATION_ERRORS: errorsOutput,
644
+ CURRENT_MAPPINGS: currentMappings,
645
+ SCHEMA_CONTENT: schema,
646
+ REQUIRED_SPECS_LIST: '- ' + [
647
+ 'BARDE.md',
648
+ 'HEIMDALL.md',
649
+ 'HUGINN.md',
650
+ 'OVERSEER.md',
651
+ 'UNDERSEER.md',
652
+ 'THOR.md',
653
+ 'SIGNALS.md',
654
+ 'CORE-INVARIANTS.md'
655
+ ].join('\n- '),
656
+ MAX_AGENTS: CONFIG.global.maxAgentsPerDay.toString(),
657
+ CURRENT_AGENT_COUNT: result.agentCount.toString(),
658
+ EXCESS_COUNT: result.errors.find(e => e.code === 'AGENT_LIMIT_EXCEEDED')?.details?.excess?.toString() || '0'
659
+ });
660
+
661
+ spawnClaudeInstance(prompt, { COMPLIANCE_MODE: 'mapping-fix' }, {
662
+ type: AGENT_TYPES.COMPLIANCE_MAPPING_FIX,
663
+ description: `Fixing mapping validation errors (${result.errors.length} errors)`,
664
+ metadata: { errorCount: result.errors.length, agentCount: result.agentCount }
665
+ });
666
+ recordMappingFixSpawn();
667
+ console.log('Claude spawned to fix mapping file. Run this script again after fixes are applied.');
668
+ }
669
+
670
+ /**
671
+ * Handle mapping validation success - optionally spawn Claude to review
672
+ * @param {ValidationResult} result
673
+ */
674
+ function handleMappingValidationSuccess(result) {
675
+ console.log('\n' + formatValidationResult(result));
676
+
677
+ // Check if we should review the mappings (weekly cooldown)
678
+ const mappings = JSON.parse(fs.readFileSync(MAPPING_FILE, 'utf8'));
679
+ const lastReviewed = mappings.lastReviewed ? new Date(mappings.lastReviewed) : null;
680
+ const now = new Date();
681
+
682
+ if (lastReviewed) {
683
+ const daysSinceReview = (now - lastReviewed) / (1000 * 60 * 60 * 24);
684
+ if (daysSinceReview < CONFIG.global.mappingReviewCooldownDays) {
685
+ console.log(`\nMapping review skipped (last reviewed ${Math.round(daysSinceReview)} days ago, cooldown: ${CONFIG.global.mappingReviewCooldownDays} days)\n`);
686
+ return;
687
+ }
688
+ }
689
+
690
+ console.log('\nSpawning Claude to review mappings...\n');
691
+
692
+ const promptPath = path.join(projectDir, '.claude/hooks/prompts/mapping-review.md');
693
+
694
+ if (!fs.existsSync(promptPath)) {
695
+ console.log('Note: mapping-review.md prompt not found, skipping review');
696
+ return;
697
+ }
698
+
699
+ // Build spec breakdown table
700
+ const breakdownLines = Object.entries(result.specBreakdown)
701
+ .map(([spec, data]) => `| ${spec.padEnd(25)} | ${String(data.fileCount).padStart(3)} | ${data.priority.padEnd(8)} |`)
702
+ .join('\n');
703
+
704
+ const breakdownTable = `| Spec | Files | Priority |\n|------|-------|----------|\n${breakdownLines}`;
705
+
706
+ const prompt = buildPrompt(promptPath, {
707
+ CURRENT_MAPPINGS: fs.readFileSync(MAPPING_FILE, 'utf8'),
708
+ CURRENT_AGENT_COUNT: result.agentCount.toString(),
709
+ MAX_AGENTS: CONFIG.global.maxAgentsPerDay.toString(),
710
+ UTILIZATION_PERCENT: result.utilizationPercent.toString(),
711
+ REMAINING_BUDGET: (result.limit - result.agentCount).toString(),
712
+ SPEC_BREAKDOWN_TABLE: breakdownTable
713
+ });
714
+
715
+ spawnClaudeInstance(prompt, { COMPLIANCE_MODE: 'mapping-review' }, {
716
+ type: AGENT_TYPES.COMPLIANCE_MAPPING_REVIEW,
717
+ description: `Reviewing spec-file-mappings.json (${result.agentCount} agents, ${result.utilizationPercent}% utilization)`,
718
+ metadata: { agentCount: result.agentCount, utilizationPercent: result.utilizationPercent }
719
+ });
720
+ console.log('Claude spawned to review mappings.');
721
+ }
722
+
723
+ /**
724
+ * Run global spec enforcement for files that need checking (uses spec-file-mappings.json + suites)
725
+ * @param {object} args - Command line arguments
726
+ */
727
+ function runGlobalEnforcement(args) {
728
+ // Check daily agent cap first
729
+ const agentCap = checkDailyAgentCap('global');
730
+
731
+ if (!agentCap.allowed) {
732
+ console.log(`[GLOBAL] Daily agent cap reached: ${agentCap.used}/${agentCap.limit} agents used today`);
733
+ console.log('Adjust global.maxAgentsPerDay in compliance-config.json to increase limit');
734
+ return;
735
+ }
736
+
737
+ console.log(`[GLOBAL] Daily agent budget: ${agentCap.used}/${agentCap.limit} used, ${agentCap.remaining} remaining\n`);
738
+
739
+ // Load mappings and suites config
740
+ const mappings = JSON.parse(fs.readFileSync(MAPPING_FILE, 'utf8'));
741
+ const suitesConfig = loadSuitesConfig(); // null if not configured
742
+
743
+ if (suitesConfig) {
744
+ const suiteCount = Object.keys(suitesConfig.suites).length;
745
+ console.log(`[GLOBAL] Loaded ${suiteCount} suite(s) from suites-config.json\n`);
746
+ }
747
+
748
+ // Collect ALL files from mappings
749
+ const allFiles = new Set();
750
+ for (const specData of Object.values(mappings.specs || {})) {
751
+ for (const fileEntry of specData.files || []) {
752
+ allFiles.add(fileEntry.path);
753
+ }
754
+ }
755
+
756
+ // Collect files that need checking with their applicable specs
757
+ const filesToCheck = [];
758
+
759
+ for (const filePath of allFiles) {
760
+ // Check if file exists
761
+ const fullPath = path.join(projectDir, filePath);
762
+ if (!fs.existsSync(fullPath)) {
763
+ console.warn(`Warning: File ${filePath} not found, skipping`);
764
+ continue;
765
+ }
766
+
767
+ // Get ALL specs for this file (main + subspecs) - UNIFIED
768
+ const applicableSpecs = getAllApplicableSpecs(filePath, mappings, suitesConfig);
769
+
770
+ for (const spec of applicableSpecs) {
771
+ // Skip if within cooldown
772
+ if (isWithinFileCooldown(spec.lastVerified)) {
773
+ continue;
774
+ }
775
+
776
+ filesToCheck.push({
777
+ spec: spec.name,
778
+ specDir: spec.dir,
779
+ file: filePath,
780
+ priority: spec.priority,
781
+ suiteId: spec.suiteId,
782
+ suiteScope: spec.suiteScope
783
+ });
784
+ }
785
+ }
786
+
787
+ if (filesToCheck.length === 0) {
788
+ console.log('[GLOBAL] No files need checking (all within cooldown period)');
789
+ console.log('Adjust global.fileVerificationCooldownDays in compliance-config.json to change cooldown');
790
+ return;
791
+ }
792
+
793
+ // Check if we have enough budget
794
+ const needed = filesToCheck.length;
795
+ if (needed > agentCap.remaining) {
796
+ console.log(`Cannot spawn ${needed} agents (only ${agentCap.remaining} remaining in daily budget)`);
797
+ console.log(`Files needing check: ${needed}`);
798
+ console.log(`Files that will be checked today: ${agentCap.remaining}`);
799
+ console.log('\nPrioritizing by spec priority and checking what we can...\n');
800
+
801
+ // Sort by priority (critical > high > medium > low)
802
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
803
+ filesToCheck.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
804
+
805
+ // Take only what we can check
806
+ filesToCheck.splice(agentCap.remaining);
807
+ }
808
+
809
+ if (args.dryRun) {
810
+ console.log('DRY RUN - would check these files:\n');
811
+ for (const item of filesToCheck) {
812
+ const suiteInfo = item.suiteId ? ` [suite: ${item.suiteId}]` : '';
813
+ console.log(` [${item.priority.toUpperCase()}] ${item.spec}: ${item.file}${suiteInfo}`);
814
+ }
815
+ console.log(`\nTotal agents: ${filesToCheck.length}`);
816
+ return;
817
+ }
818
+
819
+ // Spawn enforcement agents
820
+ console.log(`Spawning ${filesToCheck.length} compliance enforcement agents...\n`);
821
+
822
+ const promptPath = path.join(projectDir, '.claude/hooks/prompts/spec-enforcement.md');
823
+
824
+ if (!fs.existsSync(promptPath)) {
825
+ console.error(`Error: spec-enforcement.md prompt not found at ${promptPath}`);
826
+ process.exit(1);
827
+ }
828
+
829
+ // Read spec files and store their paths (keyed by dir+name for uniqueness)
830
+ const specContents = {};
831
+ const specPaths = {};
832
+ for (const item of filesToCheck) {
833
+ const specKey = `${item.specDir}/${item.spec}`;
834
+ if (!specContents[specKey]) {
835
+ // Use the spec's directory (from suite or default)
836
+ let specPath = path.join(projectDir, item.specDir, item.spec);
837
+
838
+ // Fallback search order for main specs only (not suite specs)
839
+ if (!item.suiteId && !fs.existsSync(specPath)) {
840
+ specPath = path.join(projectDir, 'specs/local', item.spec);
841
+ if (!fs.existsSync(specPath)) {
842
+ specPath = path.join(projectDir, 'specs/global', item.spec);
843
+ }
844
+ if (!fs.existsSync(specPath)) {
845
+ specPath = path.join(projectDir, 'specs/reference', item.spec);
846
+ }
847
+ }
848
+
849
+ // Fail hard if spec file cannot be read (per CLAUDE.md - no graceful fallbacks)
850
+ if (!fs.existsSync(specPath)) {
851
+ throw new Error(`CRITICAL: Spec file '${item.spec}' not found in ${item.specDir}. Cannot proceed with compliance checking without spec definition.`);
852
+ }
853
+
854
+ try {
855
+ specContents[specKey] = fs.readFileSync(specPath, 'utf8');
856
+ // Store relative path for the agent to use when updating specs
857
+ specPaths[specKey] = specPath.replace(projectDir + '/', '');
858
+ } catch (err) {
859
+ throw new Error(`CRITICAL: Failed to read spec file '${item.spec}' at ${specPath}: ${err.message}. Per CLAUDE.md, no graceful fallbacks allowed.`);
860
+ }
861
+ }
862
+ }
863
+
864
+ // Spawn agents
865
+ for (const item of filesToCheck) {
866
+ const specKey = `${item.specDir}/${item.spec}`;
867
+
868
+ // Build prompt with optional suite context
869
+ const promptVars = {
870
+ FILE_PATH: item.file,
871
+ SPEC_NAME: item.spec,
872
+ SPEC_PATH: specPaths[specKey],
873
+ SPEC_CONTENT: specContents[specKey]
874
+ };
875
+
876
+ // Add suite context if this is a subspec
877
+ if (item.suiteId) {
878
+ promptVars.SUITE_ID = item.suiteId;
879
+ promptVars.SUITE_SCOPE = item.suiteScope;
880
+ }
881
+
882
+ const prompt = buildPrompt(promptPath, promptVars);
883
+
884
+ const suiteInfo = item.suiteId ? ` [suite: ${item.suiteId}]` : '';
885
+ spawnClaudeInstance(prompt, {
886
+ COMPLIANCE_MODE: 'enforcement',
887
+ COMPLIANCE_SPEC: item.spec,
888
+ COMPLIANCE_FILE: item.file,
889
+ COMPLIANCE_SUITE: item.suiteId || ''
890
+ }, {
891
+ type: AGENT_TYPES.COMPLIANCE_GLOBAL,
892
+ description: `Global enforcement: ${item.file} against ${item.spec}${suiteInfo}`,
893
+ metadata: { spec: item.spec, file: item.file, priority: item.priority, suiteId: item.suiteId }
894
+ });
895
+
896
+ console.log(` ✓ Spawned for ${item.file} (${item.spec})${suiteInfo}`);
897
+
898
+ // Update lastVerified timestamp (only for main specs)
899
+ if (!item.suiteId) {
900
+ updateFileVerificationTimestamp(item.spec, item.file);
901
+ }
902
+ }
903
+
904
+ // Record spawns in log (global specs enforcement)
905
+ recordAgentSpawns('global', filesToCheck);
906
+
907
+ console.log(`\n[GLOBAL] Spawned ${filesToCheck.length} enforcement agents`);
908
+ console.log('Agents will run in background and report results when complete');
909
+ }
910
+
911
+ /**
912
+ * Run local spec enforcement (no file mappings, agent explores codebase + suites)
913
+ * @param {object} args - Command line arguments
914
+ */
915
+ async function runLocalEnforcement(args) {
916
+ const state = readState();
917
+
918
+ // Check daily agent cap first
919
+ const agentCap = checkDailyAgentCap('local');
920
+
921
+ if (!agentCap.allowed) {
922
+ console.log(`[LOCAL] Daily agent cap reached: ${agentCap.used}/${agentCap.limit} agents used today`);
923
+ console.log('Adjust local.maxAgentsPerDay in compliance-config.json to increase limit');
924
+ return;
925
+ }
926
+
927
+ console.log(`[LOCAL] Daily agent budget: ${agentCap.used}/${agentCap.limit} used, ${agentCap.remaining} remaining\n`);
928
+
929
+ // Load suites config (optional)
930
+ const suitesConfig = loadSuitesConfig();
931
+
932
+ // Get ALL exploratory specs (main + suites) - UNIFIED
933
+ const allSpecs = getAllExploratorySpecs(suitesConfig);
934
+
935
+ if (allSpecs.length === 0) {
936
+ console.log('[LOCAL] No exploratory specs found');
937
+ return;
938
+ }
939
+
940
+ if (suitesConfig) {
941
+ const suiteSpecs = allSpecs.filter(s => s.suiteId);
942
+ if (suiteSpecs.length > 0) {
943
+ console.log(`[LOCAL] Found ${allSpecs.length} specs (${allSpecs.length - suiteSpecs.length} main + ${suiteSpecs.length} from suites)\n`);
944
+ }
945
+ }
946
+
947
+ // Filter out specs within cooldown
948
+ const specsToRun = [];
949
+ for (const spec of allSpecs) {
950
+ // Use suite:specName as cooldown key for suite specs
951
+ const cooldownKey = spec.suiteId ? `${spec.suiteId}:${spec.name}` : spec.name;
952
+ if (isWithinSpecCooldown(cooldownKey, state)) {
953
+ const lastRun = state.localSpecs.perSpecLastRun[cooldownKey];
954
+ const daysSince = Math.round((Date.now() - new Date(lastRun)) / (1000 * 60 * 60 * 24));
955
+ const suiteInfo = spec.suiteId ? ` [suite: ${spec.suiteId}]` : '';
956
+ console.log(`[LOCAL] Skipping ${spec.name}${suiteInfo} (last run ${daysSince} days ago, cooldown: ${CONFIG.local.specCooldownDays} days)`);
957
+ continue;
958
+ }
959
+ specsToRun.push(spec);
960
+ }
961
+
962
+ if (specsToRun.length === 0) {
963
+ console.log('[LOCAL] No specs need enforcement (all within cooldown period)');
964
+ console.log('Adjust local.specCooldownDays in compliance-config.json to change cooldown');
965
+ return;
966
+ }
967
+
968
+ // Respect daily agent cap (one agent per spec)
969
+ const specsToRunToday = specsToRun.slice(0, agentCap.remaining);
970
+
971
+ if (specsToRun.length > agentCap.remaining) {
972
+ console.log(`[LOCAL] Cannot spawn agents for ${specsToRun.length} specs (only ${agentCap.remaining} remaining in daily budget)`);
973
+ console.log(`[LOCAL] Will enforce first ${agentCap.remaining} specs today\n`);
974
+ }
975
+
976
+ if (args.dryRun) {
977
+ console.log('[LOCAL] DRY RUN - would enforce these specs:\n');
978
+ for (const spec of specsToRunToday) {
979
+ const suiteInfo = spec.suiteId ? ` [suite: ${spec.suiteId}, scope: ${spec.suiteScope}]` : '';
980
+ console.log(` - ${spec.name}${suiteInfo}`);
981
+ }
982
+ console.log(`\nTotal agents: ${specsToRunToday.length}`);
983
+ return;
984
+ }
985
+
986
+ // Spawn enforcement agents
987
+ console.log(`[LOCAL] Spawning ${specsToRunToday.length} local spec enforcement agents...\n`);
988
+
989
+ const promptPath = path.join(projectDir, '.claude/hooks/prompts/local-spec-enforcement.md');
990
+
991
+ if (!fs.existsSync(promptPath)) {
992
+ console.error(`Error: local-spec-enforcement.md prompt not found at ${promptPath}`);
993
+ process.exit(1);
994
+ }
995
+
996
+ const agentsSpawned = [];
997
+
998
+ for (const spec of specsToRunToday) {
999
+ const specPath = path.join(projectDir, spec.dir, spec.name);
1000
+ const specContent = fs.readFileSync(specPath, 'utf8');
1001
+ const specRelativePath = path.join(spec.dir, spec.name);
1002
+
1003
+ // Build prompt with optional suite context
1004
+ const promptVars = {
1005
+ SPEC_NAME: spec.name,
1006
+ SPEC_PATH: specRelativePath,
1007
+ SPEC_CONTENT: specContent
1008
+ };
1009
+
1010
+ // Add suite context if this is a suite exploratory spec
1011
+ if (spec.suiteId) {
1012
+ promptVars.SUITE_ID = spec.suiteId;
1013
+ promptVars.SUITE_SCOPE = spec.suiteScope;
1014
+ }
1015
+
1016
+ const prompt = buildPrompt(promptPath, promptVars);
1017
+
1018
+ const suiteInfo = spec.suiteId ? ` [suite: ${spec.suiteId}]` : '';
1019
+ spawnClaudeInstance(prompt, {
1020
+ COMPLIANCE_MODE: 'local-enforcement',
1021
+ COMPLIANCE_SPEC: spec.name,
1022
+ COMPLIANCE_SUITE: spec.suiteId || ''
1023
+ }, {
1024
+ type: AGENT_TYPES.COMPLIANCE_LOCAL,
1025
+ description: `Local enforcement: exploring for ${spec.name}${suiteInfo}`,
1026
+ metadata: { spec: spec.name, specPath: specRelativePath, suiteId: spec.suiteId, suiteScope: spec.suiteScope }
1027
+ });
1028
+
1029
+ console.log(` ✓ Spawned for ${spec.name}${suiteInfo}`);
1030
+
1031
+ // Update last run timestamp (use suite:specName for suite specs)
1032
+ const cooldownKey = spec.suiteId ? `${spec.suiteId}:${spec.name}` : spec.name;
1033
+ updateSpecCooldown(cooldownKey, state);
1034
+
1035
+ agentsSpawned.push({
1036
+ spec: spec.name,
1037
+ file: spec.suiteScope || '**/* (agent explores)',
1038
+ priority: 'N/A',
1039
+ suiteId: spec.suiteId
1040
+ });
1041
+ }
1042
+
1043
+ // Record spawns in log (local specs enforcement)
1044
+ recordAgentSpawns('local', agentsSpawned);
1045
+
1046
+ console.log(`\n[LOCAL] Spawned ${specsToRunToday.length} enforcement agents`);
1047
+ console.log('Agents will explore codebase and report results when complete');
1048
+ }
1049
+
1050
+ /**
1051
+ * Show status
1052
+ */
1053
+ function showStatus() {
1054
+ const state = readState();
1055
+ const globalAgentCap = checkDailyAgentCap('global');
1056
+ const localAgentCap = checkDailyAgentCap('local');
1057
+ const mappings = JSON.parse(fs.readFileSync(MAPPING_FILE, 'utf8'));
1058
+
1059
+ console.log('╔═══════════════════════════════════════════════════════════════╗');
1060
+ console.log('║ COMPLIANCE CHECKER STATUS ║');
1061
+ console.log('╠═══════════════════════════════════════════════════════════════╣');
1062
+ console.log('║ Daily Agent Budget (GLOBAL - mapped files) ║');
1063
+ console.log(`║ Used Today: ${String(globalAgentCap.used).padStart(3)} / ${String(globalAgentCap.limit).padStart(3)} ║`);
1064
+ console.log(`║ Remaining: ${String(globalAgentCap.remaining).padStart(3)} ║`);
1065
+ console.log('╠═══════════════════════════════════════════════════════════════╣');
1066
+ console.log('║ Daily Agent Budget (LOCAL - explore codebase) ║');
1067
+ console.log(`║ Used Today: ${String(localAgentCap.used).padStart(3)} / ${String(localAgentCap.limit).padStart(3)} ║`);
1068
+ console.log(`║ Remaining: ${String(localAgentCap.remaining).padStart(3)} ║`);
1069
+ console.log('╠═══════════════════════════════════════════════════════════════╣');
1070
+ console.log('║ Mapped Files ║');
1071
+ console.log(`║ Total Files: ${String(mappings.totalMappedFiles).padStart(3)} ║`);
1072
+ console.log(`║ Last Reviewed: ${mappings.lastReviewed ? new Date(mappings.lastReviewed).toISOString().substring(0, 16).replace('T', ' ') : 'Never'.padEnd(16)} ║`);
1073
+ console.log('╠═══════════════════════════════════════════════════════════════╣');
1074
+ console.log('║ Global Spec Breakdown (mapped files) ║');
1075
+
1076
+ for (const [spec, data] of Object.entries(mappings.specs)) {
1077
+ const filesNeedingCheck = data.files.filter(f => !isWithinFileCooldown(f.lastVerified)).length;
1078
+ const specStr = spec.substring(0, 20).padEnd(20);
1079
+ const filesStr = String(data.files.length).padStart(2);
1080
+ const needsStr = String(filesNeedingCheck).padStart(2);
1081
+
1082
+ console.log(`║ ${specStr} ${filesStr} files (${needsStr} need check) ║`);
1083
+ }
1084
+
1085
+ console.log('╠═══════════════════════════════════════════════════════════════╣');
1086
+ console.log('║ Local Spec Breakdown (explore codebase) ║');
1087
+
1088
+ const specsLocalDir = path.join(projectDir, CONFIG.specsLocalDir);
1089
+ if (fs.existsSync(specsLocalDir)) {
1090
+ const localSpecFiles = fs.readdirSync(specsLocalDir).filter(f => f.endsWith('.md'));
1091
+
1092
+ if (localSpecFiles.length === 0) {
1093
+ console.log('║ (no local specs found) ║');
1094
+ } else {
1095
+ for (const specFile of localSpecFiles) {
1096
+ const needsEnforcement = !isWithinSpecCooldown(specFile, state);
1097
+ const specStr = specFile.substring(0, 20).padEnd(20);
1098
+ const statusStr = needsEnforcement ? 'needs check' : 'in cooldown';
1099
+
1100
+ console.log(`║ ${specStr} ${statusStr.padEnd(20)} ║`);
1101
+ }
1102
+ }
1103
+ } else {
1104
+ console.log('║ (specs/local/ directory not found) ║');
1105
+ }
1106
+
1107
+ console.log('╚═══════════════════════════════════════════════════════════════╝');
1108
+ }
1109
+
1110
+ /**
1111
+ * Show enforcement history
1112
+ * @param {string} filter - 'all', 'global', or 'local'
1113
+ */
1114
+ function showHistory(filter = 'all') {
1115
+ const log = readLog();
1116
+
1117
+ if (!log.history || log.history.length === 0) {
1118
+ console.log('No enforcement history found.');
1119
+ return;
1120
+ }
1121
+
1122
+ // Filter history by mode
1123
+ let filteredHistory = log.history;
1124
+ if (filter === 'global') {
1125
+ filteredHistory = log.history.filter(h => h.mode === 'global');
1126
+ } else if (filter === 'local') {
1127
+ filteredHistory = log.history.filter(h => h.mode === 'local');
1128
+ }
1129
+
1130
+ if (filteredHistory.length === 0) {
1131
+ console.log(`No ${filter} enforcement history found.`);
1132
+ return;
1133
+ }
1134
+
1135
+ const title = filter === 'all' ? 'ALL ENFORCEMENT' : `${filter.toUpperCase()} SPEC ENFORCEMENT`;
1136
+ console.log('╔═══════════════════════════════════════════════════════════════════════════════╗');
1137
+ console.log(`║ ${title} HISTORY ║`);
1138
+ console.log('╚═══════════════════════════════════════════════════════════════════════════════╝');
1139
+ console.log('');
1140
+
1141
+ // Group by date and show sessions
1142
+ for (const session of filteredHistory.slice().reverse()) {
1143
+ const date = new Date(session.date);
1144
+ const dateStr = date.toISOString().substring(0, 19).replace('T', ' ');
1145
+ const modeLabel = session.mode === 'global' ? '[GLOBAL]' : '[LOCAL ]';
1146
+
1147
+ console.log(`┌─────────────────────────────────────────────────────────────────────────────┐`);
1148
+ console.log(`│ ${modeLabel} ${dateStr} (${session.count} agents spawned)`);
1149
+ console.log(`├─────────────────────────────────────────────────────────────────────────────┤`);
1150
+
1151
+ if (session.agents && session.agents.length > 0) {
1152
+ // Group agents by spec
1153
+ const bySpec = {};
1154
+ for (const agent of session.agents) {
1155
+ if (!bySpec[agent.spec]) {
1156
+ bySpec[agent.spec] = [];
1157
+ }
1158
+ bySpec[agent.spec].push(agent.file);
1159
+ }
1160
+
1161
+ for (const [spec, files] of Object.entries(bySpec)) {
1162
+ console.log(`│ ${spec}`);
1163
+ for (const file of files) {
1164
+ console.log(`│ └─ ${file}`);
1165
+ }
1166
+ }
1167
+ } else if (session.files) {
1168
+ // Legacy format support
1169
+ console.log(`│ ${session.spec || 'enforcement'}`);
1170
+ for (const file of session.files) {
1171
+ console.log(`│ └─ ${file}`);
1172
+ }
1173
+ }
1174
+
1175
+ console.log(`└─────────────────────────────────────────────────────────────────────────────┘`);
1176
+ console.log('');
1177
+ }
1178
+
1179
+ console.log(`Total sessions shown: ${filteredHistory.length}`);
1180
+ }
1181
+
1182
+ /**
1183
+ * Main entry point
1184
+ */
1185
+ async function main() {
1186
+ const startTime = Date.now();
1187
+ const args = parseArgs(process.argv.slice(2));
1188
+
1189
+ // Prevent chain reactions
1190
+ if (process.env.CLAUDE_SPAWNED_SESSION === 'true') {
1191
+ console.log('Spawned session detected - skipping compliance check');
1192
+ registerHookExecution({
1193
+ hookType: HOOK_TYPES.COMPLIANCE_CHECKER,
1194
+ status: 'skipped',
1195
+ durationMs: Date.now() - startTime,
1196
+ metadata: { reason: 'spawned_session' }
1197
+ });
1198
+ process.exit(0);
1199
+ }
1200
+
1201
+ // Show status
1202
+ if (args.status) {
1203
+ showStatus();
1204
+ registerHookExecution({
1205
+ hookType: HOOK_TYPES.COMPLIANCE_CHECKER,
1206
+ status: 'success',
1207
+ durationMs: Date.now() - startTime,
1208
+ metadata: { mode: 'status' }
1209
+ });
1210
+ process.exit(0);
1211
+ }
1212
+
1213
+ // Show history
1214
+ if (args.history) {
1215
+ showHistory('all');
1216
+ registerHookExecution({
1217
+ hookType: HOOK_TYPES.COMPLIANCE_CHECKER,
1218
+ status: 'success',
1219
+ durationMs: Date.now() - startTime,
1220
+ metadata: { mode: 'history' }
1221
+ });
1222
+ process.exit(0);
1223
+ }
1224
+ if (args.historyGlobal) {
1225
+ showHistory('global');
1226
+ registerHookExecution({
1227
+ hookType: HOOK_TYPES.COMPLIANCE_CHECKER,
1228
+ status: 'success',
1229
+ durationMs: Date.now() - startTime,
1230
+ metadata: { mode: 'history-global' }
1231
+ });
1232
+ process.exit(0);
1233
+ }
1234
+ if (args.historyLocal) {
1235
+ showHistory('local');
1236
+ registerHookExecution({
1237
+ hookType: HOOK_TYPES.COMPLIANCE_CHECKER,
1238
+ status: 'success',
1239
+ durationMs: Date.now() - startTime,
1240
+ metadata: { mode: 'history-local' }
1241
+ });
1242
+ process.exit(0);
1243
+ }
1244
+
1245
+ // Step 1: Validate mapping file (only for global enforcement)
1246
+ if (!args.localOnly) {
1247
+ console.log('Validating spec-file-mappings.json...\n');
1248
+ const validationResult = validateMappings(projectDir, CONFIG);
1249
+
1250
+ if (!validationResult.valid) {
1251
+ handleMappingValidationFailure(validationResult);
1252
+ registerHookExecution({
1253
+ hookType: HOOK_TYPES.COMPLIANCE_CHECKER,
1254
+ status: 'failure',
1255
+ durationMs: Date.now() - startTime,
1256
+ metadata: { reason: 'mapping_validation_failed', errorCount: validationResult.errors?.length }
1257
+ });
1258
+ process.exit(2);
1259
+ }
1260
+
1261
+ handleMappingValidationSuccess(validationResult);
1262
+ }
1263
+
1264
+ // Step 2: Run enforcement (unless only showing status)
1265
+ if (!args.status) {
1266
+ console.log('\n═══════════════════════════════════════════════════════════════\n');
1267
+ console.log('Running compliance enforcement...\n');
1268
+
1269
+ // Run global enforcement (mapped files)
1270
+ if (!args.localOnly) {
1271
+ console.log('═══════════════════════════════════════════════════════════════');
1272
+ console.log(' GLOBAL ENFORCEMENT');
1273
+ console.log('═══════════════════════════════════════════════════════════════\n');
1274
+ runGlobalEnforcement(args);
1275
+ }
1276
+
1277
+ // Run local enforcement (explore codebase)
1278
+ if (!args.globalOnly) {
1279
+ console.log('\n═══════════════════════════════════════════════════════════════');
1280
+ console.log(' LOCAL ENFORCEMENT');
1281
+ console.log('═══════════════════════════════════════════════════════════════\n');
1282
+ await runLocalEnforcement(args);
1283
+ }
1284
+
1285
+ console.log('\n═══════════════════════════════════════════════════════════════');
1286
+ console.log('Compliance enforcement complete');
1287
+ console.log('═══════════════════════════════════════════════════════════════\n');
1288
+ }
1289
+
1290
+ registerHookExecution({
1291
+ hookType: HOOK_TYPES.COMPLIANCE_CHECKER,
1292
+ status: 'success',
1293
+ durationMs: Date.now() - startTime,
1294
+ metadata: { globalOnly: args.globalOnly, localOnly: args.localOnly, dryRun: args.dryRun }
1295
+ });
1296
+
1297
+ process.exit(0);
1298
+ }
1299
+
1300
+ main().catch(err => {
1301
+ console.error(`Error: ${err.message}`);
1302
+ registerHookExecution({
1303
+ hookType: HOOK_TYPES.COMPLIANCE_CHECKER,
1304
+ status: 'failure',
1305
+ durationMs: 0,
1306
+ metadata: { error: err.message }
1307
+ });
1308
+ process.exit(1);
1309
+ });