@dewtech/dare-cli 3.11.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 (308) hide show
  1. package/README.md +2 -0
  2. package/dist/__tests__/ensure-skills.test.js +5 -0
  3. package/dist/__tests__/ensure-skills.test.js.map +1 -1
  4. package/dist/__tests__/ide-command-parity.test.js +1 -0
  5. package/dist/__tests__/ide-command-parity.test.js.map +1 -1
  6. package/dist/__tests__/project-generator.test.js +17 -0
  7. package/dist/__tests__/project-generator.test.js.map +1 -1
  8. package/dist/__tests__/reverse-facts.test.js +1 -0
  9. package/dist/__tests__/reverse-facts.test.js.map +1 -1
  10. package/dist/__tests__/terminal-parity-regression.test.d.ts +2 -0
  11. package/dist/__tests__/terminal-parity-regression.test.d.ts.map +1 -0
  12. package/dist/__tests__/terminal-parity-regression.test.js +116 -0
  13. package/dist/__tests__/terminal-parity-regression.test.js.map +1 -0
  14. package/dist/__tests__/terminal-parity.test.d.ts +2 -0
  15. package/dist/__tests__/terminal-parity.test.d.ts.map +1 -0
  16. package/dist/__tests__/terminal-parity.test.js +81 -0
  17. package/dist/__tests__/terminal-parity.test.js.map +1 -0
  18. package/dist/agent/__tests__/antigravity-driver.test.d.ts +2 -0
  19. package/dist/agent/__tests__/antigravity-driver.test.d.ts.map +1 -0
  20. package/dist/agent/__tests__/antigravity-driver.test.js +52 -0
  21. package/dist/agent/__tests__/antigravity-driver.test.js.map +1 -0
  22. package/dist/agent/__tests__/codex-driver.test.d.ts +2 -0
  23. package/dist/agent/__tests__/codex-driver.test.d.ts.map +1 -0
  24. package/dist/agent/__tests__/codex-driver.test.js +68 -0
  25. package/dist/agent/__tests__/codex-driver.test.js.map +1 -0
  26. package/dist/agent/__tests__/cursor-driver.test.d.ts +2 -0
  27. package/dist/agent/__tests__/cursor-driver.test.d.ts.map +1 -0
  28. package/dist/agent/__tests__/cursor-driver.test.js +52 -0
  29. package/dist/agent/__tests__/cursor-driver.test.js.map +1 -0
  30. package/dist/agent/driver.d.ts +1 -1
  31. package/dist/agent/driver.d.ts.map +1 -1
  32. package/dist/agent/drivers/antigravity.d.ts +8 -0
  33. package/dist/agent/drivers/antigravity.d.ts.map +1 -0
  34. package/dist/agent/drivers/antigravity.js +99 -0
  35. package/dist/agent/drivers/antigravity.js.map +1 -0
  36. package/dist/agent/drivers/codex.d.ts +12 -0
  37. package/dist/agent/drivers/codex.d.ts.map +1 -0
  38. package/dist/agent/drivers/codex.js +137 -0
  39. package/dist/agent/drivers/codex.js.map +1 -0
  40. package/dist/agent/drivers/cursor.d.ts +8 -0
  41. package/dist/agent/drivers/cursor.d.ts.map +1 -0
  42. package/dist/agent/drivers/cursor.js +99 -0
  43. package/dist/agent/drivers/cursor.js.map +1 -0
  44. package/dist/ai/__tests__/ai-core.test.d.ts +2 -0
  45. package/dist/ai/__tests__/ai-core.test.d.ts.map +1 -0
  46. package/dist/ai/__tests__/ai-core.test.js +41 -0
  47. package/dist/ai/__tests__/ai-core.test.js.map +1 -0
  48. package/dist/ai/__tests__/parity.test.d.ts +2 -0
  49. package/dist/ai/__tests__/parity.test.d.ts.map +1 -0
  50. package/dist/ai/__tests__/parity.test.js +36 -0
  51. package/dist/ai/__tests__/parity.test.js.map +1 -0
  52. package/dist/ai/__tests__/pipeline.test.d.ts +2 -0
  53. package/dist/ai/__tests__/pipeline.test.d.ts.map +1 -0
  54. package/dist/ai/__tests__/pipeline.test.js +147 -0
  55. package/dist/ai/__tests__/pipeline.test.js.map +1 -0
  56. package/dist/ai/__tests__/refine-bridge.test.d.ts +2 -0
  57. package/dist/ai/__tests__/refine-bridge.test.d.ts.map +1 -0
  58. package/dist/ai/__tests__/refine-bridge.test.js +17 -0
  59. package/dist/ai/__tests__/refine-bridge.test.js.map +1 -0
  60. package/dist/ai/__tests__/resolve.test.d.ts +2 -0
  61. package/dist/ai/__tests__/resolve.test.d.ts.map +1 -0
  62. package/dist/ai/__tests__/resolve.test.js +42 -0
  63. package/dist/ai/__tests__/resolve.test.js.map +1 -0
  64. package/dist/ai/capabilities.d.ts +3 -0
  65. package/dist/ai/capabilities.d.ts.map +1 -0
  66. package/dist/ai/capabilities.js +11 -0
  67. package/dist/ai/capabilities.js.map +1 -0
  68. package/dist/ai/command-options.d.ts +10 -0
  69. package/dist/ai/command-options.d.ts.map +1 -0
  70. package/dist/ai/command-options.js +15 -0
  71. package/dist/ai/command-options.js.map +1 -0
  72. package/dist/ai/config.d.ts +27 -0
  73. package/dist/ai/config.d.ts.map +1 -0
  74. package/dist/ai/config.js +89 -0
  75. package/dist/ai/config.js.map +1 -0
  76. package/dist/ai/parity.d.ts +13 -0
  77. package/dist/ai/parity.d.ts.map +1 -0
  78. package/dist/ai/parity.js +87 -0
  79. package/dist/ai/parity.js.map +1 -0
  80. package/dist/ai/parse-json-output.d.ts +5 -0
  81. package/dist/ai/parse-json-output.d.ts.map +1 -0
  82. package/dist/ai/parse-json-output.js +25 -0
  83. package/dist/ai/parse-json-output.js.map +1 -0
  84. package/dist/ai/pipeline.d.ts +20 -0
  85. package/dist/ai/pipeline.d.ts.map +1 -0
  86. package/dist/ai/pipeline.js +303 -0
  87. package/dist/ai/pipeline.js.map +1 -0
  88. package/dist/ai/prompts.d.ts +6 -0
  89. package/dist/ai/prompts.d.ts.map +1 -0
  90. package/dist/ai/prompts.js +49 -0
  91. package/dist/ai/prompts.js.map +1 -0
  92. package/dist/ai/providers.d.ts +63 -0
  93. package/dist/ai/providers.d.ts.map +1 -0
  94. package/dist/ai/providers.js +297 -0
  95. package/dist/ai/providers.js.map +1 -0
  96. package/dist/ai/refine-bridge.d.ts +5 -0
  97. package/dist/ai/refine-bridge.d.ts.map +1 -0
  98. package/dist/ai/refine-bridge.js +14 -0
  99. package/dist/ai/refine-bridge.js.map +1 -0
  100. package/dist/ai/registry.d.ts +12 -0
  101. package/dist/ai/registry.d.ts.map +1 -0
  102. package/dist/ai/registry.js +43 -0
  103. package/dist/ai/registry.js.map +1 -0
  104. package/dist/ai/resolve.d.ts +28 -0
  105. package/dist/ai/resolve.d.ts.map +1 -0
  106. package/dist/ai/resolve.js +83 -0
  107. package/dist/ai/resolve.js.map +1 -0
  108. package/dist/ai/schemas.d.ts +175 -0
  109. package/dist/ai/schemas.d.ts.map +1 -0
  110. package/dist/ai/schemas.js +199 -0
  111. package/dist/ai/schemas.js.map +1 -0
  112. package/dist/ai/types.d.ts +52 -0
  113. package/dist/ai/types.d.ts.map +1 -0
  114. package/dist/ai/types.js +8 -0
  115. package/dist/ai/types.js.map +1 -0
  116. package/dist/bin/dare.js +2 -0
  117. package/dist/bin/dare.js.map +1 -1
  118. package/dist/commands/__tests__/ai-command.test.d.ts +2 -0
  119. package/dist/commands/__tests__/ai-command.test.d.ts.map +1 -0
  120. package/dist/commands/__tests__/ai-command.test.js +68 -0
  121. package/dist/commands/__tests__/ai-command.test.js.map +1 -0
  122. package/dist/commands/__tests__/execute-agent.test.js +82 -0
  123. package/dist/commands/__tests__/execute-agent.test.js.map +1 -1
  124. package/dist/commands/ai.d.ts +3 -0
  125. package/dist/commands/ai.d.ts.map +1 -0
  126. package/dist/commands/ai.js +141 -0
  127. package/dist/commands/ai.js.map +1 -0
  128. package/dist/commands/blueprint.d.ts.map +1 -1
  129. package/dist/commands/blueprint.js +17 -3
  130. package/dist/commands/blueprint.js.map +1 -1
  131. package/dist/commands/design.d.ts.map +1 -1
  132. package/dist/commands/design.js +21 -2
  133. package/dist/commands/design.js.map +1 -1
  134. package/dist/commands/discover.d.ts.map +1 -1
  135. package/dist/commands/discover.js +9 -1
  136. package/dist/commands/discover.js.map +1 -1
  137. package/dist/commands/dna.d.ts.map +1 -1
  138. package/dist/commands/dna.js +23 -3
  139. package/dist/commands/dna.js.map +1 -1
  140. package/dist/commands/execute.d.ts +11 -0
  141. package/dist/commands/execute.d.ts.map +1 -1
  142. package/dist/commands/execute.js +111 -4
  143. package/dist/commands/execute.js.map +1 -1
  144. package/dist/commands/init.d.ts.map +1 -1
  145. package/dist/commands/init.js +1 -0
  146. package/dist/commands/init.js.map +1 -1
  147. package/dist/commands/migrate.d.ts.map +1 -1
  148. package/dist/commands/migrate.js +14 -2
  149. package/dist/commands/migrate.js.map +1 -1
  150. package/dist/commands/patterns.d.ts.map +1 -1
  151. package/dist/commands/patterns.js +14 -2
  152. package/dist/commands/patterns.js.map +1 -1
  153. package/dist/commands/refine.d.ts.map +1 -1
  154. package/dist/commands/refine.js +23 -2
  155. package/dist/commands/refine.js.map +1 -1
  156. package/dist/commands/reverse.d.ts.map +1 -1
  157. package/dist/commands/reverse.js +28 -3
  158. package/dist/commands/reverse.js.map +1 -1
  159. package/dist/commands/review.d.ts.map +1 -1
  160. package/dist/commands/review.js +25 -3
  161. package/dist/commands/review.js.map +1 -1
  162. package/dist/core/types/project.d.ts +1 -1
  163. package/dist/core/types/project.d.ts.map +1 -1
  164. package/dist/dag-runner/run_dag.d.ts +1 -1
  165. package/dist/dag-runner/run_dag.d.ts.map +1 -1
  166. package/dist/exec/safe-spawn.d.ts.map +1 -1
  167. package/dist/exec/safe-spawn.js +6 -1
  168. package/dist/exec/safe-spawn.js.map +1 -1
  169. package/dist/skills/bundled.d.ts +5 -0
  170. package/dist/skills/bundled.d.ts.map +1 -0
  171. package/dist/skills/bundled.js +34 -0
  172. package/dist/skills/bundled.js.map +1 -0
  173. package/dist/skills/commands/add.d.ts +1 -3
  174. package/dist/skills/commands/add.d.ts.map +1 -1
  175. package/dist/skills/commands/add.js +20 -3
  176. package/dist/skills/commands/add.js.map +1 -1
  177. package/dist/skills/tests/bundled.spec.d.ts +2 -0
  178. package/dist/skills/tests/bundled.spec.d.ts.map +1 -0
  179. package/dist/skills/tests/bundled.spec.js +24 -0
  180. package/dist/skills/tests/bundled.spec.js.map +1 -0
  181. package/dist/types/UpdateManifest.types.d.ts +1 -1
  182. package/dist/types/UpdateManifest.types.d.ts.map +1 -1
  183. package/dist/utils/dag-converter.js +1 -1
  184. package/dist/utils/dag-converter.js.map +1 -1
  185. package/dist/utils/project-detector.d.ts +1 -0
  186. package/dist/utils/project-detector.d.ts.map +1 -1
  187. package/dist/utils/project-detector.js +8 -0
  188. package/dist/utils/project-detector.js.map +1 -1
  189. package/dist/utils/project-generator.d.ts +1 -1
  190. package/dist/utils/project-generator.d.ts.map +1 -1
  191. package/dist/utils/project-generator.js +23 -2
  192. package/dist/utils/project-generator.js.map +1 -1
  193. package/dist/utils/templates.d.ts +2 -0
  194. package/dist/utils/templates.d.ts.map +1 -1
  195. package/dist/utils/templates.js +74 -0
  196. package/dist/utils/templates.js.map +1 -1
  197. package/dist/verification/__tests__/safe-spawn.test.js +12 -0
  198. package/dist/verification/__tests__/safe-spawn.test.js.map +1 -1
  199. package/package.json +2 -1
  200. package/skills/dare-ax/generator.ts +325 -0
  201. package/skills/dare-ax/index.ts +19 -0
  202. package/skills/dare-ax/metrics.ts +352 -0
  203. package/skills/dare-ax/package-lock.json +1855 -0
  204. package/skills/dare-ax/package.json +50 -0
  205. package/skills/dare-ax/secret-detector.ts +123 -0
  206. package/skills/dare-ax/skill.yml +19 -0
  207. package/skills/dare-ax/templates/llms.txt.jinja2 +80 -0
  208. package/skills/dare-ax/tests/generator.spec.ts +193 -0
  209. package/skills/dare-ax/tests/metrics.spec.ts +394 -0
  210. package/skills/dare-ax/tests/validator.spec.ts +298 -0
  211. package/skills/dare-ax/tsconfig.json +18 -0
  212. package/skills/dare-ax/types.ts +79 -0
  213. package/skills/dare-ax/validator.ts +238 -0
  214. package/skills/dare-frontend-design/generator.ts +616 -0
  215. package/skills/dare-frontend-design/index.ts +25 -0
  216. package/skills/dare-frontend-design/linter.ts +227 -0
  217. package/skills/dare-frontend-design/metrics.ts +82 -0
  218. package/skills/dare-frontend-design/package-lock.json +1855 -0
  219. package/skills/dare-frontend-design/package.json +43 -0
  220. package/skills/dare-frontend-design/skill.yml +20 -0
  221. package/skills/dare-frontend-design/tests/frontend_design.spec.ts +435 -0
  222. package/skills/dare-frontend-design/tsconfig.json +18 -0
  223. package/skills/dare-frontend-design/types.ts +62 -0
  224. package/skills/dare-layered-design/generator.ts +740 -0
  225. package/skills/dare-layered-design/index.ts +17 -0
  226. package/skills/dare-layered-design/linter.ts +462 -0
  227. package/skills/dare-layered-design/metrics.ts +409 -0
  228. package/skills/dare-layered-design/package-lock.json +1855 -0
  229. package/skills/dare-layered-design/package.json +50 -0
  230. package/skills/dare-layered-design/skill.yml +35 -0
  231. package/skills/dare-layered-design/tests/generator.spec.ts +156 -0
  232. package/skills/dare-layered-design/tests/linter.spec.ts +255 -0
  233. package/skills/dare-layered-design/tests/metrics.spec.ts +286 -0
  234. package/skills/dare-layered-design/tsconfig.json +18 -0
  235. package/skills/dare-layered-design/types.ts +48 -0
  236. package/skills/dare-llm-integration/cache/llm_cache.ts +122 -0
  237. package/skills/dare-llm-integration/index.ts +49 -0
  238. package/skills/dare-llm-integration/metrics.ts +107 -0
  239. package/skills/dare-llm-integration/package-lock.json +1855 -0
  240. package/skills/dare-llm-integration/package.json +49 -0
  241. package/skills/dare-llm-integration/prompts/prompt_loader.ts +258 -0
  242. package/skills/dare-llm-integration/providers/anthropic_provider.ts +159 -0
  243. package/skills/dare-llm-integration/providers/dummy_provider.ts +113 -0
  244. package/skills/dare-llm-integration/providers/llm_provider.ts +6 -0
  245. package/skills/dare-llm-integration/providers/openai_provider.ts +215 -0
  246. package/skills/dare-llm-integration/rate_limit/token_bucket.ts +86 -0
  247. package/skills/dare-llm-integration/skill.yml +23 -0
  248. package/skills/dare-llm-integration/tests/fixtures/greet_v1.jinja2 +1 -0
  249. package/skills/dare-llm-integration/tests/fixtures/summarize_v1.jinja2 +1 -0
  250. package/skills/dare-llm-integration/tests/fixtures/summarize_v2.jinja2 +3 -0
  251. package/skills/dare-llm-integration/tests/llm_integration.spec.ts +657 -0
  252. package/skills/dare-llm-integration/tsconfig.json +23 -0
  253. package/skills/dare-llm-integration/types.ts +91 -0
  254. package/skills/dare-llm-integration/validators/output_validator.ts +200 -0
  255. package/skills/dare-quality-telemetry/collect.ts +134 -0
  256. package/skills/dare-quality-telemetry/collectors/dare_ax_collector.ts +301 -0
  257. package/skills/dare-quality-telemetry/collectors/dare_layered_design_collector.ts +406 -0
  258. package/skills/dare-quality-telemetry/collectors/index.ts +24 -0
  259. package/skills/dare-quality-telemetry/github_actions_template.ts +25 -0
  260. package/skills/dare-quality-telemetry/index.ts +18 -0
  261. package/skills/dare-quality-telemetry/metrics.ts +137 -0
  262. package/skills/dare-quality-telemetry/package-lock.json +1855 -0
  263. package/skills/dare-quality-telemetry/package.json +48 -0
  264. package/skills/dare-quality-telemetry/regression.ts +60 -0
  265. package/skills/dare-quality-telemetry/reporter.ts +132 -0
  266. package/skills/dare-quality-telemetry/skill.yml +18 -0
  267. package/skills/dare-quality-telemetry/tests/quality_telemetry.spec.ts +885 -0
  268. package/skills/dare-quality-telemetry/tsconfig.json +19 -0
  269. package/skills/dare-quality-telemetry/types.ts +41 -0
  270. package/skills/dare-realtime/event_registry.ts +101 -0
  271. package/skills/dare-realtime/index.ts +30 -0
  272. package/skills/dare-realtime/metrics.ts +84 -0
  273. package/skills/dare-realtime/package-lock.json +1855 -0
  274. package/skills/dare-realtime/package.json +43 -0
  275. package/skills/dare-realtime/reconnect_strategy.ts +85 -0
  276. package/skills/dare-realtime/schema_validator.ts +80 -0
  277. package/skills/dare-realtime/skill.yml +21 -0
  278. package/skills/dare-realtime/subscription_manager.ts +106 -0
  279. package/skills/dare-realtime/tests/realtime.spec.ts +482 -0
  280. package/skills/dare-realtime/tsconfig.json +18 -0
  281. package/skills/dare-realtime/types.ts +51 -0
  282. package/templates/ide/antigravity/.agents/skills/dare-ai/SKILL.md +17 -0
  283. package/templates/ide/antigravity/.agents/skills/dare-blueprint/SKILL.md +2 -0
  284. package/templates/ide/antigravity/.agents/skills/dare-design/SKILL.md +2 -0
  285. package/templates/ide/antigravity/.agents/skills/dare-dna/SKILL.md +3 -0
  286. package/templates/ide/antigravity/.agents/skills/dare-migrate/SKILL.md +3 -0
  287. package/templates/ide/antigravity/.agents/skills/dare-patterns/SKILL.md +3 -0
  288. package/templates/ide/antigravity/.agents/skills/dare-refine/SKILL.md +3 -0
  289. package/templates/ide/antigravity/.agents/skills/dare-reverse/SKILL.md +3 -0
  290. package/templates/ide/antigravity/.agents/skills/dare-review/SKILL.md +3 -0
  291. package/templates/ide/claude/.claude/commands/dare-ai.md +17 -0
  292. package/templates/ide/claude/.claude/commands/dare-blueprint.md +2 -0
  293. package/templates/ide/claude/.claude/commands/dare-design.md +2 -0
  294. package/templates/ide/claude/.claude/commands/dare-dna.md +2 -0
  295. package/templates/ide/claude/.claude/commands/dare-migrate.md +2 -0
  296. package/templates/ide/claude/.claude/commands/dare-patterns.md +3 -0
  297. package/templates/ide/claude/.claude/commands/dare-refine.md +3 -0
  298. package/templates/ide/claude/.claude/commands/dare-reverse.md +2 -0
  299. package/templates/ide/claude/.claude/commands/dare-review.md +3 -0
  300. package/templates/ide/cursor/.cursor/commands/dare-ai.md +17 -0
  301. package/templates/ide/cursor/.cursor/commands/dare-blueprint.md +3 -0
  302. package/templates/ide/cursor/.cursor/commands/dare-design.md +3 -0
  303. package/templates/ide/cursor/.cursor/commands/dare-dna.md +2 -0
  304. package/templates/ide/cursor/.cursor/commands/dare-migrate.md +2 -0
  305. package/templates/ide/cursor/.cursor/commands/dare-patterns.md +3 -0
  306. package/templates/ide/cursor/.cursor/commands/dare-refine.md +3 -0
  307. package/templates/ide/cursor/.cursor/commands/dare-reverse.md +2 -0
  308. package/templates/ide/cursor/.cursor/commands/dare-review.md +3 -0
