@dewtech/dare-cli 3.10.0 → 3.12.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 (441) hide show
  1. package/README.md +7 -1
  2. package/dist/__tests__/ci-pr-regression.test.d.ts +2 -0
  3. package/dist/__tests__/ci-pr-regression.test.d.ts.map +1 -0
  4. package/dist/__tests__/ci-pr-regression.test.js +76 -0
  5. package/dist/__tests__/ci-pr-regression.test.js.map +1 -0
  6. package/dist/__tests__/dashboard-front.test.d.ts +2 -0
  7. package/dist/__tests__/dashboard-front.test.d.ts.map +1 -0
  8. package/dist/__tests__/dashboard-front.test.js +37 -0
  9. package/dist/__tests__/dashboard-front.test.js.map +1 -0
  10. package/dist/__tests__/dashboard-regression.test.d.ts +2 -0
  11. package/dist/__tests__/dashboard-regression.test.d.ts.map +1 -0
  12. package/dist/__tests__/dashboard-regression.test.js +68 -0
  13. package/dist/__tests__/dashboard-regression.test.js.map +1 -0
  14. package/dist/__tests__/dynamic-dag-regression.test.d.ts +2 -0
  15. package/dist/__tests__/dynamic-dag-regression.test.d.ts.map +1 -0
  16. package/dist/__tests__/dynamic-dag-regression.test.js +464 -0
  17. package/dist/__tests__/dynamic-dag-regression.test.js.map +1 -0
  18. package/dist/__tests__/ensure-skills.test.js +5 -0
  19. package/dist/__tests__/ensure-skills.test.js.map +1 -1
  20. package/dist/__tests__/ide-command-parity.test.js +2 -0
  21. package/dist/__tests__/ide-command-parity.test.js.map +1 -1
  22. package/dist/__tests__/project-generator.test.js +17 -0
  23. package/dist/__tests__/project-generator.test.js.map +1 -1
  24. package/dist/__tests__/reverse-facts.test.js +1 -0
  25. package/dist/__tests__/reverse-facts.test.js.map +1 -1
  26. package/dist/__tests__/terminal-parity-regression.test.d.ts +2 -0
  27. package/dist/__tests__/terminal-parity-regression.test.d.ts.map +1 -0
  28. package/dist/__tests__/terminal-parity-regression.test.js +116 -0
  29. package/dist/__tests__/terminal-parity-regression.test.js.map +1 -0
  30. package/dist/__tests__/terminal-parity.test.d.ts +2 -0
  31. package/dist/__tests__/terminal-parity.test.d.ts.map +1 -0
  32. package/dist/__tests__/terminal-parity.test.js +81 -0
  33. package/dist/__tests__/terminal-parity.test.js.map +1 -0
  34. package/dist/agent/__tests__/antigravity-driver.test.d.ts +2 -0
  35. package/dist/agent/__tests__/antigravity-driver.test.d.ts.map +1 -0
  36. package/dist/agent/__tests__/antigravity-driver.test.js +52 -0
  37. package/dist/agent/__tests__/antigravity-driver.test.js.map +1 -0
  38. package/dist/agent/__tests__/codex-driver.test.d.ts +2 -0
  39. package/dist/agent/__tests__/codex-driver.test.d.ts.map +1 -0
  40. package/dist/agent/__tests__/codex-driver.test.js +68 -0
  41. package/dist/agent/__tests__/codex-driver.test.js.map +1 -0
  42. package/dist/agent/__tests__/cursor-driver.test.d.ts +2 -0
  43. package/dist/agent/__tests__/cursor-driver.test.d.ts.map +1 -0
  44. package/dist/agent/__tests__/cursor-driver.test.js +52 -0
  45. package/dist/agent/__tests__/cursor-driver.test.js.map +1 -0
  46. package/dist/agent/driver.d.ts +1 -1
  47. package/dist/agent/driver.d.ts.map +1 -1
  48. package/dist/agent/drivers/antigravity.d.ts +8 -0
  49. package/dist/agent/drivers/antigravity.d.ts.map +1 -0
  50. package/dist/agent/drivers/antigravity.js +99 -0
  51. package/dist/agent/drivers/antigravity.js.map +1 -0
  52. package/dist/agent/drivers/codex.d.ts +12 -0
  53. package/dist/agent/drivers/codex.d.ts.map +1 -0
  54. package/dist/agent/drivers/codex.js +137 -0
  55. package/dist/agent/drivers/codex.js.map +1 -0
  56. package/dist/agent/drivers/cursor.d.ts +8 -0
  57. package/dist/agent/drivers/cursor.d.ts.map +1 -0
  58. package/dist/agent/drivers/cursor.js +99 -0
  59. package/dist/agent/drivers/cursor.js.map +1 -0
  60. package/dist/ai/__tests__/ai-core.test.d.ts +2 -0
  61. package/dist/ai/__tests__/ai-core.test.d.ts.map +1 -0
  62. package/dist/ai/__tests__/ai-core.test.js +41 -0
  63. package/dist/ai/__tests__/ai-core.test.js.map +1 -0
  64. package/dist/ai/__tests__/parity.test.d.ts +2 -0
  65. package/dist/ai/__tests__/parity.test.d.ts.map +1 -0
  66. package/dist/ai/__tests__/parity.test.js +36 -0
  67. package/dist/ai/__tests__/parity.test.js.map +1 -0
  68. package/dist/ai/__tests__/pipeline.test.d.ts +2 -0
  69. package/dist/ai/__tests__/pipeline.test.d.ts.map +1 -0
  70. package/dist/ai/__tests__/pipeline.test.js +147 -0
  71. package/dist/ai/__tests__/pipeline.test.js.map +1 -0
  72. package/dist/ai/__tests__/refine-bridge.test.d.ts +2 -0
  73. package/dist/ai/__tests__/refine-bridge.test.d.ts.map +1 -0
  74. package/dist/ai/__tests__/refine-bridge.test.js +17 -0
  75. package/dist/ai/__tests__/refine-bridge.test.js.map +1 -0
  76. package/dist/ai/__tests__/resolve.test.d.ts +2 -0
  77. package/dist/ai/__tests__/resolve.test.d.ts.map +1 -0
  78. package/dist/ai/__tests__/resolve.test.js +42 -0
  79. package/dist/ai/__tests__/resolve.test.js.map +1 -0
  80. package/dist/ai/capabilities.d.ts +3 -0
  81. package/dist/ai/capabilities.d.ts.map +1 -0
  82. package/dist/ai/capabilities.js +11 -0
  83. package/dist/ai/capabilities.js.map +1 -0
  84. package/dist/ai/command-options.d.ts +10 -0
  85. package/dist/ai/command-options.d.ts.map +1 -0
  86. package/dist/ai/command-options.js +15 -0
  87. package/dist/ai/command-options.js.map +1 -0
  88. package/dist/ai/config.d.ts +27 -0
  89. package/dist/ai/config.d.ts.map +1 -0
  90. package/dist/ai/config.js +89 -0
  91. package/dist/ai/config.js.map +1 -0
  92. package/dist/ai/parity.d.ts +13 -0
  93. package/dist/ai/parity.d.ts.map +1 -0
  94. package/dist/ai/parity.js +87 -0
  95. package/dist/ai/parity.js.map +1 -0
  96. package/dist/ai/parse-json-output.d.ts +5 -0
  97. package/dist/ai/parse-json-output.d.ts.map +1 -0
  98. package/dist/ai/parse-json-output.js +25 -0
  99. package/dist/ai/parse-json-output.js.map +1 -0
  100. package/dist/ai/pipeline.d.ts +20 -0
  101. package/dist/ai/pipeline.d.ts.map +1 -0
  102. package/dist/ai/pipeline.js +303 -0
  103. package/dist/ai/pipeline.js.map +1 -0
  104. package/dist/ai/prompts.d.ts +6 -0
  105. package/dist/ai/prompts.d.ts.map +1 -0
  106. package/dist/ai/prompts.js +49 -0
  107. package/dist/ai/prompts.js.map +1 -0
  108. package/dist/ai/providers.d.ts +63 -0
  109. package/dist/ai/providers.d.ts.map +1 -0
  110. package/dist/ai/providers.js +297 -0
  111. package/dist/ai/providers.js.map +1 -0
  112. package/dist/ai/refine-bridge.d.ts +5 -0
  113. package/dist/ai/refine-bridge.d.ts.map +1 -0
  114. package/dist/ai/refine-bridge.js +14 -0
  115. package/dist/ai/refine-bridge.js.map +1 -0
  116. package/dist/ai/registry.d.ts +12 -0
  117. package/dist/ai/registry.d.ts.map +1 -0
  118. package/dist/ai/registry.js +43 -0
  119. package/dist/ai/registry.js.map +1 -0
  120. package/dist/ai/resolve.d.ts +28 -0
  121. package/dist/ai/resolve.d.ts.map +1 -0
  122. package/dist/ai/resolve.js +83 -0
  123. package/dist/ai/resolve.js.map +1 -0
  124. package/dist/ai/schemas.d.ts +175 -0
  125. package/dist/ai/schemas.d.ts.map +1 -0
  126. package/dist/ai/schemas.js +199 -0
  127. package/dist/ai/schemas.js.map +1 -0
  128. package/dist/ai/types.d.ts +52 -0
  129. package/dist/ai/types.d.ts.map +1 -0
  130. package/dist/ai/types.js +8 -0
  131. package/dist/ai/types.js.map +1 -0
  132. package/dist/bin/dare.js +4 -0
  133. package/dist/bin/dare.js.map +1 -1
  134. package/dist/commands/__tests__/ai-command.test.d.ts +2 -0
  135. package/dist/commands/__tests__/ai-command.test.d.ts.map +1 -0
  136. package/dist/commands/__tests__/ai-command.test.js +68 -0
  137. package/dist/commands/__tests__/ai-command.test.js.map +1 -0
  138. package/dist/commands/__tests__/dashboard-command.test.d.ts +2 -0
  139. package/dist/commands/__tests__/dashboard-command.test.d.ts.map +1 -0
  140. package/dist/commands/__tests__/dashboard-command.test.js +62 -0
  141. package/dist/commands/__tests__/dashboard-command.test.js.map +1 -0
  142. package/dist/commands/__tests__/execute-agent.test.js +82 -0
  143. package/dist/commands/__tests__/execute-agent.test.js.map +1 -1
  144. package/dist/commands/__tests__/gate-flags.test.d.ts +2 -0
  145. package/dist/commands/__tests__/gate-flags.test.d.ts.map +1 -0
  146. package/dist/commands/__tests__/gate-flags.test.js +58 -0
  147. package/dist/commands/__tests__/gate-flags.test.js.map +1 -0
  148. package/dist/commands/__tests__/persist-viz.test.d.ts +2 -0
  149. package/dist/commands/__tests__/persist-viz.test.d.ts.map +1 -0
  150. package/dist/commands/__tests__/persist-viz.test.js +178 -0
  151. package/dist/commands/__tests__/persist-viz.test.js.map +1 -0
  152. package/dist/commands/__tests__/refine-apply.test.d.ts +2 -0
  153. package/dist/commands/__tests__/refine-apply.test.d.ts.map +1 -0
  154. package/dist/commands/__tests__/refine-apply.test.js +118 -0
  155. package/dist/commands/__tests__/refine-apply.test.js.map +1 -0
  156. package/dist/commands/__tests__/replan-splice.test.d.ts +2 -0
  157. package/dist/commands/__tests__/replan-splice.test.d.ts.map +1 -0
  158. package/dist/commands/__tests__/replan-splice.test.js +295 -0
  159. package/dist/commands/__tests__/replan-splice.test.js.map +1 -0
  160. package/dist/commands/ai.d.ts +3 -0
  161. package/dist/commands/ai.d.ts.map +1 -0
  162. package/dist/commands/ai.js +141 -0
  163. package/dist/commands/ai.js.map +1 -0
  164. package/dist/commands/blueprint.d.ts.map +1 -1
  165. package/dist/commands/blueprint.js +17 -3
  166. package/dist/commands/blueprint.js.map +1 -1
  167. package/dist/commands/dag.d.ts.map +1 -1
  168. package/dist/commands/dag.js +2 -0
  169. package/dist/commands/dag.js.map +1 -1
  170. package/dist/commands/dashboard.d.ts +17 -0
  171. package/dist/commands/dashboard.d.ts.map +1 -0
  172. package/dist/commands/dashboard.js +77 -0
  173. package/dist/commands/dashboard.js.map +1 -0
  174. package/dist/commands/design.d.ts.map +1 -1
  175. package/dist/commands/design.js +21 -2
  176. package/dist/commands/design.js.map +1 -1
  177. package/dist/commands/discover.d.ts.map +1 -1
  178. package/dist/commands/discover.js +9 -1
  179. package/dist/commands/discover.js.map +1 -1
  180. package/dist/commands/dna.d.ts.map +1 -1
  181. package/dist/commands/dna.js +23 -3
  182. package/dist/commands/dna.js.map +1 -1
  183. package/dist/commands/execute.d.ts +11 -0
  184. package/dist/commands/execute.d.ts.map +1 -1
  185. package/dist/commands/execute.js +219 -6
  186. package/dist/commands/execute.js.map +1 -1
  187. package/dist/commands/graph.d.ts.map +1 -1
  188. package/dist/commands/graph.js +33 -5
  189. package/dist/commands/graph.js.map +1 -1
  190. package/dist/commands/guard.d.ts.map +1 -1
  191. package/dist/commands/guard.js +27 -3
  192. package/dist/commands/guard.js.map +1 -1
  193. package/dist/commands/init.d.ts.map +1 -1
  194. package/dist/commands/init.js +1 -0
  195. package/dist/commands/init.js.map +1 -1
  196. package/dist/commands/migrate.d.ts.map +1 -1
  197. package/dist/commands/migrate.js +14 -2
  198. package/dist/commands/migrate.js.map +1 -1
  199. package/dist/commands/patterns.d.ts.map +1 -1
  200. package/dist/commands/patterns.js +14 -2
  201. package/dist/commands/patterns.js.map +1 -1
  202. package/dist/commands/refine.d.ts +2 -0
  203. package/dist/commands/refine.d.ts.map +1 -1
  204. package/dist/commands/refine.js +94 -22
  205. package/dist/commands/refine.js.map +1 -1
  206. package/dist/commands/reverse.d.ts.map +1 -1
  207. package/dist/commands/reverse.js +28 -3
  208. package/dist/commands/reverse.js.map +1 -1
  209. package/dist/commands/review.d.ts.map +1 -1
  210. package/dist/commands/review.js +60 -7
  211. package/dist/commands/review.js.map +1 -1
  212. package/dist/core/types/project.d.ts +1 -1
  213. package/dist/core/types/project.d.ts.map +1 -1
  214. package/dist/dag-runner/__tests__/maxdepth-config.test.d.ts +2 -0
  215. package/dist/dag-runner/__tests__/maxdepth-config.test.d.ts.map +1 -0
  216. package/dist/dag-runner/__tests__/maxdepth-config.test.js +28 -0
  217. package/dist/dag-runner/__tests__/maxdepth-config.test.js.map +1 -0
  218. package/dist/dag-runner/__tests__/sub-dag.test.d.ts +2 -0
  219. package/dist/dag-runner/__tests__/sub-dag.test.d.ts.map +1 -0
  220. package/dist/dag-runner/__tests__/sub-dag.test.js +127 -0
  221. package/dist/dag-runner/__tests__/sub-dag.test.js.map +1 -0
  222. package/dist/dag-runner/run_dag.d.ts +3 -1
  223. package/dist/dag-runner/run_dag.d.ts.map +1 -1
  224. package/dist/dag-runner/run_dag.js.map +1 -1
  225. package/dist/dag-runner/state-store.d.ts +2 -0
  226. package/dist/dag-runner/state-store.d.ts.map +1 -1
  227. package/dist/dag-runner/state-store.js +44 -8
  228. package/dist/dag-runner/state-store.js.map +1 -1
  229. package/dist/dag-runner/sub-dag.d.ts +23 -0
  230. package/dist/dag-runner/sub-dag.d.ts.map +1 -0
  231. package/dist/dag-runner/sub-dag.js +117 -0
  232. package/dist/dag-runner/sub-dag.js.map +1 -0
  233. package/dist/dashboard/__tests__/dashboard-routes.test.d.ts +2 -0
  234. package/dist/dashboard/__tests__/dashboard-routes.test.d.ts.map +1 -0
  235. package/dist/dashboard/__tests__/dashboard-routes.test.js +102 -0
  236. package/dist/dashboard/__tests__/dashboard-routes.test.js.map +1 -0
  237. package/dist/dashboard/routes.d.ts +8 -0
  238. package/dist/dashboard/routes.d.ts.map +1 -0
  239. package/dist/dashboard/routes.js +70 -0
  240. package/dist/dashboard/routes.js.map +1 -0
  241. package/dist/exec/safe-spawn.d.ts.map +1 -1
  242. package/dist/exec/safe-spawn.js +6 -1
  243. package/dist/exec/safe-spawn.js.map +1 -1
  244. package/dist/graphrag/__tests__/hybrid-integration.test.js +1 -1
  245. package/dist/graphrag/__tests__/hybrid-integration.test.js.map +1 -1
  246. package/dist/graphrag/__tests__/no-heavy-dep-in-core.test.js +2 -2
  247. package/dist/graphrag/__tests__/no-heavy-dep-in-core.test.js.map +1 -1
  248. package/dist/graphrag/embeddings.js +1 -1
  249. package/dist/graphrag/embeddings.js.map +1 -1
  250. package/dist/http/__tests__/shared-app.test.d.ts +2 -0
  251. package/dist/http/__tests__/shared-app.test.d.ts.map +1 -0
  252. package/dist/http/__tests__/shared-app.test.js +57 -0
  253. package/dist/http/__tests__/shared-app.test.js.map +1 -0
  254. package/dist/http/app.d.ts +15 -0
  255. package/dist/http/app.d.ts.map +1 -0
  256. package/dist/http/app.js +32 -0
  257. package/dist/http/app.js.map +1 -0
  258. package/dist/mcp-server/server.d.ts.map +1 -1
  259. package/dist/mcp-server/server.js +5 -15
  260. package/dist/mcp-server/server.js.map +1 -1
  261. package/dist/reporters/__tests__/github-reporter.test.d.ts +2 -0
  262. package/dist/reporters/__tests__/github-reporter.test.d.ts.map +1 -0
  263. package/dist/reporters/__tests__/github-reporter.test.js +93 -0
  264. package/dist/reporters/__tests__/github-reporter.test.js.map +1 -0
  265. package/dist/reporters/ci-gate.d.ts +27 -0
  266. package/dist/reporters/ci-gate.d.ts.map +1 -0
  267. package/dist/reporters/ci-gate.js +100 -0
  268. package/dist/reporters/ci-gate.js.map +1 -0
  269. package/dist/reporters/github.d.ts +36 -0
  270. package/dist/reporters/github.d.ts.map +1 -0
  271. package/dist/reporters/github.js +142 -0
  272. package/dist/reporters/github.js.map +1 -0
  273. package/dist/skills/bundled.d.ts +5 -0
  274. package/dist/skills/bundled.d.ts.map +1 -0
  275. package/dist/skills/bundled.js +34 -0
  276. package/dist/skills/bundled.js.map +1 -0
  277. package/dist/skills/commands/add.d.ts +1 -3
  278. package/dist/skills/commands/add.d.ts.map +1 -1
  279. package/dist/skills/commands/add.js +20 -3
  280. package/dist/skills/commands/add.js.map +1 -1
  281. package/dist/skills/tests/bundled.spec.d.ts +2 -0
  282. package/dist/skills/tests/bundled.spec.d.ts.map +1 -0
  283. package/dist/skills/tests/bundled.spec.js +24 -0
  284. package/dist/skills/tests/bundled.spec.js.map +1 -0
  285. package/dist/telemetry/__tests__/aggregator.test.d.ts +2 -0
  286. package/dist/telemetry/__tests__/aggregator.test.d.ts.map +1 -0
  287. package/dist/telemetry/__tests__/aggregator.test.js +164 -0
  288. package/dist/telemetry/__tests__/aggregator.test.js.map +1 -0
  289. package/dist/telemetry/aggregator.d.ts +43 -0
  290. package/dist/telemetry/aggregator.d.ts.map +1 -0
  291. package/dist/telemetry/aggregator.js +282 -0
  292. package/dist/telemetry/aggregator.js.map +1 -0
  293. package/dist/types/UpdateManifest.types.d.ts +1 -1
  294. package/dist/types/UpdateManifest.types.d.ts.map +1 -1
  295. package/dist/utils/dag-converter.js +1 -1
  296. package/dist/utils/dag-converter.js.map +1 -1
  297. package/dist/utils/excalidraw-renderer.d.ts.map +1 -1
  298. package/dist/utils/excalidraw-renderer.js +1 -0
  299. package/dist/utils/excalidraw-renderer.js.map +1 -1
  300. package/dist/utils/graph-renderer.d.ts +2 -0
  301. package/dist/utils/graph-renderer.d.ts.map +1 -1
  302. package/dist/utils/graph-renderer.js +188 -14
  303. package/dist/utils/graph-renderer.js.map +1 -1
  304. package/dist/utils/project-detector.d.ts +1 -0
  305. package/dist/utils/project-detector.d.ts.map +1 -1
  306. package/dist/utils/project-detector.js +8 -0
  307. package/dist/utils/project-detector.js.map +1 -1
  308. package/dist/utils/project-generator.d.ts +1 -1
  309. package/dist/utils/project-generator.d.ts.map +1 -1
  310. package/dist/utils/project-generator.js +23 -2
  311. package/dist/utils/project-generator.js.map +1 -1
  312. package/dist/utils/templates.d.ts +2 -0
  313. package/dist/utils/templates.d.ts.map +1 -1
  314. package/dist/utils/templates.js +74 -0
  315. package/dist/utils/templates.js.map +1 -1
  316. package/dist/verification/__tests__/decay-policy.test.js +1 -0
  317. package/dist/verification/__tests__/decay-policy.test.js.map +1 -1
  318. package/dist/verification/__tests__/safe-spawn.test.js +12 -0
  319. package/dist/verification/__tests__/safe-spawn.test.js.map +1 -1
  320. package/dist/verification/config.d.ts.map +1 -1
  321. package/dist/verification/config.js +2 -0
  322. package/dist/verification/config.js.map +1 -1
  323. package/dist/verification/types.d.ts +2 -0
  324. package/dist/verification/types.d.ts.map +1 -1
  325. package/package.json +3 -2
  326. package/skills/dare-ax/generator.ts +325 -0
  327. package/skills/dare-ax/index.ts +19 -0
  328. package/skills/dare-ax/metrics.ts +352 -0
  329. package/skills/dare-ax/package-lock.json +1855 -0
  330. package/skills/dare-ax/package.json +50 -0
  331. package/skills/dare-ax/secret-detector.ts +123 -0
  332. package/skills/dare-ax/skill.yml +19 -0
  333. package/skills/dare-ax/templates/llms.txt.jinja2 +80 -0
  334. package/skills/dare-ax/tests/generator.spec.ts +193 -0
  335. package/skills/dare-ax/tests/metrics.spec.ts +394 -0
  336. package/skills/dare-ax/tests/validator.spec.ts +298 -0
  337. package/skills/dare-ax/tsconfig.json +18 -0
  338. package/skills/dare-ax/types.ts +79 -0
  339. package/skills/dare-ax/validator.ts +238 -0
  340. package/skills/dare-frontend-design/generator.ts +616 -0
  341. package/skills/dare-frontend-design/index.ts +25 -0
  342. package/skills/dare-frontend-design/linter.ts +227 -0
  343. package/skills/dare-frontend-design/metrics.ts +82 -0
  344. package/skills/dare-frontend-design/package-lock.json +1855 -0
  345. package/skills/dare-frontend-design/package.json +43 -0
  346. package/skills/dare-frontend-design/skill.yml +20 -0
  347. package/skills/dare-frontend-design/tests/frontend_design.spec.ts +435 -0
  348. package/skills/dare-frontend-design/tsconfig.json +18 -0
  349. package/skills/dare-frontend-design/types.ts +62 -0
  350. package/skills/dare-layered-design/generator.ts +740 -0
  351. package/skills/dare-layered-design/index.ts +17 -0
  352. package/skills/dare-layered-design/linter.ts +462 -0
  353. package/skills/dare-layered-design/metrics.ts +409 -0
  354. package/skills/dare-layered-design/package-lock.json +1855 -0
  355. package/skills/dare-layered-design/package.json +50 -0
  356. package/skills/dare-layered-design/skill.yml +35 -0
  357. package/skills/dare-layered-design/tests/generator.spec.ts +156 -0
  358. package/skills/dare-layered-design/tests/linter.spec.ts +255 -0
  359. package/skills/dare-layered-design/tests/metrics.spec.ts +286 -0
  360. package/skills/dare-layered-design/tsconfig.json +18 -0
  361. package/skills/dare-layered-design/types.ts +48 -0
  362. package/skills/dare-llm-integration/cache/llm_cache.ts +122 -0
  363. package/skills/dare-llm-integration/index.ts +49 -0
  364. package/skills/dare-llm-integration/metrics.ts +107 -0
  365. package/skills/dare-llm-integration/package-lock.json +1855 -0
  366. package/skills/dare-llm-integration/package.json +49 -0
  367. package/skills/dare-llm-integration/prompts/prompt_loader.ts +258 -0
  368. package/skills/dare-llm-integration/providers/anthropic_provider.ts +159 -0
  369. package/skills/dare-llm-integration/providers/dummy_provider.ts +113 -0
  370. package/skills/dare-llm-integration/providers/llm_provider.ts +6 -0
  371. package/skills/dare-llm-integration/providers/openai_provider.ts +215 -0
  372. package/skills/dare-llm-integration/rate_limit/token_bucket.ts +86 -0
  373. package/skills/dare-llm-integration/skill.yml +23 -0
  374. package/skills/dare-llm-integration/tests/fixtures/greet_v1.jinja2 +1 -0
  375. package/skills/dare-llm-integration/tests/fixtures/summarize_v1.jinja2 +1 -0
  376. package/skills/dare-llm-integration/tests/fixtures/summarize_v2.jinja2 +3 -0
  377. package/skills/dare-llm-integration/tests/llm_integration.spec.ts +657 -0
  378. package/skills/dare-llm-integration/tsconfig.json +23 -0
  379. package/skills/dare-llm-integration/types.ts +91 -0
  380. package/skills/dare-llm-integration/validators/output_validator.ts +200 -0
  381. package/skills/dare-quality-telemetry/collect.ts +134 -0
  382. package/skills/dare-quality-telemetry/collectors/dare_ax_collector.ts +301 -0
  383. package/skills/dare-quality-telemetry/collectors/dare_layered_design_collector.ts +406 -0
  384. package/skills/dare-quality-telemetry/collectors/index.ts +24 -0
  385. package/skills/dare-quality-telemetry/github_actions_template.ts +25 -0
  386. package/skills/dare-quality-telemetry/index.ts +18 -0
  387. package/skills/dare-quality-telemetry/metrics.ts +137 -0
  388. package/skills/dare-quality-telemetry/package-lock.json +1855 -0
  389. package/skills/dare-quality-telemetry/package.json +48 -0
  390. package/skills/dare-quality-telemetry/regression.ts +60 -0
  391. package/skills/dare-quality-telemetry/reporter.ts +132 -0
  392. package/skills/dare-quality-telemetry/skill.yml +18 -0
  393. package/skills/dare-quality-telemetry/tests/quality_telemetry.spec.ts +885 -0
  394. package/skills/dare-quality-telemetry/tsconfig.json +19 -0
  395. package/skills/dare-quality-telemetry/types.ts +41 -0
  396. package/skills/dare-realtime/event_registry.ts +101 -0
  397. package/skills/dare-realtime/index.ts +30 -0
  398. package/skills/dare-realtime/metrics.ts +84 -0
  399. package/skills/dare-realtime/package-lock.json +1855 -0
  400. package/skills/dare-realtime/package.json +43 -0
  401. package/skills/dare-realtime/reconnect_strategy.ts +85 -0
  402. package/skills/dare-realtime/schema_validator.ts +80 -0
  403. package/skills/dare-realtime/skill.yml +21 -0
  404. package/skills/dare-realtime/subscription_manager.ts +106 -0
  405. package/skills/dare-realtime/tests/realtime.spec.ts +482 -0
  406. package/skills/dare-realtime/tsconfig.json +18 -0
  407. package/skills/dare-realtime/types.ts +51 -0
  408. package/templates/.github/workflows/dare-pr.yml +26 -0
  409. package/templates/dashboard/app.js +184 -0
  410. package/templates/dashboard/index.html +63 -0
  411. package/templates/dashboard/style.css +134 -0
  412. package/templates/ide/antigravity/.agents/skills/dare-ai/SKILL.md +17 -0
  413. package/templates/ide/antigravity/.agents/skills/dare-blueprint/SKILL.md +2 -0
  414. package/templates/ide/antigravity/.agents/skills/dare-dashboard/SKILL.md +18 -0
  415. package/templates/ide/antigravity/.agents/skills/dare-design/SKILL.md +2 -0
  416. package/templates/ide/antigravity/.agents/skills/dare-dna/SKILL.md +3 -0
  417. package/templates/ide/antigravity/.agents/skills/dare-migrate/SKILL.md +3 -0
  418. package/templates/ide/antigravity/.agents/skills/dare-patterns/SKILL.md +3 -0
  419. package/templates/ide/antigravity/.agents/skills/dare-refine/SKILL.md +3 -0
  420. package/templates/ide/antigravity/.agents/skills/dare-reverse/SKILL.md +3 -0
  421. package/templates/ide/antigravity/.agents/skills/dare-review/SKILL.md +3 -0
  422. package/templates/ide/claude/.claude/commands/dare-ai.md +17 -0
  423. package/templates/ide/claude/.claude/commands/dare-blueprint.md +2 -0
  424. package/templates/ide/claude/.claude/commands/dare-dashboard.md +18 -0
  425. package/templates/ide/claude/.claude/commands/dare-design.md +2 -0
  426. package/templates/ide/claude/.claude/commands/dare-dna.md +2 -0
  427. package/templates/ide/claude/.claude/commands/dare-migrate.md +2 -0
  428. package/templates/ide/claude/.claude/commands/dare-patterns.md +3 -0
  429. package/templates/ide/claude/.claude/commands/dare-refine.md +3 -0
  430. package/templates/ide/claude/.claude/commands/dare-reverse.md +2 -0
  431. package/templates/ide/claude/.claude/commands/dare-review.md +3 -0
  432. package/templates/ide/cursor/.cursor/commands/dare-ai.md +17 -0
  433. package/templates/ide/cursor/.cursor/commands/dare-blueprint.md +3 -0
  434. package/templates/ide/cursor/.cursor/commands/dare-dashboard.md +18 -0
  435. package/templates/ide/cursor/.cursor/commands/dare-design.md +3 -0
  436. package/templates/ide/cursor/.cursor/commands/dare-dna.md +2 -0
  437. package/templates/ide/cursor/.cursor/commands/dare-migrate.md +2 -0
  438. package/templates/ide/cursor/.cursor/commands/dare-patterns.md +3 -0
  439. package/templates/ide/cursor/.cursor/commands/dare-refine.md +3 -0
  440. package/templates/ide/cursor/.cursor/commands/dare-reverse.md +2 -0
  441. package/templates/ide/cursor/.cursor/commands/dare-review.md +3 -0
