@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.
- package/README.md +7 -1
- package/dist/__tests__/ci-pr-regression.test.d.ts +2 -0
- package/dist/__tests__/ci-pr-regression.test.d.ts.map +1 -0
- package/dist/__tests__/ci-pr-regression.test.js +76 -0
- package/dist/__tests__/ci-pr-regression.test.js.map +1 -0
- package/dist/__tests__/dashboard-front.test.d.ts +2 -0
- package/dist/__tests__/dashboard-front.test.d.ts.map +1 -0
- package/dist/__tests__/dashboard-front.test.js +37 -0
- package/dist/__tests__/dashboard-front.test.js.map +1 -0
- package/dist/__tests__/dashboard-regression.test.d.ts +2 -0
- package/dist/__tests__/dashboard-regression.test.d.ts.map +1 -0
- package/dist/__tests__/dashboard-regression.test.js +68 -0
- package/dist/__tests__/dashboard-regression.test.js.map +1 -0
- package/dist/__tests__/dynamic-dag-regression.test.d.ts +2 -0
- package/dist/__tests__/dynamic-dag-regression.test.d.ts.map +1 -0
- package/dist/__tests__/dynamic-dag-regression.test.js +464 -0
- package/dist/__tests__/dynamic-dag-regression.test.js.map +1 -0
- package/dist/__tests__/ensure-skills.test.js +5 -0
- package/dist/__tests__/ensure-skills.test.js.map +1 -1
- package/dist/__tests__/ide-command-parity.test.js +2 -0
- package/dist/__tests__/ide-command-parity.test.js.map +1 -1
- package/dist/__tests__/project-generator.test.js +17 -0
- package/dist/__tests__/project-generator.test.js.map +1 -1
- package/dist/__tests__/reverse-facts.test.js +1 -0
- package/dist/__tests__/reverse-facts.test.js.map +1 -1
- package/dist/__tests__/terminal-parity-regression.test.d.ts +2 -0
- package/dist/__tests__/terminal-parity-regression.test.d.ts.map +1 -0
- package/dist/__tests__/terminal-parity-regression.test.js +116 -0
- package/dist/__tests__/terminal-parity-regression.test.js.map +1 -0
- package/dist/__tests__/terminal-parity.test.d.ts +2 -0
- package/dist/__tests__/terminal-parity.test.d.ts.map +1 -0
- package/dist/__tests__/terminal-parity.test.js +81 -0
- package/dist/__tests__/terminal-parity.test.js.map +1 -0
- package/dist/agent/__tests__/antigravity-driver.test.d.ts +2 -0
- package/dist/agent/__tests__/antigravity-driver.test.d.ts.map +1 -0
- package/dist/agent/__tests__/antigravity-driver.test.js +52 -0
- package/dist/agent/__tests__/antigravity-driver.test.js.map +1 -0
- package/dist/agent/__tests__/codex-driver.test.d.ts +2 -0
- package/dist/agent/__tests__/codex-driver.test.d.ts.map +1 -0
- package/dist/agent/__tests__/codex-driver.test.js +68 -0
- package/dist/agent/__tests__/codex-driver.test.js.map +1 -0
- package/dist/agent/__tests__/cursor-driver.test.d.ts +2 -0
- package/dist/agent/__tests__/cursor-driver.test.d.ts.map +1 -0
- package/dist/agent/__tests__/cursor-driver.test.js +52 -0
- package/dist/agent/__tests__/cursor-driver.test.js.map +1 -0
- package/dist/agent/driver.d.ts +1 -1
- package/dist/agent/driver.d.ts.map +1 -1
- package/dist/agent/drivers/antigravity.d.ts +8 -0
- package/dist/agent/drivers/antigravity.d.ts.map +1 -0
- package/dist/agent/drivers/antigravity.js +99 -0
- package/dist/agent/drivers/antigravity.js.map +1 -0
- package/dist/agent/drivers/codex.d.ts +12 -0
- package/dist/agent/drivers/codex.d.ts.map +1 -0
- package/dist/agent/drivers/codex.js +137 -0
- package/dist/agent/drivers/codex.js.map +1 -0
- package/dist/agent/drivers/cursor.d.ts +8 -0
- package/dist/agent/drivers/cursor.d.ts.map +1 -0
- package/dist/agent/drivers/cursor.js +99 -0
- package/dist/agent/drivers/cursor.js.map +1 -0
- package/dist/ai/__tests__/ai-core.test.d.ts +2 -0
- package/dist/ai/__tests__/ai-core.test.d.ts.map +1 -0
- package/dist/ai/__tests__/ai-core.test.js +41 -0
- package/dist/ai/__tests__/ai-core.test.js.map +1 -0
- package/dist/ai/__tests__/parity.test.d.ts +2 -0
- package/dist/ai/__tests__/parity.test.d.ts.map +1 -0
- package/dist/ai/__tests__/parity.test.js +36 -0
- package/dist/ai/__tests__/parity.test.js.map +1 -0
- package/dist/ai/__tests__/pipeline.test.d.ts +2 -0
- package/dist/ai/__tests__/pipeline.test.d.ts.map +1 -0
- package/dist/ai/__tests__/pipeline.test.js +147 -0
- package/dist/ai/__tests__/pipeline.test.js.map +1 -0
- package/dist/ai/__tests__/refine-bridge.test.d.ts +2 -0
- package/dist/ai/__tests__/refine-bridge.test.d.ts.map +1 -0
- package/dist/ai/__tests__/refine-bridge.test.js +17 -0
- package/dist/ai/__tests__/refine-bridge.test.js.map +1 -0
- package/dist/ai/__tests__/resolve.test.d.ts +2 -0
- package/dist/ai/__tests__/resolve.test.d.ts.map +1 -0
- package/dist/ai/__tests__/resolve.test.js +42 -0
- package/dist/ai/__tests__/resolve.test.js.map +1 -0
- package/dist/ai/capabilities.d.ts +3 -0
- package/dist/ai/capabilities.d.ts.map +1 -0
- package/dist/ai/capabilities.js +11 -0
- package/dist/ai/capabilities.js.map +1 -0
- package/dist/ai/command-options.d.ts +10 -0
- package/dist/ai/command-options.d.ts.map +1 -0
- package/dist/ai/command-options.js +15 -0
- package/dist/ai/command-options.js.map +1 -0
- package/dist/ai/config.d.ts +27 -0
- package/dist/ai/config.d.ts.map +1 -0
- package/dist/ai/config.js +89 -0
- package/dist/ai/config.js.map +1 -0
- package/dist/ai/parity.d.ts +13 -0
- package/dist/ai/parity.d.ts.map +1 -0
- package/dist/ai/parity.js +87 -0
- package/dist/ai/parity.js.map +1 -0
- package/dist/ai/parse-json-output.d.ts +5 -0
- package/dist/ai/parse-json-output.d.ts.map +1 -0
- package/dist/ai/parse-json-output.js +25 -0
- package/dist/ai/parse-json-output.js.map +1 -0
- package/dist/ai/pipeline.d.ts +20 -0
- package/dist/ai/pipeline.d.ts.map +1 -0
- package/dist/ai/pipeline.js +303 -0
- package/dist/ai/pipeline.js.map +1 -0
- package/dist/ai/prompts.d.ts +6 -0
- package/dist/ai/prompts.d.ts.map +1 -0
- package/dist/ai/prompts.js +49 -0
- package/dist/ai/prompts.js.map +1 -0
- package/dist/ai/providers.d.ts +63 -0
- package/dist/ai/providers.d.ts.map +1 -0
- package/dist/ai/providers.js +297 -0
- package/dist/ai/providers.js.map +1 -0
- package/dist/ai/refine-bridge.d.ts +5 -0
- package/dist/ai/refine-bridge.d.ts.map +1 -0
- package/dist/ai/refine-bridge.js +14 -0
- package/dist/ai/refine-bridge.js.map +1 -0
- package/dist/ai/registry.d.ts +12 -0
- package/dist/ai/registry.d.ts.map +1 -0
- package/dist/ai/registry.js +43 -0
- package/dist/ai/registry.js.map +1 -0
- package/dist/ai/resolve.d.ts +28 -0
- package/dist/ai/resolve.d.ts.map +1 -0
- package/dist/ai/resolve.js +83 -0
- package/dist/ai/resolve.js.map +1 -0
- package/dist/ai/schemas.d.ts +175 -0
- package/dist/ai/schemas.d.ts.map +1 -0
- package/dist/ai/schemas.js +199 -0
- package/dist/ai/schemas.js.map +1 -0
- package/dist/ai/types.d.ts +52 -0
- package/dist/ai/types.d.ts.map +1 -0
- package/dist/ai/types.js +8 -0
- package/dist/ai/types.js.map +1 -0
- package/dist/bin/dare.js +4 -0
- package/dist/bin/dare.js.map +1 -1
- package/dist/commands/__tests__/ai-command.test.d.ts +2 -0
- package/dist/commands/__tests__/ai-command.test.d.ts.map +1 -0
- package/dist/commands/__tests__/ai-command.test.js +68 -0
- package/dist/commands/__tests__/ai-command.test.js.map +1 -0
- package/dist/commands/__tests__/dashboard-command.test.d.ts +2 -0
- package/dist/commands/__tests__/dashboard-command.test.d.ts.map +1 -0
- package/dist/commands/__tests__/dashboard-command.test.js +62 -0
- package/dist/commands/__tests__/dashboard-command.test.js.map +1 -0
- package/dist/commands/__tests__/execute-agent.test.js +82 -0
- package/dist/commands/__tests__/execute-agent.test.js.map +1 -1
- package/dist/commands/__tests__/gate-flags.test.d.ts +2 -0
- package/dist/commands/__tests__/gate-flags.test.d.ts.map +1 -0
- package/dist/commands/__tests__/gate-flags.test.js +58 -0
- package/dist/commands/__tests__/gate-flags.test.js.map +1 -0
- package/dist/commands/__tests__/persist-viz.test.d.ts +2 -0
- package/dist/commands/__tests__/persist-viz.test.d.ts.map +1 -0
- package/dist/commands/__tests__/persist-viz.test.js +178 -0
- package/dist/commands/__tests__/persist-viz.test.js.map +1 -0
- package/dist/commands/__tests__/refine-apply.test.d.ts +2 -0
- package/dist/commands/__tests__/refine-apply.test.d.ts.map +1 -0
- package/dist/commands/__tests__/refine-apply.test.js +118 -0
- package/dist/commands/__tests__/refine-apply.test.js.map +1 -0
- package/dist/commands/__tests__/replan-splice.test.d.ts +2 -0
- package/dist/commands/__tests__/replan-splice.test.d.ts.map +1 -0
- package/dist/commands/__tests__/replan-splice.test.js +295 -0
- package/dist/commands/__tests__/replan-splice.test.js.map +1 -0
- package/dist/commands/ai.d.ts +3 -0
- package/dist/commands/ai.d.ts.map +1 -0
- package/dist/commands/ai.js +141 -0
- package/dist/commands/ai.js.map +1 -0
- package/dist/commands/blueprint.d.ts.map +1 -1
- package/dist/commands/blueprint.js +17 -3
- package/dist/commands/blueprint.js.map +1 -1
- package/dist/commands/dag.d.ts.map +1 -1
- package/dist/commands/dag.js +2 -0
- package/dist/commands/dag.js.map +1 -1
- package/dist/commands/dashboard.d.ts +17 -0
- package/dist/commands/dashboard.d.ts.map +1 -0
- package/dist/commands/dashboard.js +77 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/commands/design.d.ts.map +1 -1
- package/dist/commands/design.js +21 -2
- package/dist/commands/design.js.map +1 -1
- package/dist/commands/discover.d.ts.map +1 -1
- package/dist/commands/discover.js +9 -1
- package/dist/commands/discover.js.map +1 -1
- package/dist/commands/dna.d.ts.map +1 -1
- package/dist/commands/dna.js +23 -3
- package/dist/commands/dna.js.map +1 -1
- package/dist/commands/execute.d.ts +11 -0
- package/dist/commands/execute.d.ts.map +1 -1
- package/dist/commands/execute.js +219 -6
- package/dist/commands/execute.js.map +1 -1
- package/dist/commands/graph.d.ts.map +1 -1
- package/dist/commands/graph.js +33 -5
- package/dist/commands/graph.js.map +1 -1
- package/dist/commands/guard.d.ts.map +1 -1
- package/dist/commands/guard.js +27 -3
- package/dist/commands/guard.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +1 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +14 -2
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/patterns.d.ts.map +1 -1
- package/dist/commands/patterns.js +14 -2
- package/dist/commands/patterns.js.map +1 -1
- package/dist/commands/refine.d.ts +2 -0
- package/dist/commands/refine.d.ts.map +1 -1
- package/dist/commands/refine.js +94 -22
- package/dist/commands/refine.js.map +1 -1
- package/dist/commands/reverse.d.ts.map +1 -1
- package/dist/commands/reverse.js +28 -3
- package/dist/commands/reverse.js.map +1 -1
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +60 -7
- package/dist/commands/review.js.map +1 -1
- package/dist/core/types/project.d.ts +1 -1
- package/dist/core/types/project.d.ts.map +1 -1
- package/dist/dag-runner/__tests__/maxdepth-config.test.d.ts +2 -0
- package/dist/dag-runner/__tests__/maxdepth-config.test.d.ts.map +1 -0
- package/dist/dag-runner/__tests__/maxdepth-config.test.js +28 -0
- package/dist/dag-runner/__tests__/maxdepth-config.test.js.map +1 -0
- package/dist/dag-runner/__tests__/sub-dag.test.d.ts +2 -0
- package/dist/dag-runner/__tests__/sub-dag.test.d.ts.map +1 -0
- package/dist/dag-runner/__tests__/sub-dag.test.js +127 -0
- package/dist/dag-runner/__tests__/sub-dag.test.js.map +1 -0
- package/dist/dag-runner/run_dag.d.ts +3 -1
- package/dist/dag-runner/run_dag.d.ts.map +1 -1
- package/dist/dag-runner/run_dag.js.map +1 -1
- package/dist/dag-runner/state-store.d.ts +2 -0
- package/dist/dag-runner/state-store.d.ts.map +1 -1
- package/dist/dag-runner/state-store.js +44 -8
- package/dist/dag-runner/state-store.js.map +1 -1
- package/dist/dag-runner/sub-dag.d.ts +23 -0
- package/dist/dag-runner/sub-dag.d.ts.map +1 -0
- package/dist/dag-runner/sub-dag.js +117 -0
- package/dist/dag-runner/sub-dag.js.map +1 -0
- package/dist/dashboard/__tests__/dashboard-routes.test.d.ts +2 -0
- package/dist/dashboard/__tests__/dashboard-routes.test.d.ts.map +1 -0
- package/dist/dashboard/__tests__/dashboard-routes.test.js +102 -0
- package/dist/dashboard/__tests__/dashboard-routes.test.js.map +1 -0
- package/dist/dashboard/routes.d.ts +8 -0
- package/dist/dashboard/routes.d.ts.map +1 -0
- package/dist/dashboard/routes.js +70 -0
- package/dist/dashboard/routes.js.map +1 -0
- package/dist/exec/safe-spawn.d.ts.map +1 -1
- package/dist/exec/safe-spawn.js +6 -1
- package/dist/exec/safe-spawn.js.map +1 -1
- package/dist/graphrag/__tests__/hybrid-integration.test.js +1 -1
- package/dist/graphrag/__tests__/hybrid-integration.test.js.map +1 -1
- package/dist/graphrag/__tests__/no-heavy-dep-in-core.test.js +2 -2
- package/dist/graphrag/__tests__/no-heavy-dep-in-core.test.js.map +1 -1
- package/dist/graphrag/embeddings.js +1 -1
- package/dist/graphrag/embeddings.js.map +1 -1
- package/dist/http/__tests__/shared-app.test.d.ts +2 -0
- package/dist/http/__tests__/shared-app.test.d.ts.map +1 -0
- package/dist/http/__tests__/shared-app.test.js +57 -0
- package/dist/http/__tests__/shared-app.test.js.map +1 -0
- package/dist/http/app.d.ts +15 -0
- package/dist/http/app.d.ts.map +1 -0
- package/dist/http/app.js +32 -0
- package/dist/http/app.js.map +1 -0
- package/dist/mcp-server/server.d.ts.map +1 -1
- package/dist/mcp-server/server.js +5 -15
- package/dist/mcp-server/server.js.map +1 -1
- package/dist/reporters/__tests__/github-reporter.test.d.ts +2 -0
- package/dist/reporters/__tests__/github-reporter.test.d.ts.map +1 -0
- package/dist/reporters/__tests__/github-reporter.test.js +93 -0
- package/dist/reporters/__tests__/github-reporter.test.js.map +1 -0
- package/dist/reporters/ci-gate.d.ts +27 -0
- package/dist/reporters/ci-gate.d.ts.map +1 -0
- package/dist/reporters/ci-gate.js +100 -0
- package/dist/reporters/ci-gate.js.map +1 -0
- package/dist/reporters/github.d.ts +36 -0
- package/dist/reporters/github.d.ts.map +1 -0
- package/dist/reporters/github.js +142 -0
- package/dist/reporters/github.js.map +1 -0
- package/dist/skills/bundled.d.ts +5 -0
- package/dist/skills/bundled.d.ts.map +1 -0
- package/dist/skills/bundled.js +34 -0
- package/dist/skills/bundled.js.map +1 -0
- package/dist/skills/commands/add.d.ts +1 -3
- package/dist/skills/commands/add.d.ts.map +1 -1
- package/dist/skills/commands/add.js +20 -3
- package/dist/skills/commands/add.js.map +1 -1
- package/dist/skills/tests/bundled.spec.d.ts +2 -0
- package/dist/skills/tests/bundled.spec.d.ts.map +1 -0
- package/dist/skills/tests/bundled.spec.js +24 -0
- package/dist/skills/tests/bundled.spec.js.map +1 -0
- package/dist/telemetry/__tests__/aggregator.test.d.ts +2 -0
- package/dist/telemetry/__tests__/aggregator.test.d.ts.map +1 -0
- package/dist/telemetry/__tests__/aggregator.test.js +164 -0
- package/dist/telemetry/__tests__/aggregator.test.js.map +1 -0
- package/dist/telemetry/aggregator.d.ts +43 -0
- package/dist/telemetry/aggregator.d.ts.map +1 -0
- package/dist/telemetry/aggregator.js +282 -0
- package/dist/telemetry/aggregator.js.map +1 -0
- package/dist/types/UpdateManifest.types.d.ts +1 -1
- package/dist/types/UpdateManifest.types.d.ts.map +1 -1
- package/dist/utils/dag-converter.js +1 -1
- package/dist/utils/dag-converter.js.map +1 -1
- package/dist/utils/excalidraw-renderer.d.ts.map +1 -1
- package/dist/utils/excalidraw-renderer.js +1 -0
- package/dist/utils/excalidraw-renderer.js.map +1 -1
- package/dist/utils/graph-renderer.d.ts +2 -0
- package/dist/utils/graph-renderer.d.ts.map +1 -1
- package/dist/utils/graph-renderer.js +188 -14
- package/dist/utils/graph-renderer.js.map +1 -1
- package/dist/utils/project-detector.d.ts +1 -0
- package/dist/utils/project-detector.d.ts.map +1 -1
- package/dist/utils/project-detector.js +8 -0
- package/dist/utils/project-detector.js.map +1 -1
- package/dist/utils/project-generator.d.ts +1 -1
- package/dist/utils/project-generator.d.ts.map +1 -1
- package/dist/utils/project-generator.js +23 -2
- package/dist/utils/project-generator.js.map +1 -1
- package/dist/utils/templates.d.ts +2 -0
- package/dist/utils/templates.d.ts.map +1 -1
- package/dist/utils/templates.js +74 -0
- package/dist/utils/templates.js.map +1 -1
- package/dist/verification/__tests__/decay-policy.test.js +1 -0
- package/dist/verification/__tests__/decay-policy.test.js.map +1 -1
- package/dist/verification/__tests__/safe-spawn.test.js +12 -0
- package/dist/verification/__tests__/safe-spawn.test.js.map +1 -1
- package/dist/verification/config.d.ts.map +1 -1
- package/dist/verification/config.js +2 -0
- package/dist/verification/config.js.map +1 -1
- package/dist/verification/types.d.ts +2 -0
- package/dist/verification/types.d.ts.map +1 -1
- package/package.json +3 -2
- package/skills/dare-ax/generator.ts +325 -0
- package/skills/dare-ax/index.ts +19 -0
- package/skills/dare-ax/metrics.ts +352 -0
- package/skills/dare-ax/package-lock.json +1855 -0
- package/skills/dare-ax/package.json +50 -0
- package/skills/dare-ax/secret-detector.ts +123 -0
- package/skills/dare-ax/skill.yml +19 -0
- package/skills/dare-ax/templates/llms.txt.jinja2 +80 -0
- package/skills/dare-ax/tests/generator.spec.ts +193 -0
- package/skills/dare-ax/tests/metrics.spec.ts +394 -0
- package/skills/dare-ax/tests/validator.spec.ts +298 -0
- package/skills/dare-ax/tsconfig.json +18 -0
- package/skills/dare-ax/types.ts +79 -0
- package/skills/dare-ax/validator.ts +238 -0
- package/skills/dare-frontend-design/generator.ts +616 -0
- package/skills/dare-frontend-design/index.ts +25 -0
- package/skills/dare-frontend-design/linter.ts +227 -0
- package/skills/dare-frontend-design/metrics.ts +82 -0
- package/skills/dare-frontend-design/package-lock.json +1855 -0
- package/skills/dare-frontend-design/package.json +43 -0
- package/skills/dare-frontend-design/skill.yml +20 -0
- package/skills/dare-frontend-design/tests/frontend_design.spec.ts +435 -0
- package/skills/dare-frontend-design/tsconfig.json +18 -0
- package/skills/dare-frontend-design/types.ts +62 -0
- package/skills/dare-layered-design/generator.ts +740 -0
- package/skills/dare-layered-design/index.ts +17 -0
- package/skills/dare-layered-design/linter.ts +462 -0
- package/skills/dare-layered-design/metrics.ts +409 -0
- package/skills/dare-layered-design/package-lock.json +1855 -0
- package/skills/dare-layered-design/package.json +50 -0
- package/skills/dare-layered-design/skill.yml +35 -0
- package/skills/dare-layered-design/tests/generator.spec.ts +156 -0
- package/skills/dare-layered-design/tests/linter.spec.ts +255 -0
- package/skills/dare-layered-design/tests/metrics.spec.ts +286 -0
- package/skills/dare-layered-design/tsconfig.json +18 -0
- package/skills/dare-layered-design/types.ts +48 -0
- package/skills/dare-llm-integration/cache/llm_cache.ts +122 -0
- package/skills/dare-llm-integration/index.ts +49 -0
- package/skills/dare-llm-integration/metrics.ts +107 -0
- package/skills/dare-llm-integration/package-lock.json +1855 -0
- package/skills/dare-llm-integration/package.json +49 -0
- package/skills/dare-llm-integration/prompts/prompt_loader.ts +258 -0
- package/skills/dare-llm-integration/providers/anthropic_provider.ts +159 -0
- package/skills/dare-llm-integration/providers/dummy_provider.ts +113 -0
- package/skills/dare-llm-integration/providers/llm_provider.ts +6 -0
- package/skills/dare-llm-integration/providers/openai_provider.ts +215 -0
- package/skills/dare-llm-integration/rate_limit/token_bucket.ts +86 -0
- package/skills/dare-llm-integration/skill.yml +23 -0
- package/skills/dare-llm-integration/tests/fixtures/greet_v1.jinja2 +1 -0
- package/skills/dare-llm-integration/tests/fixtures/summarize_v1.jinja2 +1 -0
- package/skills/dare-llm-integration/tests/fixtures/summarize_v2.jinja2 +3 -0
- package/skills/dare-llm-integration/tests/llm_integration.spec.ts +657 -0
- package/skills/dare-llm-integration/tsconfig.json +23 -0
- package/skills/dare-llm-integration/types.ts +91 -0
- package/skills/dare-llm-integration/validators/output_validator.ts +200 -0
- package/skills/dare-quality-telemetry/collect.ts +134 -0
- package/skills/dare-quality-telemetry/collectors/dare_ax_collector.ts +301 -0
- package/skills/dare-quality-telemetry/collectors/dare_layered_design_collector.ts +406 -0
- package/skills/dare-quality-telemetry/collectors/index.ts +24 -0
- package/skills/dare-quality-telemetry/github_actions_template.ts +25 -0
- package/skills/dare-quality-telemetry/index.ts +18 -0
- package/skills/dare-quality-telemetry/metrics.ts +137 -0
- package/skills/dare-quality-telemetry/package-lock.json +1855 -0
- package/skills/dare-quality-telemetry/package.json +48 -0
- package/skills/dare-quality-telemetry/regression.ts +60 -0
- package/skills/dare-quality-telemetry/reporter.ts +132 -0
- package/skills/dare-quality-telemetry/skill.yml +18 -0
- package/skills/dare-quality-telemetry/tests/quality_telemetry.spec.ts +885 -0
- package/skills/dare-quality-telemetry/tsconfig.json +19 -0
- package/skills/dare-quality-telemetry/types.ts +41 -0
- package/skills/dare-realtime/event_registry.ts +101 -0
- package/skills/dare-realtime/index.ts +30 -0
- package/skills/dare-realtime/metrics.ts +84 -0
- package/skills/dare-realtime/package-lock.json +1855 -0
- package/skills/dare-realtime/package.json +43 -0
- package/skills/dare-realtime/reconnect_strategy.ts +85 -0
- package/skills/dare-realtime/schema_validator.ts +80 -0
- package/skills/dare-realtime/skill.yml +21 -0
- package/skills/dare-realtime/subscription_manager.ts +106 -0
- package/skills/dare-realtime/tests/realtime.spec.ts +482 -0
- package/skills/dare-realtime/tsconfig.json +18 -0
- package/skills/dare-realtime/types.ts +51 -0
- package/templates/.github/workflows/dare-pr.yml +26 -0
- package/templates/dashboard/app.js +184 -0
- package/templates/dashboard/index.html +63 -0
- package/templates/dashboard/style.css +134 -0
- package/templates/ide/antigravity/.agents/skills/dare-ai/SKILL.md +17 -0
- package/templates/ide/antigravity/.agents/skills/dare-blueprint/SKILL.md +2 -0
- package/templates/ide/antigravity/.agents/skills/dare-dashboard/SKILL.md +18 -0
- package/templates/ide/antigravity/.agents/skills/dare-design/SKILL.md +2 -0
- package/templates/ide/antigravity/.agents/skills/dare-dna/SKILL.md +3 -0
- package/templates/ide/antigravity/.agents/skills/dare-migrate/SKILL.md +3 -0
- package/templates/ide/antigravity/.agents/skills/dare-patterns/SKILL.md +3 -0
- package/templates/ide/antigravity/.agents/skills/dare-refine/SKILL.md +3 -0
- package/templates/ide/antigravity/.agents/skills/dare-reverse/SKILL.md +3 -0
- package/templates/ide/antigravity/.agents/skills/dare-review/SKILL.md +3 -0
- package/templates/ide/claude/.claude/commands/dare-ai.md +17 -0
- package/templates/ide/claude/.claude/commands/dare-blueprint.md +2 -0
- package/templates/ide/claude/.claude/commands/dare-dashboard.md +18 -0
- package/templates/ide/claude/.claude/commands/dare-design.md +2 -0
- package/templates/ide/claude/.claude/commands/dare-dna.md +2 -0
- package/templates/ide/claude/.claude/commands/dare-migrate.md +2 -0
- package/templates/ide/claude/.claude/commands/dare-patterns.md +3 -0
- package/templates/ide/claude/.claude/commands/dare-refine.md +3 -0
- package/templates/ide/claude/.claude/commands/dare-reverse.md +2 -0
- package/templates/ide/claude/.claude/commands/dare-review.md +3 -0
- package/templates/ide/cursor/.cursor/commands/dare-ai.md +17 -0
- package/templates/ide/cursor/.cursor/commands/dare-blueprint.md +3 -0
- package/templates/ide/cursor/.cursor/commands/dare-dashboard.md +18 -0
- package/templates/ide/cursor/.cursor/commands/dare-design.md +3 -0
- package/templates/ide/cursor/.cursor/commands/dare-dna.md +2 -0
- package/templates/ide/cursor/.cursor/commands/dare-migrate.md +2 -0
- package/templates/ide/cursor/.cursor/commands/dare-patterns.md +3 -0
- package/templates/ide/cursor/.cursor/commands/dare-refine.md +3 -0
- package/templates/ide/cursor/.cursor/commands/dare-reverse.md +2 -0
- package/templates/ide/cursor/.cursor/commands/dare-review.md +3 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dare-ax — DareAxGenerator
|
|
3
|
+
* Generates llms.txt at project root using the Jinja2-style template.
|
|
4
|
+
* License: MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { ProjectConfig } from './types.js';
|
|
11
|
+
import { containsSecrets } from './secret-detector.js';
|
|
12
|
+
|
|
13
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const DEFAULT_TEMPLATE_PATH = path.join(__dirname, 'templates', 'llms.txt.jinja2');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Renders a Jinja2-style template with simple variable substitution.
|
|
18
|
+
* Supports: {{ variable }}, {% for x in list %}...{% endfor %},
|
|
19
|
+
* {% if condition %}...{% endif %}, {% if cond %}...{% else %}...{% endif %}
|
|
20
|
+
*
|
|
21
|
+
* Uses a token-based approach to avoid regex nesting issues.
|
|
22
|
+
*/
|
|
23
|
+
function renderTemplate(templateContent: string, context: Record<string, unknown>): string {
|
|
24
|
+
return renderWithContext(templateContent, context);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Tokenizes the template into literal strings and tag tokens, then evaluates them.
|
|
29
|
+
* This avoids the regex-ordering / nesting problems of sequential replacements.
|
|
30
|
+
*/
|
|
31
|
+
function renderWithContext(template: string, context: Record<string, unknown>): string {
|
|
32
|
+
const tokens = tokenize(template);
|
|
33
|
+
const result = evaluate(tokens, context, 0);
|
|
34
|
+
return result.output;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type Token =
|
|
38
|
+
| { type: 'text'; value: string }
|
|
39
|
+
| { type: 'var'; name: string }
|
|
40
|
+
| { type: 'for'; varName: string; listName: string }
|
|
41
|
+
| { type: 'endfor' }
|
|
42
|
+
| { type: 'if'; condName: string }
|
|
43
|
+
| { type: 'else' }
|
|
44
|
+
| { type: 'endif' };
|
|
45
|
+
|
|
46
|
+
/** Splits the template into text and tag tokens */
|
|
47
|
+
function tokenize(template: string): Token[] {
|
|
48
|
+
const tokens: Token[] = [];
|
|
49
|
+
let pos = 0;
|
|
50
|
+
|
|
51
|
+
while (pos < template.length) {
|
|
52
|
+
// Find next tag {{ or {%
|
|
53
|
+
const varStart = template.indexOf('{{', pos);
|
|
54
|
+
const tagStart = template.indexOf('{%', pos);
|
|
55
|
+
|
|
56
|
+
let next: number;
|
|
57
|
+
let isVar: boolean;
|
|
58
|
+
|
|
59
|
+
if (varStart === -1 && tagStart === -1) {
|
|
60
|
+
// No more tags — rest is text
|
|
61
|
+
tokens.push({ type: 'text', value: template.slice(pos) });
|
|
62
|
+
break;
|
|
63
|
+
} else if (varStart === -1) {
|
|
64
|
+
next = tagStart;
|
|
65
|
+
isVar = false;
|
|
66
|
+
} else if (tagStart === -1) {
|
|
67
|
+
next = varStart;
|
|
68
|
+
isVar = true;
|
|
69
|
+
} else {
|
|
70
|
+
next = Math.min(varStart, tagStart);
|
|
71
|
+
isVar = varStart < tagStart;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Emit text before the tag
|
|
75
|
+
if (next > pos) {
|
|
76
|
+
tokens.push({ type: 'text', value: template.slice(pos, next) });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (isVar) {
|
|
80
|
+
// {{ varname }}
|
|
81
|
+
const end = template.indexOf('}}', next + 2);
|
|
82
|
+
if (end === -1) {
|
|
83
|
+
tokens.push({ type: 'text', value: template.slice(next) });
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
const name = template.slice(next + 2, end).trim();
|
|
87
|
+
tokens.push({ type: 'var', name });
|
|
88
|
+
pos = end + 2;
|
|
89
|
+
} else {
|
|
90
|
+
// {% tag ... %}
|
|
91
|
+
const end = template.indexOf('%}', next + 2);
|
|
92
|
+
if (end === -1) {
|
|
93
|
+
tokens.push({ type: 'text', value: template.slice(next) });
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
const inner = template.slice(next + 2, end).trim();
|
|
97
|
+
pos = end + 2;
|
|
98
|
+
|
|
99
|
+
if (inner.startsWith('for ')) {
|
|
100
|
+
const m = /^for\s+(\w+)\s+in\s+([\w.]+)$/.exec(inner);
|
|
101
|
+
if (m) {
|
|
102
|
+
tokens.push({ type: 'for', varName: m[1], listName: m[2] });
|
|
103
|
+
}
|
|
104
|
+
} else if (inner === 'endfor') {
|
|
105
|
+
tokens.push({ type: 'endfor' });
|
|
106
|
+
} else if (inner.startsWith('if ')) {
|
|
107
|
+
const condName = inner.slice(3).trim();
|
|
108
|
+
tokens.push({ type: 'if', condName });
|
|
109
|
+
} else if (inner === 'else') {
|
|
110
|
+
tokens.push({ type: 'else' });
|
|
111
|
+
} else if (inner === 'endif') {
|
|
112
|
+
tokens.push({ type: 'endif' });
|
|
113
|
+
}
|
|
114
|
+
// Unknown tags are ignored
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return tokens;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Evaluates a token list starting at `startIdx`. Returns the rendered output and next index. */
|
|
122
|
+
function evaluate(
|
|
123
|
+
tokens: Token[],
|
|
124
|
+
context: Record<string, unknown>,
|
|
125
|
+
startIdx: number
|
|
126
|
+
): { output: string; nextIdx: number } {
|
|
127
|
+
let output = '';
|
|
128
|
+
let i = startIdx;
|
|
129
|
+
|
|
130
|
+
while (i < tokens.length) {
|
|
131
|
+
const token = tokens[i];
|
|
132
|
+
|
|
133
|
+
if (token.type === 'text') {
|
|
134
|
+
output += token.value;
|
|
135
|
+
i++;
|
|
136
|
+
} else if (token.type === 'var') {
|
|
137
|
+
// {{ varname }} — resolve from context
|
|
138
|
+
const direct = context[token.name];
|
|
139
|
+
if (direct !== undefined && direct !== null) {
|
|
140
|
+
output += String(direct);
|
|
141
|
+
} else {
|
|
142
|
+
const val = getNestedValue(context, token.name);
|
|
143
|
+
if (val !== undefined && val !== null) output += String(val);
|
|
144
|
+
}
|
|
145
|
+
i++;
|
|
146
|
+
} else if (token.type === 'for') {
|
|
147
|
+
// Collect the for-body tokens until matching endfor
|
|
148
|
+
const bodyStart = i + 1;
|
|
149
|
+
let depth = 1;
|
|
150
|
+
let j = bodyStart;
|
|
151
|
+
while (j < tokens.length && depth > 0) {
|
|
152
|
+
if (tokens[j].type === 'for') depth++;
|
|
153
|
+
if (tokens[j].type === 'endfor') depth--;
|
|
154
|
+
if (depth > 0) j++;
|
|
155
|
+
else break;
|
|
156
|
+
}
|
|
157
|
+
const bodyTokens = tokens.slice(bodyStart, j);
|
|
158
|
+
|
|
159
|
+
const list = getNestedValue(context, token.listName);
|
|
160
|
+
if (Array.isArray(list)) {
|
|
161
|
+
for (const item of list) {
|
|
162
|
+
const itemContext: Record<string, unknown> = { ...context };
|
|
163
|
+
if (typeof item === 'object' && item !== null) {
|
|
164
|
+
itemContext[token.varName] = item;
|
|
165
|
+
for (const [k, v] of Object.entries(item as Record<string, unknown>)) {
|
|
166
|
+
itemContext[`${token.varName}.${k}`] = v;
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
itemContext[token.varName] = item;
|
|
170
|
+
}
|
|
171
|
+
output += evaluate(bodyTokens, itemContext, 0).output;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
i = j + 1; // skip endfor
|
|
175
|
+
} else if (token.type === 'if') {
|
|
176
|
+
// Collect then-body and optional else-body until matching endif
|
|
177
|
+
const condName = token.condName;
|
|
178
|
+
const bodyStart = i + 1;
|
|
179
|
+
let depth = 1;
|
|
180
|
+
let j = bodyStart;
|
|
181
|
+
let elseIdx = -1;
|
|
182
|
+
|
|
183
|
+
while (j < tokens.length && depth > 0) {
|
|
184
|
+
if (tokens[j].type === 'if') depth++;
|
|
185
|
+
if (tokens[j].type === 'endif') depth--;
|
|
186
|
+
if (depth === 1 && tokens[j].type === 'else') elseIdx = j;
|
|
187
|
+
if (depth > 0) j++;
|
|
188
|
+
else break;
|
|
189
|
+
}
|
|
190
|
+
const endifIdx = j;
|
|
191
|
+
|
|
192
|
+
const thenTokens =
|
|
193
|
+
elseIdx >= 0
|
|
194
|
+
? tokens.slice(bodyStart, elseIdx)
|
|
195
|
+
: tokens.slice(bodyStart, endifIdx);
|
|
196
|
+
const elseTokens =
|
|
197
|
+
elseIdx >= 0 ? tokens.slice(elseIdx + 1, endifIdx) : [];
|
|
198
|
+
|
|
199
|
+
const condValue = getNestedValue(context, condName);
|
|
200
|
+
if (isTruthy(condValue)) {
|
|
201
|
+
output += evaluate(thenTokens, context, 0).output;
|
|
202
|
+
} else {
|
|
203
|
+
output += evaluate(elseTokens, context, 0).output;
|
|
204
|
+
}
|
|
205
|
+
i = endifIdx + 1; // skip endif
|
|
206
|
+
} else if (token.type === 'else' || token.type === 'endif' || token.type === 'endfor') {
|
|
207
|
+
// These are consumed by the parent for/if handler above; if we hit them here, stop.
|
|
208
|
+
break;
|
|
209
|
+
} else {
|
|
210
|
+
i++;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return { output, nextIdx: i };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function getNestedValue(obj: Record<string, unknown>, path: string): unknown {
|
|
218
|
+
return path.split('.').reduce((current: unknown, key: string) => {
|
|
219
|
+
if (current !== null && typeof current === 'object') {
|
|
220
|
+
return (current as Record<string, unknown>)[key];
|
|
221
|
+
}
|
|
222
|
+
return undefined;
|
|
223
|
+
}, obj as unknown);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function isTruthy(value: unknown): boolean {
|
|
227
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
228
|
+
if (value === null || value === undefined) return false;
|
|
229
|
+
if (typeof value === 'string') return value.length > 0;
|
|
230
|
+
return Boolean(value);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Converts a ProjectConfig into the template context object.
|
|
235
|
+
*/
|
|
236
|
+
function buildContext(config: ProjectConfig): Record<string, unknown> {
|
|
237
|
+
return {
|
|
238
|
+
project_overview: config.projectOverview,
|
|
239
|
+
language: config.language,
|
|
240
|
+
framework: config.framework,
|
|
241
|
+
database: config.database,
|
|
242
|
+
key_dependencies: config.keyDependencies,
|
|
243
|
+
architecture_description: config.architectureDescription,
|
|
244
|
+
directory_structure: config.directoryStructure ?? defaultDirectoryStructure(config),
|
|
245
|
+
endpoints: config.endpoints ?? [],
|
|
246
|
+
config_file: config.configFile ?? 'config.json',
|
|
247
|
+
has_docker: config.hasDocker ?? false,
|
|
248
|
+
has_makefile: config.hasMakefile ?? false,
|
|
249
|
+
has_taskfile: config.hasTaskfile ?? false,
|
|
250
|
+
getting_started_command: config.gettingStartedCommand ?? 'make dev',
|
|
251
|
+
rate_limits: config.rateLimits ?? [
|
|
252
|
+
{ scope: 'Public endpoints', limit: 100 },
|
|
253
|
+
{ scope: 'Auth endpoints', limit: 10 },
|
|
254
|
+
],
|
|
255
|
+
extra_security_notes: config.extraSecurityNotes ?? [],
|
|
256
|
+
cli_binary: config.cliBinary ?? config.name,
|
|
257
|
+
agent_notes: config.agentNotes ?? [],
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function defaultDirectoryStructure(config: ProjectConfig): string {
|
|
262
|
+
const src = config.language === 'Ruby' ? 'app' : 'src';
|
|
263
|
+
return `${src}/
|
|
264
|
+
├── handlers/ # HTTP handlers / controllers
|
|
265
|
+
├── services/ # Business logic
|
|
266
|
+
├── models/ # Data structures
|
|
267
|
+
├── repositories/ # Database access
|
|
268
|
+
└── utils/ # Utilities`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export class DareAxGenerator {
|
|
272
|
+
private readonly templatePath: string;
|
|
273
|
+
|
|
274
|
+
constructor(templatePath?: string) {
|
|
275
|
+
this.templatePath = templatePath ?? DEFAULT_TEMPLATE_PATH;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Generates llms.txt in the project root.
|
|
280
|
+
* Throws if generated content would contain secrets.
|
|
281
|
+
*
|
|
282
|
+
* @param projectPath - Absolute path to the project root.
|
|
283
|
+
* @param config - Project configuration.
|
|
284
|
+
* @returns Absolute path to the generated file.
|
|
285
|
+
*/
|
|
286
|
+
generateLlmsTxt(projectPath: string, config: ProjectConfig): string {
|
|
287
|
+
if (config.axNotApplicable) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
'ax: not-applicable is set for this project. Skipping llms.txt generation.'
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const templateContent = fs.readFileSync(this.templatePath, 'utf-8');
|
|
294
|
+
const context = buildContext(config);
|
|
295
|
+
const rendered = renderTemplate(templateContent, context);
|
|
296
|
+
|
|
297
|
+
// Security check: no secrets in rendered content
|
|
298
|
+
const secretCheck = containsSecrets(rendered);
|
|
299
|
+
if (secretCheck.found) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`dare-ax: Secret detected in llms.txt content (${secretCheck.pattern}). ` +
|
|
302
|
+
`Remove secrets before generating. llms.txt is a public file.`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const outputPath = path.join(projectPath, 'llms.txt');
|
|
307
|
+
fs.writeFileSync(outputPath, rendered, 'utf-8');
|
|
308
|
+
|
|
309
|
+
return outputPath;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Returns the rendered llms.txt content WITHOUT writing to disk.
|
|
314
|
+
* Useful for previewing or testing.
|
|
315
|
+
*/
|
|
316
|
+
renderLlmsTxt(config: ProjectConfig): string {
|
|
317
|
+
if (config.axNotApplicable) {
|
|
318
|
+
throw new Error('ax: not-applicable is set; rendering skipped.');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const templateContent = fs.readFileSync(this.templatePath, 'utf-8');
|
|
322
|
+
const context = buildContext(config);
|
|
323
|
+
return renderTemplate(templateContent, context);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dare-ax — Agent Experience (AX) skill
|
|
3
|
+
* Codifies best practices for AI-assisted development.
|
|
4
|
+
* License: MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { DareAxGenerator } from './generator.js';
|
|
8
|
+
export { DareAxValidator } from './validator.js';
|
|
9
|
+
export { DareAxMetrics } from './metrics.js';
|
|
10
|
+
export { containsSecrets, findAllSecrets } from './secret-detector.js';
|
|
11
|
+
export type {
|
|
12
|
+
ProjectConfig,
|
|
13
|
+
ValidationResult,
|
|
14
|
+
ValidationError,
|
|
15
|
+
ValidationWarning,
|
|
16
|
+
MetricResult,
|
|
17
|
+
RateLimit,
|
|
18
|
+
Endpoint,
|
|
19
|
+
} from './types.js';
|
|
@@ -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
|
+
}
|