@@ -0,0 +1,352 @@
1
+ /**
2
+ * dare-ax — DareAxMetrics
3
+ * Collects M-01 to M-04 metrics for a project.
4
+ * License: MIT
5
+ */
6
+
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import { MetricResult } from './types.js';
10
+ import { DareAxValidator } from './validator.js';
11
+
12
+ /**
13
+ * Known rate-limit library identifiers, checked in:
14
+ * - package.json / Gemfile / Cargo.toml / go.mod / requirements.txt / composer.json
15
+ * - Source files (middleware setup patterns)
16
+ */
17
+ const RATE_LIMIT_LIBRARIES: Array<{ name: string; pattern: RegExp }> = [
18
+ // Ruby / Rails
19
+ { name: 'rack-attack', pattern: /rack-attack/i },
20
+ // Node.js
21
+ { name: 'express-rate-limit', pattern: /express-rate-limit/i },
22
+ { name: '@nestjs/throttler', pattern: /@nestjs\/throttler/i },
23
+ { name: 'rate-limiter-flexible', pattern: /rate-limiter-flexible/i },
24
+ { name: 'limiter', pattern: /["']limiter["']/ },
25
+ // Rust
26
+ { name: 'tower-governor', pattern: /tower-governor/i },
27
+ { name: 'axum-ratelimit', pattern: /axum-ratelimit/i },
28
+ // Python
29
+ { name: 'slowapi', pattern: /slowapi/i },
30
+ { name: 'limits', pattern: /["']limits["']/ },
31
+ { name: 'django-ratelimit', pattern: /django-ratelimit/i },
32
+ // Go
33
+ { name: 'golang.org/x/time/rate', pattern: /golang\.org\/x\/time\/rate/i },
34
+ { name: 'ulule/limiter', pattern: /ulule\/limiter/i },
35
+ // PHP
36
+ { name: 'laravel-rate-limiting', pattern: /throttle:/i },
37
+ // Generic middleware patterns in source
38
+ { name: 'RateLimit middleware', pattern: /rate[_-]?limit/i },
39
+ ];
40
+
41
+ /** Known CLI JSON flag patterns to look for in source code */
42
+ const JSON_FLAG_PATTERNS: RegExp[] = [
43
+ /--json/,
44
+ /['"]json['"]\s*,.*flag/i,
45
+ /\.option\(['"]-?-?json/i,
46
+ /jsonOutput/i,
47
+ /output.*json/i,
48
+ /format.*json/i,
49
+ ];
50
+
51
+ export class DareAxMetrics {
52
+ private readonly validator: DareAxValidator;
53
+
54
+ constructor() {
55
+ this.validator = new DareAxValidator();
56
+ }
57
+
58
+ /**
59
+ * Collects all four metrics for the given project path.
60
+ *
61
+ * @param projectPath - Absolute path to the project root.
62
+ * @returns Array of MetricResult for M-01 to M-04.
63
+ */
64
+ collect(projectPath: string): MetricResult[] {
65
+ return [
66
+ this.collectM01(projectPath),
67
+ this.collectM02(projectPath),
68
+ this.collectM03(projectPath),
69
+ this.collectM04(projectPath),
70
+ ];
71
+ }
72
+
73
+ /**
74
+ * M-01: llms.txt exists and is valid (no secrets, required sections present).
75
+ */
76
+ collectM01(projectPath: string): MetricResult {
77
+ const llmsTxtPath = path.join(projectPath, 'llms.txt');
78
+
79
+ if (!fs.existsSync(llmsTxtPath)) {
80
+ return {
81
+ id: 'M-01',
82
+ pass: false,
83
+ description: 'llms.txt exists and is valid',
84
+ detail: `llms.txt not found at ${llmsTxtPath}`,
85
+ };
86
+ }
87
+
88
+ const result = this.validator.validate(llmsTxtPath);
89
+
90
+ if (!result.valid) {
91
+ const errorSummary = result.errors.map((e) => e.message).join('; ');
92
+ return {
93
+ id: 'M-01',
94
+ pass: false,
95
+ description: 'llms.txt exists and is valid',
96
+ detail: `Validation failed: ${errorSummary}`,
97
+ };
98
+ }
99
+
100
+ const warningSummary =
101
+ result.warnings.length > 0
102
+ ? ` (${result.warnings.length} warnings: ${result.warnings.map((w) => w.code).join(', ')})`
103
+ : '';
104
+
105
+ return {
106
+ id: 'M-01',
107
+ pass: true,
108
+ description: 'llms.txt exists and is valid',
109
+ detail: `Valid${warningSummary}`,
110
+ };
111
+ }
112
+
113
+ /**
114
+ * M-02: openapi.json exists (checks public/openapi.json and openapi.json).
115
+ */
116
+ collectM02(projectPath: string): MetricResult {
117
+ const candidates = [
118
+ path.join(projectPath, 'public', 'openapi.json'),
119
+ path.join(projectPath, 'openapi.json'),
120
+ path.join(projectPath, 'static', 'openapi.json'),
121
+ path.join(projectPath, 'docs', 'openapi.json'),
122
+ path.join(projectPath, 'api', 'openapi.json'),
123
+ // Also support YAML variants
124
+ path.join(projectPath, 'openapi.yaml'),
125
+ path.join(projectPath, 'openapi.yml'),
126
+ path.join(projectPath, 'public', 'openapi.yaml'),
127
+ ];
128
+
129
+ for (const candidate of candidates) {
130
+ if (fs.existsSync(candidate)) {
131
+ return {
132
+ id: 'M-02',
133
+ pass: true,
134
+ description: 'OpenAPI specification exists',
135
+ detail: `Found at ${path.relative(projectPath, candidate)}`,
136
+ };
137
+ }
138
+ }
139
+
140
+ return {
141
+ id: 'M-02',
142
+ pass: false,
143
+ description: 'OpenAPI specification exists',
144
+ detail:
145
+ 'No openapi.json or openapi.yaml found in project root, public/, static/, docs/, or api/ directories.',
146
+ };
147
+ }
148
+
149
+ /**
150
+ * M-03: CLI supports --json flag.
151
+ * Searches source code for --json flag definitions.
152
+ */
153
+ collectM03(projectPath: string): MetricResult {
154
+ const sourceExtensions = ['.ts', '.js', '.mjs', '.rb', '.rs', '.py', '.go', '.php'];
155
+ const searchDirs = [
156
+ path.join(projectPath, 'src'),
157
+ path.join(projectPath, 'lib'),
158
+ path.join(projectPath, 'app'),
159
+ path.join(projectPath, 'cmd'),
160
+ path.join(projectPath, 'bin'),
161
+ path.join(projectPath, 'cli'),
162
+ ];
163
+
164
+ // Search for --json pattern in CLI-related source files
165
+ for (const dir of searchDirs) {
166
+ if (!fs.existsSync(dir)) continue;
167
+
168
+ const found = searchDirectoryForPattern(dir, JSON_FLAG_PATTERNS, sourceExtensions, 3);
169
+ if (found) {
170
+ return {
171
+ id: 'M-03',
172
+ pass: true,
173
+ description: 'CLI supports --json flag',
174
+ detail: `Found --json flag pattern in ${path.relative(projectPath, found)}`,
175
+ };
176
+ }
177
+ }
178
+
179
+ // Also check root-level bin files
180
+ const rootBinCandidates = findFilesWithPattern(projectPath, /^(cli|main|index|app)\.(ts|js|rb|rs|py|go)$/, 1);
181
+ for (const file of rootBinCandidates) {
182
+ if (fileMatchesAnyPattern(file, JSON_FLAG_PATTERNS)) {
183
+ return {
184
+ id: 'M-03',
185
+ pass: true,
186
+ description: 'CLI supports --json flag',
187
+ detail: `Found --json flag pattern in ${path.relative(projectPath, file)}`,
188
+ };
189
+ }
190
+ }
191
+
192
+ return {
193
+ id: 'M-03',
194
+ pass: false,
195
+ description: 'CLI supports --json flag',
196
+ detail:
197
+ 'No --json flag detected in CLI source files. Add --json output flag to CLI commands (see dare-ax ADR-04).',
198
+ };
199
+ }
200
+
201
+ /**
202
+ * M-04: Rate limit configuration detected.
203
+ * Checks package manifests and source code.
204
+ */
205
+ collectM04(projectPath: string): MetricResult {
206
+ // Check package manifests
207
+ const manifests = [
208
+ { file: path.join(projectPath, 'package.json'), key: 'dependencies' },
209
+ { file: path.join(projectPath, 'package.json'), key: 'devDependencies' },
210
+ { file: path.join(projectPath, 'Gemfile'), key: null },
211
+ { file: path.join(projectPath, 'Cargo.toml'), key: null },
212
+ { file: path.join(projectPath, 'go.mod'), key: null },
213
+ { file: path.join(projectPath, 'requirements.txt'), key: null },
214
+ { file: path.join(projectPath, 'composer.json'), key: null },
215
+ { file: path.join(projectPath, 'pyproject.toml'), key: null },
216
+ ];
217
+
218
+ for (const { file } of manifests) {
219
+ if (!fs.existsSync(file)) continue;
220
+
221
+ try {
222
+ const content = fs.readFileSync(file, 'utf-8');
223
+ for (const { name, pattern } of RATE_LIMIT_LIBRARIES) {
224
+ if (pattern.test(content)) {
225
+ return {
226
+ id: 'M-04',
227
+ pass: true,
228
+ description: 'Rate limit configuration detected',
229
+ detail: `Found rate limit library "${name}" in ${path.relative(projectPath, file)}`,
230
+ };
231
+ }
232
+ }
233
+ } catch {
234
+ // Skip unreadable files
235
+ }
236
+ }
237
+
238
+ // Search source directories for rate limit patterns
239
+ const sourceDirs = [
240
+ path.join(projectPath, 'src'),
241
+ path.join(projectPath, 'lib'),
242
+ path.join(projectPath, 'app'),
243
+ path.join(projectPath, 'config'),
244
+ ];
245
+
246
+ const rateLimitPatterns = RATE_LIMIT_LIBRARIES.map((l) => l.pattern);
247
+
248
+ for (const dir of sourceDirs) {
249
+ if (!fs.existsSync(dir)) continue;
250
+
251
+ const found = searchDirectoryForPattern(
252
+ dir,
253
+ rateLimitPatterns,
254
+ ['.ts', '.js', '.rb', '.rs', '.py', '.go', '.php'],
255
+ 4
256
+ );
257
+
258
+ if (found) {
259
+ return {
260
+ id: 'M-04',
261
+ pass: true,
262
+ description: 'Rate limit configuration detected',
263
+ detail: `Found rate limit pattern in ${path.relative(projectPath, found)}`,
264
+ };
265
+ }
266
+ }
267
+
268
+ return {
269
+ id: 'M-04',
270
+ pass: false,
271
+ description: 'Rate limit configuration detected',
272
+ detail:
273
+ 'No rate limit library or middleware detected. ' +
274
+ 'Add rate limiting (rack-attack, express-rate-limit, tower-governor, etc.) to protect public endpoints.',
275
+ };
276
+ }
277
+ }
278
+
279
+ // ── Helpers ─────────────────────────────────────────────────────────────────
280
+
281
+ /**
282
+ * Recursively searches a directory for files matching given patterns.
283
+ * Returns the first matching file path, or null.
284
+ */
285
+ function searchDirectoryForPattern(
286
+ dir: string,
287
+ patterns: RegExp[],
288
+ extensions: string[],
289
+ maxDepth: number,
290
+ currentDepth = 0
291
+ ): string | null {
292
+ if (currentDepth > maxDepth) return null;
293
+
294
+ let entries: fs.Dirent[];
295
+ try {
296
+ entries = fs.readdirSync(dir, { withFileTypes: true });
297
+ } catch {
298
+ return null;
299
+ }
300
+
301
+ for (const entry of entries) {
302
+ if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'target' || entry.name === 'dist') {
303
+ continue;
304
+ }
305
+
306
+ const fullPath = path.join(dir, entry.name);
307
+
308
+ if (entry.isDirectory()) {
309
+ const found = searchDirectoryForPattern(fullPath, patterns, extensions, maxDepth, currentDepth + 1);
310
+ if (found) return found;
311
+ } else if (entry.isFile()) {
312
+ const ext = path.extname(entry.name);
313
+ if (!extensions.includes(ext)) continue;
314
+
315
+ if (fileMatchesAnyPattern(fullPath, patterns)) {
316
+ return fullPath;
317
+ }
318
+ }
319
+ }
320
+
321
+ return null;
322
+ }
323
+
324
+ /**
325
+ * Reads a file and returns true if any of the patterns match its content.
326
+ */
327
+ function fileMatchesAnyPattern(filePath: string, patterns: RegExp[]): boolean {
328
+ let content: string;
329
+ try {
330
+ content = fs.readFileSync(filePath, 'utf-8');
331
+ } catch {
332
+ return false;
333
+ }
334
+
335
+ return patterns.some((p) => p.test(content));
336
+ }
337
+
338
+ /**
339
+ * Returns files in a directory (non-recursive) whose names match the given pattern.
340
+ */
341
+ function findFilesWithPattern(dir: string, namePattern: RegExp, _maxDepth: number): string[] {
342
+ let entries: fs.Dirent[];
343
+ try {
344
+ entries = fs.readdirSync(dir, { withFileTypes: true });
345
+ } catch {
346
+ return [];
347
+ }
348
+
349
+ return entries
350
+ .filter((e) => e.isFile() && namePattern.test(e.name))
351
+ .map((e) => path.join(dir, e.name));
352
+ }