@@ -0,0 +1,301 @@
1
+ /**
2
+ * dare-quality-telemetry — DareAx collector
3
+ * Collects M-01 to M-04 metrics for a project that should comply with dare-ax.
4
+ * License: MIT
5
+ */
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import { MetricResult } from '../types.js';
10
+
11
+ /** Required sections that must appear in a valid llms.txt */
12
+ const REQUIRED_LLMS_SECTIONS = [
13
+ 'Project Overview',
14
+ 'Tech Stack',
15
+ 'Architecture',
16
+ 'Getting Started',
17
+ 'For AI Agents',
18
+ ];
19
+
20
+ /** Rate-limit patterns to detect */
21
+ const RATE_LIMIT_PATTERNS: RegExp[] = [
22
+ /rack-attack/i,
23
+ /express-rate-limit/i,
24
+ /tower-governor/i,
25
+ /throttler/i,
26
+ /RateLimiter/i,
27
+ /rate[_-]?limit/i,
28
+ /slowapi/i,
29
+ /django-ratelimit/i,
30
+ /ulule\/limiter/i,
31
+ /golang\.org\/x\/time\/rate/i,
32
+ ];
33
+
34
+ /** Source file extensions for grep operations */
35
+ const SOURCE_EXTENSIONS = ['.ts', '.js', '.mjs', '.rb', '.rs', '.py', '.go', '.php'];
36
+
37
+ const SKIP_DIRS = new Set(['node_modules', 'target', 'dist', '.git', 'vendor', '__pycache__', 'coverage']);
38
+
39
+ export function collectDareAx(projectPath: string): Promise<MetricResult[]> {
40
+ return Promise.resolve([
41
+ collectM01(projectPath),
42
+ collectM02(projectPath),
43
+ collectM03(projectPath),
44
+ collectM04(projectPath),
45
+ ]);
46
+ }
47
+
48
+ /**
49
+ * M-01: llms.txt exists and has the 5 required sections.
50
+ */
51
+ function collectM01(projectPath: string): MetricResult {
52
+ const llmsTxtPath = path.join(projectPath, 'llms.txt');
53
+
54
+ if (!fs.existsSync(llmsTxtPath)) {
55
+ return {
56
+ id: 'M-01',
57
+ pass: false,
58
+ description: 'llms.txt exists and has 5 required sections',
59
+ details: `llms.txt not found at ${llmsTxtPath}`,
60
+ };
61
+ }
62
+
63
+ let content: string;
64
+ try {
65
+ content = fs.readFileSync(llmsTxtPath, 'utf-8');
66
+ } catch (err) {
67
+ return {
68
+ id: 'M-01',
69
+ pass: false,
70
+ description: 'llms.txt exists and has 5 required sections',
71
+ details: `Failed to read llms.txt: ${(err as Error).message}`,
72
+ };
73
+ }
74
+
75
+ const missingSections = REQUIRED_LLMS_SECTIONS.filter(
76
+ (section) => !content.includes(section)
77
+ );
78
+
79
+ if (missingSections.length > 0) {
80
+ return {
81
+ id: 'M-01',
82
+ pass: false,
83
+ description: 'llms.txt exists and has 5 required sections',
84
+ details: `Missing required sections: ${missingSections.join(', ')}`,
85
+ };
86
+ }
87
+
88
+ return {
89
+ id: 'M-01',
90
+ pass: true,
91
+ description: 'llms.txt exists and has 5 required sections',
92
+ details: 'All 5 required sections present in llms.txt',
93
+ };
94
+ }
95
+
96
+ /**
97
+ * M-02: openapi.json or public/openapi.json exists.
98
+ */
99
+ function collectM02(projectPath: string): MetricResult {
100
+ const candidates = [
101
+ path.join(projectPath, 'openapi.json'),
102
+ path.join(projectPath, 'public', 'openapi.json'),
103
+ path.join(projectPath, 'static', 'openapi.json'),
104
+ path.join(projectPath, 'docs', 'openapi.json'),
105
+ path.join(projectPath, 'api', 'openapi.json'),
106
+ path.join(projectPath, 'openapi.yaml'),
107
+ path.join(projectPath, 'openapi.yml'),
108
+ path.join(projectPath, 'public', 'openapi.yaml'),
109
+ ];
110
+
111
+ for (const candidate of candidates) {
112
+ if (fs.existsSync(candidate)) {
113
+ return {
114
+ id: 'M-02',
115
+ pass: true,
116
+ description: 'OpenAPI specification exists (openapi.json or public/openapi.json)',
117
+ details: `Found at ${path.relative(projectPath, candidate)}`,
118
+ };
119
+ }
120
+ }
121
+
122
+ return {
123
+ id: 'M-02',
124
+ pass: false,
125
+ description: 'OpenAPI specification exists (openapi.json or public/openapi.json)',
126
+ details:
127
+ 'No openapi.json or openapi.yaml found in project root, public/, static/, docs/, or api/ directories.',
128
+ };
129
+ }
130
+
131
+ /**
132
+ * M-03: CLI supports --json flag.
133
+ * Greps for --json in bin/, cli.ts, cli.js, dare.ts.
134
+ */
135
+ function collectM03(projectPath: string): MetricResult {
136
+ const JSON_FLAG_PATTERN = /--json/;
137
+
138
+ // Priority CLI files to check
139
+ const cliFileCandidates = [
140
+ path.join(projectPath, 'cli.ts'),
141
+ path.join(projectPath, 'cli.js'),
142
+ path.join(projectPath, 'dare.ts'),
143
+ path.join(projectPath, 'dare.js'),
144
+ ];
145
+
146
+ for (const candidate of cliFileCandidates) {
147
+ if (fs.existsSync(candidate) && fileMatchesPattern(candidate, JSON_FLAG_PATTERN)) {
148
+ return {
149
+ id: 'M-03',
150
+ pass: true,
151
+ description: 'CLI supports --json flag',
152
+ details: `Found --json flag in ${path.relative(projectPath, candidate)}`,
153
+ };
154
+ }
155
+ }
156
+
157
+ // Search in bin/ directory
158
+ const binDir = path.join(projectPath, 'bin');
159
+ if (fs.existsSync(binDir)) {
160
+ const found = searchDirForPattern(binDir, JSON_FLAG_PATTERN, SOURCE_EXTENSIONS, 3);
161
+ if (found) {
162
+ return {
163
+ id: 'M-03',
164
+ pass: true,
165
+ description: 'CLI supports --json flag',
166
+ details: `Found --json flag in ${path.relative(projectPath, found)}`,
167
+ };
168
+ }
169
+ }
170
+
171
+ // Search in src/ and lib/ directories
172
+ for (const dir of ['src', 'lib', 'app', 'cmd', 'cli']) {
173
+ const fullDir = path.join(projectPath, dir);
174
+ if (!fs.existsSync(fullDir)) continue;
175
+ const found = searchDirForPattern(fullDir, JSON_FLAG_PATTERN, SOURCE_EXTENSIONS, 3);
176
+ if (found) {
177
+ return {
178
+ id: 'M-03',
179
+ pass: true,
180
+ description: 'CLI supports --json flag',
181
+ details: `Found --json flag in ${path.relative(projectPath, found)}`,
182
+ };
183
+ }
184
+ }
185
+
186
+ return {
187
+ id: 'M-03',
188
+ pass: false,
189
+ description: 'CLI supports --json flag',
190
+ details:
191
+ 'No --json flag detected in CLI source files (bin/, cli.ts, cli.js, dare.ts). Add --json output flag to CLI commands.',
192
+ };
193
+ }
194
+
195
+ /**
196
+ * M-04: Rate limit configuration detected.
197
+ * Greps for known rate limit patterns in package manifests and source files.
198
+ */
199
+ function collectM04(projectPath: string): MetricResult {
200
+ // Check package manifests first
201
+ const manifests = [
202
+ path.join(projectPath, 'package.json'),
203
+ path.join(projectPath, 'Gemfile'),
204
+ path.join(projectPath, 'Cargo.toml'),
205
+ path.join(projectPath, 'go.mod'),
206
+ path.join(projectPath, 'requirements.txt'),
207
+ path.join(projectPath, 'pyproject.toml'),
208
+ path.join(projectPath, 'composer.json'),
209
+ ];
210
+
211
+ for (const manifest of manifests) {
212
+ if (!fs.existsSync(manifest)) continue;
213
+ try {
214
+ const content = fs.readFileSync(manifest, 'utf-8');
215
+ for (const pattern of RATE_LIMIT_PATTERNS) {
216
+ if (pattern.test(content)) {
217
+ const matchedLib = content.match(pattern)?.[0] ?? 'rate-limit';
218
+ return {
219
+ id: 'M-04',
220
+ pass: true,
221
+ description: 'Rate limit configuration detected',
222
+ details: `Found rate limit pattern "${matchedLib}" in ${path.relative(projectPath, manifest)}`,
223
+ };
224
+ }
225
+ }
226
+ } catch {
227
+ // Skip unreadable files
228
+ }
229
+ }
230
+
231
+ // Search source directories
232
+ for (const dir of ['src', 'lib', 'app', 'config']) {
233
+ const fullDir = path.join(projectPath, dir);
234
+ if (!fs.existsSync(fullDir)) continue;
235
+
236
+ for (const pattern of RATE_LIMIT_PATTERNS) {
237
+ const found = searchDirForPattern(fullDir, pattern, SOURCE_EXTENSIONS, 4);
238
+ if (found) {
239
+ return {
240
+ id: 'M-04',
241
+ pass: true,
242
+ description: 'Rate limit configuration detected',
243
+ details: `Found rate limit pattern in ${path.relative(projectPath, found)}`,
244
+ };
245
+ }
246
+ }
247
+ }
248
+
249
+ return {
250
+ id: 'M-04',
251
+ pass: false,
252
+ description: 'Rate limit configuration detected',
253
+ details:
254
+ 'No rate limit library or middleware detected. Add rate limiting (rack-attack, express-rate-limit, tower-governor, etc.) to protect public endpoints.',
255
+ };
256
+ }
257
+
258
+ // ── Helpers ──────────────────────────────────────────────────────────────────
259
+
260
+ function fileMatchesPattern(filePath: string, pattern: RegExp): boolean {
261
+ try {
262
+ const content = fs.readFileSync(filePath, 'utf-8');
263
+ return pattern.test(content);
264
+ } catch {
265
+ return false;
266
+ }
267
+ }
268
+
269
+ function searchDirForPattern(
270
+ dir: string,
271
+ pattern: RegExp,
272
+ extensions: string[],
273
+ maxDepth: number,
274
+ depth = 0
275
+ ): string | null {
276
+ if (depth > maxDepth) return null;
277
+
278
+ let entries: fs.Dirent[];
279
+ try {
280
+ entries = fs.readdirSync(dir, { withFileTypes: true });
281
+ } catch {
282
+ return null;
283
+ }
284
+
285
+ for (const entry of entries) {
286
+ if (entry.name.startsWith('.') || SKIP_DIRS.has(entry.name)) continue;
287
+
288
+ const fullPath = path.join(dir, entry.name);
289
+
290
+ if (entry.isDirectory()) {
291
+ const found = searchDirForPattern(fullPath, pattern, extensions, maxDepth, depth + 1);
292
+ if (found) return found;
293
+ } else if (entry.isFile()) {
294
+ const ext = path.extname(entry.name);
295
+ if (!extensions.includes(ext)) continue;
296
+ if (fileMatchesPattern(fullPath, pattern)) return fullPath;
297
+ }
298
+ }
299
+
300
+ return null;
301
+ }
@@ -0,0 +1,406 @@
1
+ /**
2
+ * dare-quality-telemetry — DareLayeredDesign collector
3
+ * Collects M-01 to M-04 metrics for a project that should comply with dare-layered-design.
4
+ * License: MIT
5
+ */
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import { MetricResult } from '../types.js';
10
+
11
+ /** Source file extensions */
12
+ const SOURCE_EXTENSIONS = ['.ts', '.js', '.rb', '.rs', '.py', '.go', '.php'];
13
+
14
+ /** Test file extensions (same as source but for test detection) */
15
+ const TEST_PATTERNS = ['.spec.ts', '.spec.js', '.test.ts', '.test.js', '_spec.rb', '_spec.ts', '_test.ts', '_test.js'];
16
+
17
+ const SKIP_DIRS = new Set(['node_modules', 'target', 'dist', '.git', 'vendor', '__pycache__', 'coverage']);
18
+
19
+ /** DI violation patterns: direct service instantiation inside handlers */
20
+ const DI_VIOLATION_PATTERNS: RegExp[] = [
21
+ /new\s+\w+Service\s*\(/,
22
+ /new\s+\w+UseCase\s*\(/,
23
+ /new\s+Create\w+\s*\(/,
24
+ /new\s+Update\w+\s*\(/,
25
+ /new\s+Delete\w+\s*\(/,
26
+ /\w+Service\.new\b/, // Ruby
27
+ /\w+UseCase\.new\b/, // Ruby
28
+ ];
29
+
30
+ /** DI positive patterns: proper injection mechanisms */
31
+ const DI_POSITIVE_PATTERNS: RegExp[] = [
32
+ /@Inject\s*\(/,
33
+ /constructor\s*\(\s*private/,
34
+ /Extension\s*\(/,
35
+ /@Injectable/,
36
+ /@Controller/,
37
+ /inject\s*\(/i,
38
+ ];
39
+
40
+ /** HTTP concern patterns inside repositories */
41
+ const HTTP_IN_REPO_PATTERNS: RegExp[] = [
42
+ /status\s*[:=]\s*[45]\d\d/,
43
+ /http_status|httpStatus|HttpStatus/i,
44
+ /res\.status\s*\(/,
45
+ /NotFoundException.*\(\s*[45]\d\d/,
46
+ /render\s+json.*status:/i,
47
+ /Response\.\w+\(\s*[45]\d\d/,
48
+ /throw new HttpException/i,
49
+ /import.*express/,
50
+ /import.*fastapi/,
51
+ /require.*axum/,
52
+ /rails.*controller/i,
53
+ ];
54
+
55
+ /** Handler→Repository direct call patterns */
56
+ const HANDLER_TO_REPO_PATTERNS: RegExp[] = [
57
+ /import.*[Rr]epository/,
58
+ /require.*[Rr]epository/,
59
+ /\w+Repository\s*\(/,
60
+ /\w+Repo\s*\./,
61
+ /\w+repository\s*\./i,
62
+ ];
63
+
64
+ export function collectDareLayeredDesign(projectPath: string): Promise<MetricResult[]> {
65
+ return Promise.resolve([
66
+ collectM01(projectPath),
67
+ collectM02(projectPath),
68
+ collectM03(projectPath),
69
+ collectM04(projectPath),
70
+ ]);
71
+ }
72
+
73
+ /**
74
+ * M-01: services/ directory has at least one test file (*.spec.* or *.test.*).
75
+ */
76
+ function collectM01(projectPath: string): MetricResult {
77
+ const serviceDirs = findLayerDirs(projectPath, ['services', 'use_cases', 'interactors', 'commands']);
78
+
79
+ if (serviceDirs.length === 0) {
80
+ return {
81
+ id: 'M-01',
82
+ pass: false,
83
+ description: '100% of Services have unit tests',
84
+ details: 'No services/ directory found in project. Create services layer first.',
85
+ };
86
+ }
87
+
88
+ // Find all service files (non-test)
89
+ const serviceFiles: string[] = [];
90
+ for (const dir of serviceDirs) {
91
+ collectFilesRecursive(dir, SOURCE_EXTENSIONS, 4).forEach((f) => {
92
+ if (!isTestFile(f)) serviceFiles.push(f);
93
+ });
94
+ }
95
+
96
+ if (serviceFiles.length === 0) {
97
+ return {
98
+ id: 'M-01',
99
+ pass: false,
100
+ description: '100% of Services have unit tests',
101
+ details: 'services/ directory exists but contains no service files.',
102
+ };
103
+ }
104
+
105
+ // Check if there are test files somewhere in the project
106
+ const testRoots = [
107
+ path.join(projectPath, 'tests'),
108
+ path.join(projectPath, 'test'),
109
+ path.join(projectPath, 'spec'),
110
+ path.join(projectPath, '__tests__'),
111
+ path.join(projectPath, 'src', '__tests__'),
112
+ ...serviceDirs, // co-located tests
113
+ ];
114
+
115
+ let testFilesFound = 0;
116
+ for (const root of testRoots) {
117
+ if (!fs.existsSync(root)) continue;
118
+ const files = collectFilesRecursive(root, SOURCE_EXTENSIONS, 5);
119
+ testFilesFound += files.filter(isTestFile).length;
120
+ }
121
+
122
+ if (testFilesFound === 0) {
123
+ return {
124
+ id: 'M-01',
125
+ pass: false,
126
+ description: '100% of Services have unit tests',
127
+ details: `Found ${serviceFiles.length} service file(s) but no test files (*.spec.* or *.test.*) in the project.`,
128
+ };
129
+ }
130
+
131
+ return {
132
+ id: 'M-01',
133
+ pass: true,
134
+ description: '100% of Services have unit tests',
135
+ details: `Found ${testFilesFound} test file(s) covering ${serviceFiles.length} service file(s).`,
136
+ };
137
+ }
138
+
139
+ /**
140
+ * M-02: 0% Handler→Repository direct calls.
141
+ * Scans handler files for direct repository imports/usage.
142
+ */
143
+ function collectM02(projectPath: string): MetricResult {
144
+ const handlerDirs = findLayerDirs(projectPath, ['handlers', 'controllers', 'routers', 'routes']);
145
+
146
+ if (handlerDirs.length === 0) {
147
+ return {
148
+ id: 'M-02',
149
+ pass: true,
150
+ description: '0% Handler→Repository direct calls',
151
+ details: 'No handler/controller directories found.',
152
+ };
153
+ }
154
+
155
+ const violations: Array<{ file: string; line: number; content: string }> = [];
156
+
157
+ for (const dir of handlerDirs) {
158
+ const files = collectFilesRecursive(dir, SOURCE_EXTENSIONS, 4);
159
+ for (const file of files) {
160
+ if (isTestFile(file)) continue;
161
+ findPatternViolations(file, HANDLER_TO_REPO_PATTERNS, violations);
162
+ }
163
+ }
164
+
165
+ if (violations.length > 0) {
166
+ const summary = violations
167
+ .slice(0, 3)
168
+ .map((v) => `${path.relative(projectPath, v.file)}:${v.line}`)
169
+ .join(', ');
170
+ return {
171
+ id: 'M-02',
172
+ pass: false,
173
+ description: '0% Handler→Repository direct calls',
174
+ details: `Found ${violations.length} Handler→Repository violation(s). First: ${summary}. Handlers must call Services, not Repositories directly.`,
175
+ };
176
+ }
177
+
178
+ const totalFiles = handlerDirs.reduce(
179
+ (sum, d) => sum + collectFilesRecursive(d, SOURCE_EXTENSIONS, 4).filter((f) => !isTestFile(f)).length,
180
+ 0
181
+ );
182
+
183
+ return {
184
+ id: 'M-02',
185
+ pass: true,
186
+ description: '0% Handler→Repository direct calls',
187
+ details: `No Handler→Repository violations found across ${totalFiles} handler file(s).`,
188
+ };
189
+ }
190
+
191
+ /**
192
+ * M-03: 100% of Handlers use dependency injection.
193
+ * Checks for DI patterns (@Inject, constructor(private), Extension()).
194
+ */
195
+ function collectM03(projectPath: string): MetricResult {
196
+ const handlerDirs = findLayerDirs(projectPath, ['handlers', 'controllers', 'routers', 'routes']);
197
+
198
+ if (handlerDirs.length === 0) {
199
+ return {
200
+ id: 'M-03',
201
+ pass: true,
202
+ description: '100% of Handlers use dependency injection',
203
+ details: 'No handler/controller directories found.',
204
+ };
205
+ }
206
+
207
+ const allHandlerFiles: string[] = [];
208
+ for (const dir of handlerDirs) {
209
+ collectFilesRecursive(dir, SOURCE_EXTENSIONS, 4).forEach((f) => {
210
+ if (!isTestFile(f)) allHandlerFiles.push(f);
211
+ });
212
+ }
213
+
214
+ if (allHandlerFiles.length === 0) {
215
+ return {
216
+ id: 'M-03',
217
+ pass: true,
218
+ description: '100% of Handlers use dependency injection',
219
+ details: 'No handler files to scan.',
220
+ };
221
+ }
222
+
223
+ // Check for DI violations (direct instantiation)
224
+ const diViolations: Array<{ file: string; line: number; content: string }> = [];
225
+ for (const file of allHandlerFiles) {
226
+ findPatternViolations(file, DI_VIOLATION_PATTERNS, diViolations);
227
+ }
228
+
229
+ if (diViolations.length > 0) {
230
+ const summary = diViolations
231
+ .slice(0, 3)
232
+ .map((v) => `${path.relative(projectPath, v.file)}:${v.line}`)
233
+ .join(', ');
234
+ return {
235
+ id: 'M-03',
236
+ pass: false,
237
+ description: '100% of Handlers use dependency injection',
238
+ details: `Found ${diViolations.length} handler(s) that instantiate services directly. Violations: ${summary}. Use @Inject, constructor(private ...) or Extension() instead.`,
239
+ };
240
+ }
241
+
242
+ // Check that at least some DI patterns exist
243
+ let diPatternsFound = 0;
244
+ for (const file of allHandlerFiles) {
245
+ try {
246
+ const content = fs.readFileSync(file, 'utf-8');
247
+ if (DI_POSITIVE_PATTERNS.some((p) => p.test(content))) {
248
+ diPatternsFound++;
249
+ }
250
+ } catch {
251
+ // skip
252
+ }
253
+ }
254
+
255
+ return {
256
+ id: 'M-03',
257
+ pass: true,
258
+ description: '100% of Handlers use dependency injection',
259
+ details: `No direct instantiation found in ${allHandlerFiles.length} handler file(s). ${diPatternsFound} file(s) use DI patterns.`,
260
+ };
261
+ }
262
+
263
+ /**
264
+ * M-04: 100% of Repositories are agnostic to upper layers.
265
+ * Checks that repositories don't import HTTP modules (express, fastapi, axum, rails).
266
+ */
267
+ function collectM04(projectPath: string): MetricResult {
268
+ const repoDirs = findLayerDirs(projectPath, ['repositories', 'repos', 'data_access', 'stores']);
269
+
270
+ if (repoDirs.length === 0) {
271
+ return {
272
+ id: 'M-04',
273
+ pass: true,
274
+ description: '100% of Repositories are agnostic to upper layers (no HTTP imports)',
275
+ details: 'No repository directories found.',
276
+ };
277
+ }
278
+
279
+ const violations: Array<{ file: string; line: number; content: string }> = [];
280
+
281
+ for (const dir of repoDirs) {
282
+ const files = collectFilesRecursive(dir, SOURCE_EXTENSIONS, 4);
283
+ for (const file of files) {
284
+ if (isTestFile(file)) continue;
285
+ findPatternViolations(file, HTTP_IN_REPO_PATTERNS, violations);
286
+ }
287
+ }
288
+
289
+ if (violations.length > 0) {
290
+ const summary = violations
291
+ .slice(0, 3)
292
+ .map((v) => `${path.relative(projectPath, v.file)}:${v.line} — "${v.content.slice(0, 60)}"`)
293
+ .join('; ');
294
+ return {
295
+ id: 'M-04',
296
+ pass: false,
297
+ description: '100% of Repositories are agnostic to upper layers (no HTTP imports)',
298
+ details: `Found ${violations.length} HTTP concern(s) in repository files. Repositories must not import express, fastapi, axum, or return HTTP status codes. Violations: ${summary}`,
299
+ };
300
+ }
301
+
302
+ const totalFiles = repoDirs.reduce(
303
+ (sum, d) => sum + collectFilesRecursive(d, SOURCE_EXTENSIONS, 4).filter((f) => !isTestFile(f)).length,
304
+ 0
305
+ );
306
+
307
+ return {
308
+ id: 'M-04',
309
+ pass: true,
310
+ description: '100% of Repositories are agnostic to upper layers (no HTTP imports)',
311
+ details: `No HTTP concerns found in ${totalFiles} repository file(s).`,
312
+ };
313
+ }
314
+
315
+ // ── Helpers ──────────────────────────────────────────────────────────────────
316
+
317
+ function findLayerDirs(projectPath: string, layerNames: string[]): string[] {
318
+ const results: string[] = [];
319
+ const searchRoots = [
320
+ projectPath,
321
+ ...['src', 'app', 'lib'].map((r) => path.join(projectPath, r)),
322
+ ];
323
+
324
+ for (const root of searchRoots) {
325
+ if (!fs.existsSync(root)) continue;
326
+ for (const name of layerNames) {
327
+ const candidate = path.join(root, name);
328
+ if (fs.existsSync(candidate)) results.push(candidate);
329
+ }
330
+ }
331
+
332
+ return results;
333
+ }
334
+
335
+ function collectFilesRecursive(
336
+ dir: string,
337
+ extensions: string[],
338
+ maxDepth: number,
339
+ depth = 0
340
+ ): string[] {
341
+ if (depth > maxDepth) return [];
342
+
343
+ let entries: fs.Dirent[];
344
+ try {
345
+ entries = fs.readdirSync(dir, { withFileTypes: true });
346
+ } catch {
347
+ return [];
348
+ }
349
+
350
+ const results: string[] = [];
351
+ for (const entry of entries) {
352
+ if (entry.name.startsWith('.') || SKIP_DIRS.has(entry.name)) continue;
353
+ const fp = path.join(dir, entry.name);
354
+ if (entry.isDirectory()) {
355
+ results.push(...collectFilesRecursive(fp, extensions, maxDepth, depth + 1));
356
+ } else if (entry.isFile()) {
357
+ const ext = path.extname(entry.name);
358
+ if (extensions.includes(ext)) results.push(fp);
359
+ }
360
+ }
361
+ return results;
362
+ }
363
+
364
+ function isTestFile(file: string): boolean {
365
+ return (
366
+ /\.(spec|test)\.[a-z]+$/.test(file) ||
367
+ /_spec\.[a-z]+$/.test(file) ||
368
+ /_test\.[a-z]+$/.test(file) ||
369
+ TEST_PATTERNS.some((p) => file.endsWith(p))
370
+ );
371
+ }
372
+
373
+ function findPatternViolations(
374
+ file: string,
375
+ patterns: RegExp[],
376
+ out: Array<{ file: string; line: number; content: string }>
377
+ ): void {
378
+ let content: string;
379
+ try {
380
+ content = fs.readFileSync(file, 'utf-8');
381
+ } catch {
382
+ return;
383
+ }
384
+
385
+ const lines = content.split('\n');
386
+ for (let i = 0; i < lines.length; i++) {
387
+ const line = lines[i];
388
+ const trimmed = line.trim();
389
+ // Skip comments
390
+ if (
391
+ trimmed.startsWith('//') ||
392
+ trimmed.startsWith('#') ||
393
+ trimmed.startsWith('*') ||
394
+ trimmed.startsWith('/*')
395
+ ) {
396
+ continue;
397
+ }
398
+
399
+ for (const pattern of patterns) {
400
+ if (pattern.test(line)) {
401
+ out.push({ file, line: i + 1, content: trimmed });
402
+ break;
403
+ }
404
+ }
405
+ }
406
+ }