@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,885 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dare-quality-telemetry — comprehensive test suite
|
|
3
|
+
* 50+ tests covering all collectors, orchestration, regression detection, and reporters.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import os from 'os';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
|
|
11
|
+
import { collectDareAx } from '../collectors/dare_ax_collector.js';
|
|
12
|
+
import { collectDareLayeredDesign } from '../collectors/dare_layered_design_collector.js';
|
|
13
|
+
import { collectors } from '../collectors/index.js';
|
|
14
|
+
import { collectMetrics, buildSummary, detectCommit } from '../collect.js';
|
|
15
|
+
import { detectRegressions } from '../regression.js';
|
|
16
|
+
import { formatTable, formatJSON, formatPRComment } from '../reporter.js';
|
|
17
|
+
import { QualityTelemetryMetrics } from '../metrics.js';
|
|
18
|
+
import type {
|
|
19
|
+
ProjectMetricReport,
|
|
20
|
+
SkillMetricReport,
|
|
21
|
+
MetricResult,
|
|
22
|
+
RegressionResult,
|
|
23
|
+
} from '../types.js';
|
|
24
|
+
|
|
25
|
+
// ── Fixtures ─────────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
const VALID_LLMS_TXT = `# llms.txt
|
|
28
|
+
|
|
29
|
+
## Project Overview
|
|
30
|
+
A test project for dare-quality-telemetry.
|
|
31
|
+
|
|
32
|
+
## Tech Stack
|
|
33
|
+
- Language: TypeScript
|
|
34
|
+
- Framework: NestJS
|
|
35
|
+
|
|
36
|
+
## Architecture
|
|
37
|
+
4-layer architecture.
|
|
38
|
+
|
|
39
|
+
## Getting Started
|
|
40
|
+
\`\`\`bash
|
|
41
|
+
make dev
|
|
42
|
+
\`\`\`
|
|
43
|
+
|
|
44
|
+
## For AI Agents
|
|
45
|
+
- OpenAPI: GET /openapi.json
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
function makeSkillReport(
|
|
49
|
+
skillName: string,
|
|
50
|
+
metrics: MetricResult[],
|
|
51
|
+
commit = 'abc1234'
|
|
52
|
+
): SkillMetricReport {
|
|
53
|
+
const passed = metrics.filter((m) => m.pass).length;
|
|
54
|
+
const total = metrics.length;
|
|
55
|
+
return {
|
|
56
|
+
skillName,
|
|
57
|
+
timestamp: '2026-05-26T10:00:00Z',
|
|
58
|
+
commit,
|
|
59
|
+
metrics,
|
|
60
|
+
summary: {
|
|
61
|
+
passed,
|
|
62
|
+
total,
|
|
63
|
+
score: `${passed}/${total}`,
|
|
64
|
+
allPass: passed === total && total > 0,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function makeProjectReport(
|
|
70
|
+
skills: SkillMetricReport[],
|
|
71
|
+
projectPath = '/tmp/test-project'
|
|
72
|
+
): ProjectMetricReport {
|
|
73
|
+
const allMetrics = skills.flatMap((s) => s.metrics);
|
|
74
|
+
const passed = allMetrics.filter((m) => m.pass).length;
|
|
75
|
+
const total = allMetrics.length;
|
|
76
|
+
return {
|
|
77
|
+
timestamp: '2026-05-26T10:00:00Z',
|
|
78
|
+
commit: 'abc1234',
|
|
79
|
+
projectPath,
|
|
80
|
+
skills,
|
|
81
|
+
overall: {
|
|
82
|
+
passed,
|
|
83
|
+
total,
|
|
84
|
+
score: `${passed}/${total}`,
|
|
85
|
+
allPass: passed === total && total > 0,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function makeMetric(id: string, pass: boolean): MetricResult {
|
|
91
|
+
return {
|
|
92
|
+
id,
|
|
93
|
+
pass,
|
|
94
|
+
description: `Test metric ${id}`,
|
|
95
|
+
details: pass ? 'All good' : 'Failed',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── Setup ─────────────────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
let tmpDir: string;
|
|
102
|
+
|
|
103
|
+
beforeEach(() => {
|
|
104
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dare-qt-test-'));
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
afterEach(() => {
|
|
108
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// ── DareAxCollector ───────────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
describe('DareAxCollector', () => {
|
|
114
|
+
describe('M-01: llms.txt valid with 5 sections', () => {
|
|
115
|
+
it('passes when llms.txt exists with all 5 required sections', async () => {
|
|
116
|
+
fs.writeFileSync(path.join(tmpDir, 'llms.txt'), VALID_LLMS_TXT, 'utf-8');
|
|
117
|
+
const results = await collectDareAx(tmpDir);
|
|
118
|
+
const m01 = results.find((r) => r.id === 'M-01')!;
|
|
119
|
+
expect(m01.pass).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('fails when llms.txt does not exist', async () => {
|
|
123
|
+
const results = await collectDareAx(tmpDir);
|
|
124
|
+
const m01 = results.find((r) => r.id === 'M-01')!;
|
|
125
|
+
expect(m01.pass).toBe(false);
|
|
126
|
+
expect(m01.details).toContain('not found');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('fails when llms.txt is missing Project Overview section', async () => {
|
|
130
|
+
const content = VALID_LLMS_TXT.replace('## Project Overview', '## Missing Section');
|
|
131
|
+
fs.writeFileSync(path.join(tmpDir, 'llms.txt'), content, 'utf-8');
|
|
132
|
+
const results = await collectDareAx(tmpDir);
|
|
133
|
+
const m01 = results.find((r) => r.id === 'M-01')!;
|
|
134
|
+
expect(m01.pass).toBe(false);
|
|
135
|
+
expect(m01.details).toContain('Project Overview');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('fails when llms.txt is missing For AI Agents section', async () => {
|
|
139
|
+
const content = VALID_LLMS_TXT.replace('## For AI Agents', '## Something Else');
|
|
140
|
+
fs.writeFileSync(path.join(tmpDir, 'llms.txt'), content, 'utf-8');
|
|
141
|
+
const results = await collectDareAx(tmpDir);
|
|
142
|
+
const m01 = results.find((r) => r.id === 'M-01')!;
|
|
143
|
+
expect(m01.pass).toBe(false);
|
|
144
|
+
expect(m01.details).toContain('For AI Agents');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('fails when llms.txt has only 2 out of 5 sections', async () => {
|
|
148
|
+
const minimal = '## Project Overview\nSome text.\n## Tech Stack\nTS\n';
|
|
149
|
+
fs.writeFileSync(path.join(tmpDir, 'llms.txt'), minimal, 'utf-8');
|
|
150
|
+
const results = await collectDareAx(tmpDir);
|
|
151
|
+
const m01 = results.find((r) => r.id === 'M-01')!;
|
|
152
|
+
expect(m01.pass).toBe(false);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('M-02: openapi.json exists', () => {
|
|
157
|
+
it('passes when openapi.json is at project root', async () => {
|
|
158
|
+
fs.writeFileSync(path.join(tmpDir, 'openapi.json'), '{"openapi":"3.1.0"}', 'utf-8');
|
|
159
|
+
const results = await collectDareAx(tmpDir);
|
|
160
|
+
const m02 = results.find((r) => r.id === 'M-02')!;
|
|
161
|
+
expect(m02.pass).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('passes when openapi.json is in public/', async () => {
|
|
165
|
+
fs.mkdirSync(path.join(tmpDir, 'public'));
|
|
166
|
+
fs.writeFileSync(path.join(tmpDir, 'public', 'openapi.json'), '{"openapi":"3.1.0"}', 'utf-8');
|
|
167
|
+
const results = await collectDareAx(tmpDir);
|
|
168
|
+
const m02 = results.find((r) => r.id === 'M-02')!;
|
|
169
|
+
expect(m02.pass).toBe(true);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('passes when openapi.yaml exists', async () => {
|
|
173
|
+
fs.writeFileSync(path.join(tmpDir, 'openapi.yaml'), 'openapi: 3.1.0', 'utf-8');
|
|
174
|
+
const results = await collectDareAx(tmpDir);
|
|
175
|
+
const m02 = results.find((r) => r.id === 'M-02')!;
|
|
176
|
+
expect(m02.pass).toBe(true);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('fails when no openapi file found', async () => {
|
|
180
|
+
const results = await collectDareAx(tmpDir);
|
|
181
|
+
const m02 = results.find((r) => r.id === 'M-02')!;
|
|
182
|
+
expect(m02.pass).toBe(false);
|
|
183
|
+
expect(m02.details).toContain('No openapi');
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('M-03: CLI --json flag', () => {
|
|
188
|
+
it('passes when --json found in bin/dare.ts', async () => {
|
|
189
|
+
fs.mkdirSync(path.join(tmpDir, 'bin'));
|
|
190
|
+
fs.writeFileSync(
|
|
191
|
+
path.join(tmpDir, 'bin', 'dare.ts'),
|
|
192
|
+
'program.option("--json", "JSON output");\n',
|
|
193
|
+
'utf-8'
|
|
194
|
+
);
|
|
195
|
+
const results = await collectDareAx(tmpDir);
|
|
196
|
+
const m03 = results.find((r) => r.id === 'M-03')!;
|
|
197
|
+
expect(m03.pass).toBe(true);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('passes when --json found in cli.ts at root', async () => {
|
|
201
|
+
fs.writeFileSync(
|
|
202
|
+
path.join(tmpDir, 'cli.ts'),
|
|
203
|
+
'cmd.option("--json", "Output as JSON");\n',
|
|
204
|
+
'utf-8'
|
|
205
|
+
);
|
|
206
|
+
const results = await collectDareAx(tmpDir);
|
|
207
|
+
const m03 = results.find((r) => r.id === 'M-03')!;
|
|
208
|
+
expect(m03.pass).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('passes when --json found nested in src/commands/', async () => {
|
|
212
|
+
fs.mkdirSync(path.join(tmpDir, 'src', 'commands'), { recursive: true });
|
|
213
|
+
fs.writeFileSync(
|
|
214
|
+
path.join(tmpDir, 'src', 'commands', 'init.ts'),
|
|
215
|
+
'.option("--json", "Output results as JSON")\n',
|
|
216
|
+
'utf-8'
|
|
217
|
+
);
|
|
218
|
+
const results = await collectDareAx(tmpDir);
|
|
219
|
+
const m03 = results.find((r) => r.id === 'M-03')!;
|
|
220
|
+
expect(m03.pass).toBe(true);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('fails when no --json flag in any source file', async () => {
|
|
224
|
+
fs.mkdirSync(path.join(tmpDir, 'src'));
|
|
225
|
+
fs.writeFileSync(
|
|
226
|
+
path.join(tmpDir, 'src', 'cli.ts'),
|
|
227
|
+
'program.option("--verbose", "Verbose output");\n',
|
|
228
|
+
'utf-8'
|
|
229
|
+
);
|
|
230
|
+
const results = await collectDareAx(tmpDir);
|
|
231
|
+
const m03 = results.find((r) => r.id === 'M-03')!;
|
|
232
|
+
expect(m03.pass).toBe(false);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('fails when project has no cli source directories', async () => {
|
|
236
|
+
const results = await collectDareAx(tmpDir);
|
|
237
|
+
const m03 = results.find((r) => r.id === 'M-03')!;
|
|
238
|
+
expect(m03.pass).toBe(false);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe('M-04: rate limit configuration', () => {
|
|
243
|
+
it('passes when express-rate-limit in package.json', async () => {
|
|
244
|
+
const pkg = { name: 'test', dependencies: { 'express-rate-limit': '^7.0.0' } };
|
|
245
|
+
fs.writeFileSync(path.join(tmpDir, 'package.json'), JSON.stringify(pkg), 'utf-8');
|
|
246
|
+
const results = await collectDareAx(tmpDir);
|
|
247
|
+
const m04 = results.find((r) => r.id === 'M-04')!;
|
|
248
|
+
expect(m04.pass).toBe(true);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('passes when rack-attack in Gemfile', async () => {
|
|
252
|
+
fs.writeFileSync(path.join(tmpDir, 'Gemfile'), 'gem "rack-attack"\n', 'utf-8');
|
|
253
|
+
const results = await collectDareAx(tmpDir);
|
|
254
|
+
const m04 = results.find((r) => r.id === 'M-04')!;
|
|
255
|
+
expect(m04.pass).toBe(true);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('passes when tower-governor in Cargo.toml', async () => {
|
|
259
|
+
fs.writeFileSync(path.join(tmpDir, 'Cargo.toml'), '[dependencies]\ntower-governor = "0.2"\n', 'utf-8');
|
|
260
|
+
const results = await collectDareAx(tmpDir);
|
|
261
|
+
const m04 = results.find((r) => r.id === 'M-04')!;
|
|
262
|
+
expect(m04.pass).toBe(true);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('passes when throttler found in source file', async () => {
|
|
266
|
+
fs.mkdirSync(path.join(tmpDir, 'src'));
|
|
267
|
+
fs.writeFileSync(
|
|
268
|
+
path.join(tmpDir, 'src', 'app.ts'),
|
|
269
|
+
'import { ThrottlerModule } from "@nestjs/throttler";\n',
|
|
270
|
+
'utf-8'
|
|
271
|
+
);
|
|
272
|
+
const results = await collectDareAx(tmpDir);
|
|
273
|
+
const m04 = results.find((r) => r.id === 'M-04')!;
|
|
274
|
+
expect(m04.pass).toBe(true);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('fails when no rate limit found', async () => {
|
|
278
|
+
const pkg = { name: 'test', dependencies: { express: '^4.0.0' } };
|
|
279
|
+
fs.writeFileSync(path.join(tmpDir, 'package.json'), JSON.stringify(pkg), 'utf-8');
|
|
280
|
+
const results = await collectDareAx(tmpDir);
|
|
281
|
+
const m04 = results.find((r) => r.id === 'M-04')!;
|
|
282
|
+
expect(m04.pass).toBe(false);
|
|
283
|
+
expect(m04.details).toContain('No rate limit');
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('returns 4 metrics total', async () => {
|
|
287
|
+
const results = await collectDareAx(tmpDir);
|
|
288
|
+
expect(results).toHaveLength(4);
|
|
289
|
+
expect(results.map((r) => r.id)).toEqual(['M-01', 'M-02', 'M-03', 'M-04']);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// ── DareLayeredDesignCollector ────────────────────────────────────────────────
|
|
295
|
+
|
|
296
|
+
describe('DareLayeredDesignCollector', () => {
|
|
297
|
+
describe('M-01: service tests exist', () => {
|
|
298
|
+
it('passes when services/ has both service and test files', async () => {
|
|
299
|
+
const servicesDir = path.join(tmpDir, 'src', 'services');
|
|
300
|
+
fs.mkdirSync(servicesDir, { recursive: true });
|
|
301
|
+
fs.writeFileSync(path.join(servicesDir, 'user.service.ts'), 'export class UserService {}', 'utf-8');
|
|
302
|
+
fs.writeFileSync(path.join(servicesDir, 'user.service.spec.ts'), 'describe("UserService", () => {})', 'utf-8');
|
|
303
|
+
const results = await collectDareLayeredDesign(tmpDir);
|
|
304
|
+
const m01 = results.find((r) => r.id === 'M-01')!;
|
|
305
|
+
expect(m01.pass).toBe(true);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('fails when services/ exists but has no service files', async () => {
|
|
309
|
+
fs.mkdirSync(path.join(tmpDir, 'src', 'services'), { recursive: true });
|
|
310
|
+
const results = await collectDareLayeredDesign(tmpDir);
|
|
311
|
+
const m01 = results.find((r) => r.id === 'M-01')!;
|
|
312
|
+
expect(m01.pass).toBe(false);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('fails when services/ exists with service files but no tests anywhere', async () => {
|
|
316
|
+
const servicesDir = path.join(tmpDir, 'src', 'services');
|
|
317
|
+
fs.mkdirSync(servicesDir, { recursive: true });
|
|
318
|
+
fs.writeFileSync(path.join(servicesDir, 'user.service.ts'), 'export class UserService {}', 'utf-8');
|
|
319
|
+
const results = await collectDareLayeredDesign(tmpDir);
|
|
320
|
+
const m01 = results.find((r) => r.id === 'M-01')!;
|
|
321
|
+
expect(m01.pass).toBe(false);
|
|
322
|
+
expect(m01.details).toContain('no test files');
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('fails when no services/ directory found', async () => {
|
|
326
|
+
const results = await collectDareLayeredDesign(tmpDir);
|
|
327
|
+
const m01 = results.find((r) => r.id === 'M-01')!;
|
|
328
|
+
expect(m01.pass).toBe(false);
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe('M-02: no Handler→Repository violations', () => {
|
|
333
|
+
it('passes when no handler directories exist', async () => {
|
|
334
|
+
const results = await collectDareLayeredDesign(tmpDir);
|
|
335
|
+
const m02 = results.find((r) => r.id === 'M-02')!;
|
|
336
|
+
expect(m02.pass).toBe(true);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('passes when handlers do not import repositories', async () => {
|
|
340
|
+
const handlersDir = path.join(tmpDir, 'src', 'handlers');
|
|
341
|
+
fs.mkdirSync(handlersDir, { recursive: true });
|
|
342
|
+
fs.writeFileSync(
|
|
343
|
+
path.join(handlersDir, 'user.handler.ts'),
|
|
344
|
+
`import { UserService } from '../services/user.service.js';\nexport class UserHandler { constructor(private svc: UserService) {} }`,
|
|
345
|
+
'utf-8'
|
|
346
|
+
);
|
|
347
|
+
const results = await collectDareLayeredDesign(tmpDir);
|
|
348
|
+
const m02 = results.find((r) => r.id === 'M-02')!;
|
|
349
|
+
expect(m02.pass).toBe(true);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('fails when handler directly imports Repository', async () => {
|
|
353
|
+
const handlersDir = path.join(tmpDir, 'src', 'handlers');
|
|
354
|
+
fs.mkdirSync(handlersDir, { recursive: true });
|
|
355
|
+
fs.writeFileSync(
|
|
356
|
+
path.join(handlersDir, 'user.handler.ts'),
|
|
357
|
+
`import { UserRepository } from '../repositories/user.repository.js';\nexport class UserHandler {}`,
|
|
358
|
+
'utf-8'
|
|
359
|
+
);
|
|
360
|
+
const results = await collectDareLayeredDesign(tmpDir);
|
|
361
|
+
const m02 = results.find((r) => r.id === 'M-02')!;
|
|
362
|
+
expect(m02.pass).toBe(false);
|
|
363
|
+
expect(m02.details).toContain('violation');
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
describe('M-03: handlers use dependency injection', () => {
|
|
368
|
+
it('passes when no handler directories found', async () => {
|
|
369
|
+
const results = await collectDareLayeredDesign(tmpDir);
|
|
370
|
+
const m03 = results.find((r) => r.id === 'M-03')!;
|
|
371
|
+
expect(m03.pass).toBe(true);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('passes when handler uses @Inject pattern', async () => {
|
|
375
|
+
const handlersDir = path.join(tmpDir, 'src', 'handlers');
|
|
376
|
+
fs.mkdirSync(handlersDir, { recursive: true });
|
|
377
|
+
fs.writeFileSync(
|
|
378
|
+
path.join(handlersDir, 'user.handler.ts'),
|
|
379
|
+
`@Controller('users')\nexport class UserHandler { constructor(@Inject(UserService) private svc: UserService) {} }`,
|
|
380
|
+
'utf-8'
|
|
381
|
+
);
|
|
382
|
+
const results = await collectDareLayeredDesign(tmpDir);
|
|
383
|
+
const m03 = results.find((r) => r.id === 'M-03')!;
|
|
384
|
+
expect(m03.pass).toBe(true);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('fails when handler has new Service() instantiation', async () => {
|
|
388
|
+
const handlersDir = path.join(tmpDir, 'src', 'handlers');
|
|
389
|
+
fs.mkdirSync(handlersDir, { recursive: true });
|
|
390
|
+
fs.writeFileSync(
|
|
391
|
+
path.join(handlersDir, 'user.handler.ts'),
|
|
392
|
+
`export class UserHandler { async handle() { const svc = new UserService(); return svc.list(); } }`,
|
|
393
|
+
'utf-8'
|
|
394
|
+
);
|
|
395
|
+
const results = await collectDareLayeredDesign(tmpDir);
|
|
396
|
+
const m03 = results.find((r) => r.id === 'M-03')!;
|
|
397
|
+
expect(m03.pass).toBe(false);
|
|
398
|
+
expect(m03.details).toContain('instantiate');
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
describe('M-04: repositories are HTTP-agnostic', () => {
|
|
403
|
+
it('passes when no repository directories found', async () => {
|
|
404
|
+
const results = await collectDareLayeredDesign(tmpDir);
|
|
405
|
+
const m04 = results.find((r) => r.id === 'M-04')!;
|
|
406
|
+
expect(m04.pass).toBe(true);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('passes when repositories have no HTTP imports', async () => {
|
|
410
|
+
const reposDir = path.join(tmpDir, 'src', 'repositories');
|
|
411
|
+
fs.mkdirSync(reposDir, { recursive: true });
|
|
412
|
+
fs.writeFileSync(
|
|
413
|
+
path.join(reposDir, 'user.repository.ts'),
|
|
414
|
+
`import { PrismaClient } from '@prisma/client';\nexport class UserRepository { async findAll() { return []; } }`,
|
|
415
|
+
'utf-8'
|
|
416
|
+
);
|
|
417
|
+
const results = await collectDareLayeredDesign(tmpDir);
|
|
418
|
+
const m04 = results.find((r) => r.id === 'M-04')!;
|
|
419
|
+
expect(m04.pass).toBe(true);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it('fails when repository imports express', async () => {
|
|
423
|
+
const reposDir = path.join(tmpDir, 'src', 'repositories');
|
|
424
|
+
fs.mkdirSync(reposDir, { recursive: true });
|
|
425
|
+
fs.writeFileSync(
|
|
426
|
+
path.join(reposDir, 'bad.repository.ts'),
|
|
427
|
+
`import express from 'express';\nexport class BadRepo {}`,
|
|
428
|
+
'utf-8'
|
|
429
|
+
);
|
|
430
|
+
const results = await collectDareLayeredDesign(tmpDir);
|
|
431
|
+
const m04 = results.find((r) => r.id === 'M-04')!;
|
|
432
|
+
expect(m04.pass).toBe(false);
|
|
433
|
+
expect(m04.details).toContain('HTTP concern');
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('fails when repository uses HTTP status code 404', async () => {
|
|
437
|
+
const reposDir = path.join(tmpDir, 'src', 'repositories');
|
|
438
|
+
fs.mkdirSync(reposDir, { recursive: true });
|
|
439
|
+
fs.writeFileSync(
|
|
440
|
+
path.join(reposDir, 'user.repository.ts'),
|
|
441
|
+
`export class UserRepo { find(id: string) { return { status: 404 }; } }`,
|
|
442
|
+
'utf-8'
|
|
443
|
+
);
|
|
444
|
+
const results = await collectDareLayeredDesign(tmpDir);
|
|
445
|
+
const m04 = results.find((r) => r.id === 'M-04')!;
|
|
446
|
+
expect(m04.pass).toBe(false);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it('returns 4 metrics total', async () => {
|
|
450
|
+
const results = await collectDareLayeredDesign(tmpDir);
|
|
451
|
+
expect(results).toHaveLength(4);
|
|
452
|
+
expect(results.map((r) => r.id)).toEqual(['M-01', 'M-02', 'M-03', 'M-04']);
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// ── collectors registry ───────────────────────────────────────────────────────
|
|
458
|
+
|
|
459
|
+
describe('collectors registry', () => {
|
|
460
|
+
it('contains dare-ax collector', () => {
|
|
461
|
+
expect(collectors['dare-ax']).toBeDefined();
|
|
462
|
+
expect(typeof collectors['dare-ax']).toBe('function');
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('contains dare-layered-design collector', () => {
|
|
466
|
+
expect(collectors['dare-layered-design']).toBeDefined();
|
|
467
|
+
expect(typeof collectors['dare-layered-design']).toBe('function');
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('dare-ax collector returns 4 MetricResults', async () => {
|
|
471
|
+
const results = await collectors['dare-ax'](tmpDir);
|
|
472
|
+
expect(results).toHaveLength(4);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('dare-layered-design collector returns 4 MetricResults', async () => {
|
|
476
|
+
const results = await collectors['dare-layered-design'](tmpDir);
|
|
477
|
+
expect(results).toHaveLength(4);
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// ── collectMetrics ────────────────────────────────────────────────────────────
|
|
482
|
+
|
|
483
|
+
describe('collectMetrics', () => {
|
|
484
|
+
it('returns a ProjectMetricReport with correct structure', async () => {
|
|
485
|
+
const report = await collectMetrics({ projectPath: tmpDir, skills: ['dare-ax'] });
|
|
486
|
+
expect(report.timestamp).toBeTruthy();
|
|
487
|
+
expect(report.commit).toBeTruthy();
|
|
488
|
+
expect(report.projectPath).toBe(tmpDir);
|
|
489
|
+
expect(report.skills).toHaveLength(1);
|
|
490
|
+
expect(report.overall).toBeDefined();
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it('collects metrics for multiple skills', async () => {
|
|
494
|
+
const report = await collectMetrics({
|
|
495
|
+
projectPath: tmpDir,
|
|
496
|
+
skills: ['dare-ax', 'dare-layered-design'],
|
|
497
|
+
});
|
|
498
|
+
expect(report.skills).toHaveLength(2);
|
|
499
|
+
expect(report.skills[0].skillName).toBe('dare-ax');
|
|
500
|
+
expect(report.skills[1].skillName).toBe('dare-layered-design');
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it('saves dare_metrics.json to tmp/ directory', async () => {
|
|
504
|
+
await collectMetrics({ projectPath: tmpDir, skills: ['dare-ax'] });
|
|
505
|
+
const metricsPath = path.join(tmpDir, 'tmp', 'dare_metrics.json');
|
|
506
|
+
expect(fs.existsSync(metricsPath)).toBe(true);
|
|
507
|
+
const content = JSON.parse(fs.readFileSync(metricsPath, 'utf-8'));
|
|
508
|
+
expect(content.skills).toBeDefined();
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it('saved JSON is valid ProjectMetricReport', async () => {
|
|
512
|
+
await collectMetrics({ projectPath: tmpDir, skills: ['dare-ax'] });
|
|
513
|
+
const saved = JSON.parse(
|
|
514
|
+
fs.readFileSync(path.join(tmpDir, 'tmp', 'dare_metrics.json'), 'utf-8')
|
|
515
|
+
) as ProjectMetricReport;
|
|
516
|
+
expect(saved.overall.total).toBeGreaterThan(0);
|
|
517
|
+
expect(saved.overall.score).toMatch(/\d+\/\d+/);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('uses "unknown" commit when outside git repo', async () => {
|
|
521
|
+
// tmpDir is not a git repo
|
|
522
|
+
const report = await collectMetrics({ projectPath: tmpDir, skills: [] });
|
|
523
|
+
// could be "unknown" or an actual SHA if running in a git repo context
|
|
524
|
+
expect(typeof report.commit).toBe('string');
|
|
525
|
+
expect(report.commit.length).toBeGreaterThan(0);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
it('reports unknown skill gracefully with pass: false', async () => {
|
|
529
|
+
const report = await collectMetrics({
|
|
530
|
+
projectPath: tmpDir,
|
|
531
|
+
skills: ['dare-nonexistent-skill'],
|
|
532
|
+
});
|
|
533
|
+
expect(report.skills[0].skillName).toBe('dare-nonexistent-skill');
|
|
534
|
+
expect(report.skills[0].summary.allPass).toBe(false);
|
|
535
|
+
expect(report.skills[0].metrics[0].details).toContain('No collector registered');
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it('overall score reflects sum across all skills', async () => {
|
|
539
|
+
const report = await collectMetrics({
|
|
540
|
+
projectPath: tmpDir,
|
|
541
|
+
skills: ['dare-ax', 'dare-layered-design'],
|
|
542
|
+
});
|
|
543
|
+
const expectedTotal = report.skills.reduce((sum, s) => sum + s.metrics.length, 0);
|
|
544
|
+
const expectedPassed = report.skills.reduce((sum, s) => sum + s.summary.passed, 0);
|
|
545
|
+
expect(report.overall.total).toBe(expectedTotal);
|
|
546
|
+
expect(report.overall.passed).toBe(expectedPassed);
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
// ── buildSummary ──────────────────────────────────────────────────────────────
|
|
551
|
+
|
|
552
|
+
describe('buildSummary', () => {
|
|
553
|
+
it('returns correct score when all pass', () => {
|
|
554
|
+
const metrics = [makeMetric('M-01', true), makeMetric('M-02', true)];
|
|
555
|
+
const summary = buildSummary(metrics);
|
|
556
|
+
expect(summary.passed).toBe(2);
|
|
557
|
+
expect(summary.total).toBe(2);
|
|
558
|
+
expect(summary.score).toBe('2/2');
|
|
559
|
+
expect(summary.allPass).toBe(true);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it('returns allPass false when some fail', () => {
|
|
563
|
+
const metrics = [makeMetric('M-01', true), makeMetric('M-02', false)];
|
|
564
|
+
const summary = buildSummary(metrics);
|
|
565
|
+
expect(summary.allPass).toBe(false);
|
|
566
|
+
expect(summary.score).toBe('1/2');
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
it('handles empty metrics', () => {
|
|
570
|
+
const summary = buildSummary([]);
|
|
571
|
+
expect(summary.total).toBe(0);
|
|
572
|
+
expect(summary.passed).toBe(0);
|
|
573
|
+
expect(summary.allPass).toBe(false);
|
|
574
|
+
expect(summary.score).toBe('0/0');
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
// ── detectCommit ──────────────────────────────────────────────────────────────
|
|
579
|
+
|
|
580
|
+
describe('detectCommit', () => {
|
|
581
|
+
it('returns a string', () => {
|
|
582
|
+
// May return an actual SHA or "unknown" depending on environment
|
|
583
|
+
const commit = detectCommit(tmpDir);
|
|
584
|
+
expect(typeof commit).toBe('string');
|
|
585
|
+
expect(commit.length).toBeGreaterThan(0);
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
it('returns "unknown" for non-git directory', () => {
|
|
589
|
+
const commit = detectCommit(tmpDir);
|
|
590
|
+
// tmpDir is not a git repo, so should be "unknown"
|
|
591
|
+
expect(commit).toBe('unknown');
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
// ── detectRegressions ─────────────────────────────────────────────────────────
|
|
596
|
+
|
|
597
|
+
describe('detectRegressions', () => {
|
|
598
|
+
it('returns empty array when no regressions', () => {
|
|
599
|
+
const baseline = makeProjectReport([
|
|
600
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', true), makeMetric('M-02', true)]),
|
|
601
|
+
]);
|
|
602
|
+
const current = makeProjectReport([
|
|
603
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', true), makeMetric('M-02', true)]),
|
|
604
|
+
]);
|
|
605
|
+
expect(detectRegressions(baseline, current)).toHaveLength(0);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it('detects a regression (pass → fail)', () => {
|
|
609
|
+
const baseline = makeProjectReport([
|
|
610
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', true), makeMetric('M-02', true)]),
|
|
611
|
+
]);
|
|
612
|
+
const current = makeProjectReport([
|
|
613
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', false), makeMetric('M-02', true)]),
|
|
614
|
+
]);
|
|
615
|
+
const regressions = detectRegressions(baseline, current);
|
|
616
|
+
expect(regressions).toHaveLength(1);
|
|
617
|
+
expect(regressions[0].skill).toBe('dare-ax');
|
|
618
|
+
expect(regressions[0].metricId).toBe('M-01');
|
|
619
|
+
expect(regressions[0].regressed).toBe(true);
|
|
620
|
+
expect(regressions[0].baseline).toBe(true);
|
|
621
|
+
expect(regressions[0].current).toBe(false);
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
it('ignores improvements (fail → pass)', () => {
|
|
625
|
+
const baseline = makeProjectReport([
|
|
626
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', false)]),
|
|
627
|
+
]);
|
|
628
|
+
const current = makeProjectReport([
|
|
629
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', true)]),
|
|
630
|
+
]);
|
|
631
|
+
expect(detectRegressions(baseline, current)).toHaveLength(0);
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
it('ignores metrics not in baseline', () => {
|
|
635
|
+
const baseline = makeProjectReport([
|
|
636
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', true)]),
|
|
637
|
+
]);
|
|
638
|
+
const current = makeProjectReport([
|
|
639
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', true), makeMetric('M-02', false)]),
|
|
640
|
+
]);
|
|
641
|
+
// M-02 is not in baseline → not a regression
|
|
642
|
+
expect(detectRegressions(baseline, current)).toHaveLength(0);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
it('detects multiple regressions across skills', () => {
|
|
646
|
+
const baseline = makeProjectReport([
|
|
647
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', true), makeMetric('M-02', true)]),
|
|
648
|
+
makeSkillReport('dare-layered-design', [makeMetric('M-01', true)]),
|
|
649
|
+
]);
|
|
650
|
+
const current = makeProjectReport([
|
|
651
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', false), makeMetric('M-02', false)]),
|
|
652
|
+
makeSkillReport('dare-layered-design', [makeMetric('M-01', false)]),
|
|
653
|
+
]);
|
|
654
|
+
const regressions = detectRegressions(baseline, current);
|
|
655
|
+
expect(regressions).toHaveLength(3);
|
|
656
|
+
expect(regressions.every((r) => r.regressed)).toBe(true);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
it('handles empty baseline and current', () => {
|
|
660
|
+
const baseline = makeProjectReport([]);
|
|
661
|
+
const current = makeProjectReport([]);
|
|
662
|
+
expect(detectRegressions(baseline, current)).toHaveLength(0);
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
// ── formatTable ───────────────────────────────────────────────────────────────
|
|
667
|
+
|
|
668
|
+
describe('formatTable', () => {
|
|
669
|
+
it('outputs ✅ for passing metrics', () => {
|
|
670
|
+
const report = makeProjectReport([
|
|
671
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', true)]),
|
|
672
|
+
]);
|
|
673
|
+
const output = formatTable(report);
|
|
674
|
+
expect(output).toContain('✅');
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
it('outputs ❌ for failing metrics', () => {
|
|
678
|
+
const report = makeProjectReport([
|
|
679
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', false)]),
|
|
680
|
+
]);
|
|
681
|
+
const output = formatTable(report);
|
|
682
|
+
expect(output).toContain('❌');
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
it('includes skill name and metric ID', () => {
|
|
686
|
+
const report = makeProjectReport([
|
|
687
|
+
makeSkillReport('dare-ax', [makeMetric('M-03', true)]),
|
|
688
|
+
]);
|
|
689
|
+
const output = formatTable(report);
|
|
690
|
+
expect(output).toContain('dare-ax');
|
|
691
|
+
expect(output).toContain('M-03');
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
it('includes OVERALL row', () => {
|
|
695
|
+
const report = makeProjectReport([
|
|
696
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', true)]),
|
|
697
|
+
]);
|
|
698
|
+
const output = formatTable(report);
|
|
699
|
+
expect(output).toContain('OVERALL');
|
|
700
|
+
expect(output).toContain('1/1');
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it('includes commit hash in output', () => {
|
|
704
|
+
const report = makeProjectReport([
|
|
705
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', true)]),
|
|
706
|
+
]);
|
|
707
|
+
const output = formatTable(report);
|
|
708
|
+
expect(output).toContain('abc1234');
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
it('uses box-drawing characters for ASCII table', () => {
|
|
712
|
+
const report = makeProjectReport([
|
|
713
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', true)]),
|
|
714
|
+
]);
|
|
715
|
+
const output = formatTable(report);
|
|
716
|
+
expect(output).toContain('┌');
|
|
717
|
+
expect(output).toContain('┘');
|
|
718
|
+
expect(output).toContain('│');
|
|
719
|
+
});
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
// ── formatJSON ────────────────────────────────────────────────────────────────
|
|
723
|
+
|
|
724
|
+
describe('formatJSON', () => {
|
|
725
|
+
it('returns valid JSON string', () => {
|
|
726
|
+
const report = makeProjectReport([
|
|
727
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', true)]),
|
|
728
|
+
]);
|
|
729
|
+
const json = formatJSON(report);
|
|
730
|
+
expect(() => JSON.parse(json)).not.toThrow();
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
it('includes all top-level fields', () => {
|
|
734
|
+
const report = makeProjectReport([
|
|
735
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', true)]),
|
|
736
|
+
]);
|
|
737
|
+
const parsed = JSON.parse(formatJSON(report));
|
|
738
|
+
expect(parsed.timestamp).toBeDefined();
|
|
739
|
+
expect(parsed.commit).toBeDefined();
|
|
740
|
+
expect(parsed.skills).toBeDefined();
|
|
741
|
+
expect(parsed.overall).toBeDefined();
|
|
742
|
+
});
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
// ── formatPRComment ───────────────────────────────────────────────────────────
|
|
746
|
+
|
|
747
|
+
describe('formatPRComment', () => {
|
|
748
|
+
it('returns markdown with DARE Quality Metrics heading', () => {
|
|
749
|
+
const report = makeProjectReport([
|
|
750
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', true)]),
|
|
751
|
+
]);
|
|
752
|
+
const md = formatPRComment(report);
|
|
753
|
+
expect(md).toContain('## DARE Quality Metrics');
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
it('includes ✅ when all metrics pass', () => {
|
|
757
|
+
const report = makeProjectReport([
|
|
758
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', true), makeMetric('M-02', true)]),
|
|
759
|
+
]);
|
|
760
|
+
const md = formatPRComment(report);
|
|
761
|
+
expect(md).toContain('✅');
|
|
762
|
+
expect(md).toContain('All metrics passing');
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
it('includes ❌ when some metrics fail', () => {
|
|
766
|
+
const report = makeProjectReport([
|
|
767
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', false)]),
|
|
768
|
+
]);
|
|
769
|
+
const md = formatPRComment(report);
|
|
770
|
+
expect(md).toContain('❌');
|
|
771
|
+
expect(md).toContain('Some metrics failing');
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
it('includes regression warnings when regressions provided', () => {
|
|
775
|
+
const report = makeProjectReport([
|
|
776
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', false)]),
|
|
777
|
+
]);
|
|
778
|
+
const regressions: RegressionResult[] = [
|
|
779
|
+
{ skill: 'dare-ax', metricId: 'M-01', baseline: true, current: false, regressed: true },
|
|
780
|
+
];
|
|
781
|
+
const md = formatPRComment(report, regressions);
|
|
782
|
+
expect(md).toContain('Regressions Detected');
|
|
783
|
+
expect(md).toContain('dare-ax');
|
|
784
|
+
expect(md).toContain('M-01');
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
it('does not include regression section when no regressions', () => {
|
|
788
|
+
const report = makeProjectReport([
|
|
789
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', true)]),
|
|
790
|
+
]);
|
|
791
|
+
const md = formatPRComment(report, []);
|
|
792
|
+
expect(md).not.toContain('Regressions Detected');
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
it('includes score in output', () => {
|
|
796
|
+
const report = makeProjectReport([
|
|
797
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', true), makeMetric('M-02', false)]),
|
|
798
|
+
]);
|
|
799
|
+
const md = formatPRComment(report);
|
|
800
|
+
expect(md).toContain('1/2');
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
it('includes commit hash', () => {
|
|
804
|
+
const report = makeProjectReport([
|
|
805
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', true)]),
|
|
806
|
+
]);
|
|
807
|
+
const md = formatPRComment(report);
|
|
808
|
+
expect(md).toContain('abc1234');
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
it('contains markdown table separators', () => {
|
|
812
|
+
const report = makeProjectReport([
|
|
813
|
+
makeSkillReport('dare-ax', [makeMetric('M-01', true)]),
|
|
814
|
+
]);
|
|
815
|
+
const md = formatPRComment(report);
|
|
816
|
+
expect(md).toContain('|--------|');
|
|
817
|
+
});
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
// ── QualityTelemetryMetrics (self-assessment) ─────────────────────────────────
|
|
821
|
+
|
|
822
|
+
describe('QualityTelemetryMetrics', () => {
|
|
823
|
+
it('collect() returns 4 MetricResults', () => {
|
|
824
|
+
const qt = new QualityTelemetryMetrics();
|
|
825
|
+
const results = qt.collect(tmpDir);
|
|
826
|
+
expect(results).toHaveLength(4);
|
|
827
|
+
expect(results.map((r) => r.id)).toEqual(['M-01', 'M-02', 'M-03', 'M-04']);
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
it('M-01 always passes when skill is installed', () => {
|
|
831
|
+
const qt = new QualityTelemetryMetrics();
|
|
832
|
+
const m01 = qt.collectM01(tmpDir);
|
|
833
|
+
expect(m01.pass).toBe(true);
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
it('M-02 fails when no baseline file exists', () => {
|
|
837
|
+
const qt = new QualityTelemetryMetrics();
|
|
838
|
+
const m02 = qt.collectM02(tmpDir);
|
|
839
|
+
expect(m02.pass).toBe(false);
|
|
840
|
+
expect(m02.details).toContain('No baseline');
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
it('M-02 passes when tmp/dare_metrics.json exists', () => {
|
|
844
|
+
fs.mkdirSync(path.join(tmpDir, 'tmp'));
|
|
845
|
+
fs.writeFileSync(path.join(tmpDir, 'tmp', 'dare_metrics.json'), '{}', 'utf-8');
|
|
846
|
+
const qt = new QualityTelemetryMetrics();
|
|
847
|
+
const m02 = qt.collectM02(tmpDir);
|
|
848
|
+
expect(m02.pass).toBe(true);
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
it('M-03 fails when tmp/dare_metrics.json does not exist', () => {
|
|
852
|
+
const qt = new QualityTelemetryMetrics();
|
|
853
|
+
const m03 = qt.collectM03(tmpDir);
|
|
854
|
+
expect(m03.pass).toBe(false);
|
|
855
|
+
expect(m03.details).toContain('dare_metrics.json');
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
it('M-03 passes when tmp/dare_metrics.json exists', () => {
|
|
859
|
+
fs.mkdirSync(path.join(tmpDir, 'tmp'));
|
|
860
|
+
fs.writeFileSync(path.join(tmpDir, 'tmp', 'dare_metrics.json'), '{}', 'utf-8');
|
|
861
|
+
const qt = new QualityTelemetryMetrics();
|
|
862
|
+
const m03 = qt.collectM03(tmpDir);
|
|
863
|
+
expect(m03.pass).toBe(true);
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
it('M-04 fails when .github/workflows/dare-metrics.yml does not exist', () => {
|
|
867
|
+
const qt = new QualityTelemetryMetrics();
|
|
868
|
+
const m04 = qt.collectM04(tmpDir);
|
|
869
|
+
expect(m04.pass).toBe(false);
|
|
870
|
+
expect(m04.details).toContain('dare-metrics.yml');
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
it('M-04 passes when .github/workflows/dare-metrics.yml exists', () => {
|
|
874
|
+
const workflowDir = path.join(tmpDir, '.github', 'workflows');
|
|
875
|
+
fs.mkdirSync(workflowDir, { recursive: true });
|
|
876
|
+
fs.writeFileSync(
|
|
877
|
+
path.join(workflowDir, 'dare-metrics.yml'),
|
|
878
|
+
'name: DARE Metrics\n',
|
|
879
|
+
'utf-8'
|
|
880
|
+
);
|
|
881
|
+
const qt = new QualityTelemetryMetrics();
|
|
882
|
+
const m04 = qt.collectM04(tmpDir);
|
|
883
|
+
expect(m04.pass).toBe(true);
|
|
884
|
+
});
|
|
885
|
+
});
|