@dewtech/dare-cli 3.11.0 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -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 +1 -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 +2 -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__/execute-agent.test.js +82 -0
- package/dist/commands/__tests__/execute-agent.test.js.map +1 -1
- 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/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 +111 -4
- package/dist/commands/execute.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.map +1 -1
- package/dist/commands/refine.js +23 -2
- 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 +25 -3
- 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/run_dag.d.ts +1 -1
- package/dist/dag-runner/run_dag.d.ts.map +1 -1
- 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/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/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/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__/safe-spawn.test.js +12 -0
- package/dist/verification/__tests__/safe-spawn.test.js.map +1 -1
- package/package.json +2 -1
- 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/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-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-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-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,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dare-layered-design — LayeredDesignMetrics
|
|
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 { LayeredDesignLinter } from './linter.js';
|
|
11
|
+
|
|
12
|
+
export class LayeredDesignMetrics {
|
|
13
|
+
private readonly linter: LayeredDesignLinter;
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
this.linter = new LayeredDesignLinter();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Collects all four layered-design metrics.
|
|
21
|
+
*/
|
|
22
|
+
collect(projectPath: string): MetricResult[] {
|
|
23
|
+
return [
|
|
24
|
+
this.collectM01(projectPath),
|
|
25
|
+
this.collectM02(projectPath),
|
|
26
|
+
this.collectM03(projectPath),
|
|
27
|
+
this.collectM04(projectPath),
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* M-01: Services have unit tests (test files exist for services).
|
|
33
|
+
* Checks that every service file has a corresponding test file.
|
|
34
|
+
*/
|
|
35
|
+
collectM01(projectPath: string): MetricResult {
|
|
36
|
+
const serviceFiles = findServiceFiles(projectPath);
|
|
37
|
+
|
|
38
|
+
if (serviceFiles.length === 0) {
|
|
39
|
+
return {
|
|
40
|
+
id: 'M-01',
|
|
41
|
+
pass: false,
|
|
42
|
+
description: '100% of Services have unit tests',
|
|
43
|
+
detail: 'No service files found in services/ directories. Create services first.',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const servicesWithoutTests: string[] = [];
|
|
48
|
+
|
|
49
|
+
for (const serviceFile of serviceFiles) {
|
|
50
|
+
if (!hasTestFile(serviceFile, projectPath)) {
|
|
51
|
+
servicesWithoutTests.push(path.relative(projectPath, serviceFile));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (servicesWithoutTests.length > 0) {
|
|
56
|
+
return {
|
|
57
|
+
id: 'M-01',
|
|
58
|
+
pass: false,
|
|
59
|
+
description: '100% of Services have unit tests',
|
|
60
|
+
detail:
|
|
61
|
+
`${servicesWithoutTests.length} service(s) have no corresponding test file: ` +
|
|
62
|
+
servicesWithoutTests.slice(0, 5).join(', ') +
|
|
63
|
+
(servicesWithoutTests.length > 5 ? ` ... (${servicesWithoutTests.length - 5} more)` : ''),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
id: 'M-01',
|
|
69
|
+
pass: true,
|
|
70
|
+
description: '100% of Services have unit tests',
|
|
71
|
+
detail: `All ${serviceFiles.length} service file(s) have corresponding test files.`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* M-02: 0% Handler→Repository direct calls.
|
|
77
|
+
* Uses the LayeredDesignLinter to detect violations.
|
|
78
|
+
*/
|
|
79
|
+
collectM02(projectPath: string): MetricResult {
|
|
80
|
+
const result = this.linter.lint(projectPath);
|
|
81
|
+
|
|
82
|
+
if (result.filesScanned === 0) {
|
|
83
|
+
return {
|
|
84
|
+
id: 'M-02',
|
|
85
|
+
pass: true,
|
|
86
|
+
description: '0% Handler→Repository direct calls',
|
|
87
|
+
detail: 'No handler files found to scan.',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (result.violations.length > 0) {
|
|
92
|
+
const summary = result.violations
|
|
93
|
+
.slice(0, 3)
|
|
94
|
+
.map(
|
|
95
|
+
(v) =>
|
|
96
|
+
`${path.relative(projectPath, v.file)}:${v.line} — ${v.message}`
|
|
97
|
+
)
|
|
98
|
+
.join('; ');
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
id: 'M-02',
|
|
102
|
+
pass: false,
|
|
103
|
+
description: '0% Handler→Repository direct calls',
|
|
104
|
+
detail:
|
|
105
|
+
`Found ${result.violations.length} violation(s) in ${result.filesScanned} handler files scanned. ` +
|
|
106
|
+
`First violations: ${summary}`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
id: 'M-02',
|
|
112
|
+
pass: true,
|
|
113
|
+
description: '0% Handler→Repository direct calls',
|
|
114
|
+
detail: `No violations found across ${result.filesScanned} handler file(s) scanned.`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* M-03: 100% of Handlers use dependency injection (no `new Service()` inside handlers).
|
|
120
|
+
* Checks handler files for direct service instantiation.
|
|
121
|
+
*/
|
|
122
|
+
collectM03(projectPath: string): MetricResult {
|
|
123
|
+
const handlerDirs = findLayerDirs(projectPath, ['handlers', 'controllers']);
|
|
124
|
+
|
|
125
|
+
if (handlerDirs.length === 0) {
|
|
126
|
+
return {
|
|
127
|
+
id: 'M-03',
|
|
128
|
+
pass: true,
|
|
129
|
+
description: '100% of Handlers use dependency injection',
|
|
130
|
+
detail: 'No handler directories found.',
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const violations: Array<{ file: string; line: number; content: string }> = [];
|
|
135
|
+
|
|
136
|
+
for (const dir of handlerDirs) {
|
|
137
|
+
const files = collectFilesRecursive(dir, ['.ts', '.js', '.rb', '.rs', '.py', '.go', '.php'], 4);
|
|
138
|
+
for (const file of files) {
|
|
139
|
+
findDIViolations(file, violations);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (violations.length > 0) {
|
|
144
|
+
const summary = violations
|
|
145
|
+
.slice(0, 3)
|
|
146
|
+
.map((v) => `${path.relative(projectPath, v.file)}:${v.line}`)
|
|
147
|
+
.join(', ');
|
|
148
|
+
return {
|
|
149
|
+
id: 'M-03',
|
|
150
|
+
pass: false,
|
|
151
|
+
description: '100% of Handlers use dependency injection',
|
|
152
|
+
detail: `Found ${violations.length} handler(s) that instantiate services directly (e.g., new XService()). Violations: ${summary}. Use constructor injection instead.`,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const totalFiles = handlerDirs.reduce(
|
|
157
|
+
(sum, d) => sum + collectFilesRecursive(d, ['.ts', '.js', '.rb', '.rs', '.py', '.go'], 4).length,
|
|
158
|
+
0
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
id: 'M-03',
|
|
163
|
+
pass: true,
|
|
164
|
+
description: '100% of Handlers use dependency injection',
|
|
165
|
+
detail: `No direct service instantiation found in ${totalFiles} handler file(s).`,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* M-04: 100% of Repositories are agnostic to upper layers.
|
|
171
|
+
* Checks repository files for HTTP-specific code (status codes, response objects).
|
|
172
|
+
*/
|
|
173
|
+
collectM04(projectPath: string): MetricResult {
|
|
174
|
+
const repoDirs = findLayerDirs(projectPath, ['repositories', 'repos', 'data_access']);
|
|
175
|
+
|
|
176
|
+
if (repoDirs.length === 0) {
|
|
177
|
+
return {
|
|
178
|
+
id: 'M-04',
|
|
179
|
+
pass: true,
|
|
180
|
+
description: '100% of Repositories are agnostic to upper layers',
|
|
181
|
+
detail: 'No repository directories found.',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const violations: Array<{ file: string; line: number; content: string }> = [];
|
|
186
|
+
|
|
187
|
+
for (const dir of repoDirs) {
|
|
188
|
+
const files = collectFilesRecursive(dir, ['.ts', '.js', '.rb', '.rs', '.py', '.go', '.php'], 4);
|
|
189
|
+
for (const file of files) {
|
|
190
|
+
findHTTPInRepoViolations(file, violations);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (violations.length > 0) {
|
|
195
|
+
const summary = violations
|
|
196
|
+
.slice(0, 3)
|
|
197
|
+
.map((v) => `${path.relative(projectPath, v.file)}:${v.line} — "${v.content.slice(0, 60)}"`)
|
|
198
|
+
.join('; ');
|
|
199
|
+
return {
|
|
200
|
+
id: 'M-04',
|
|
201
|
+
pass: false,
|
|
202
|
+
description: '100% of Repositories are agnostic to upper layers',
|
|
203
|
+
detail: `Found ${violations.length} HTTP concern(s) in repository files. Repositories must not return HTTP status codes or DTOs. Violations: ${summary}`,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
id: 'M-04',
|
|
209
|
+
pass: true,
|
|
210
|
+
description: '100% of Repositories are agnostic to upper layers',
|
|
211
|
+
detail: 'No HTTP concerns found in repository files.',
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
const SKIP_DIRS = new Set(['node_modules', 'target', 'dist', '.git', 'vendor', '__pycache__', 'coverage']);
|
|
219
|
+
|
|
220
|
+
/** Patterns that indicate direct service instantiation in a handler (DI violation) */
|
|
221
|
+
const DI_VIOLATION_PATTERNS: RegExp[] = [
|
|
222
|
+
/new\s+\w+Service\s*\(/,
|
|
223
|
+
/new\s+\w+UseCase\s*\(/,
|
|
224
|
+
/new\s+Create\w+\s*\(/,
|
|
225
|
+
/new\s+Update\w+\s*\(/,
|
|
226
|
+
/new\s+Delete\w+\s*\(/,
|
|
227
|
+
/\w+Service\.new\b/, // Ruby style
|
|
228
|
+
/\w+UseCase\.new\b/, // Ruby style
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
/** Patterns that indicate HTTP knowledge inside a repository (layer violation) */
|
|
232
|
+
const HTTP_IN_REPO_PATTERNS: RegExp[] = [
|
|
233
|
+
/status\s*[:=]\s*[45]\d\d/, // status: 404, status = 500
|
|
234
|
+
/http_status|httpStatus|HttpStatus/i,
|
|
235
|
+
/res\.status\s*\(/, // res.status(404)
|
|
236
|
+
/raise\s+.*NotFound.*Error.*HTTP/i, // raise HTTP::NotFoundError
|
|
237
|
+
/NotFoundException.*\(\s*[45]\d\d/, // NotFoundException(404)
|
|
238
|
+
/render\s+json.*status:/i, // Rails: render json: ..., status:
|
|
239
|
+
/Response\.\w+\(\s*[45]\d\d/, // Response.notFound(404)
|
|
240
|
+
/throw new HttpException/i, // NestJS
|
|
241
|
+
];
|
|
242
|
+
|
|
243
|
+
function findServiceFiles(projectPath: string): string[] {
|
|
244
|
+
const dirs = findLayerDirs(projectPath, ['services', 'use_cases', 'interactors', 'commands']);
|
|
245
|
+
const results: string[] = [];
|
|
246
|
+
const extensions = ['.ts', '.js', '.rb', '.rs', '.py', '.go', '.php'];
|
|
247
|
+
|
|
248
|
+
for (const dir of dirs) {
|
|
249
|
+
collectFilesRecursive(dir, extensions, 4).forEach((f) => {
|
|
250
|
+
// Exclude test files
|
|
251
|
+
if (!isTestFile(f)) results.push(f);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return results;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function findLayerDirs(projectPath: string, layerNames: string[]): string[] {
|
|
259
|
+
const results: string[] = [];
|
|
260
|
+
const searchRoots = [projectPath, ...['src', 'app', 'lib'].map((r) => path.join(projectPath, r))];
|
|
261
|
+
|
|
262
|
+
for (const root of searchRoots) {
|
|
263
|
+
if (!fs.existsSync(root)) continue;
|
|
264
|
+
for (const name of layerNames) {
|
|
265
|
+
const candidate = path.join(root, name);
|
|
266
|
+
if (fs.existsSync(candidate)) results.push(candidate);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return results;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function collectFilesRecursive(dir: string, extensions: string[], maxDepth: number, depth = 0): string[] {
|
|
274
|
+
if (depth > maxDepth) return [];
|
|
275
|
+
|
|
276
|
+
let entries: fs.Dirent[];
|
|
277
|
+
try {
|
|
278
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
279
|
+
} catch {
|
|
280
|
+
return [];
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const results: string[] = [];
|
|
284
|
+
for (const entry of entries) {
|
|
285
|
+
if (entry.name.startsWith('.') || SKIP_DIRS.has(entry.name)) continue;
|
|
286
|
+
const fp = path.join(dir, entry.name);
|
|
287
|
+
if (entry.isDirectory()) {
|
|
288
|
+
results.push(...collectFilesRecursive(fp, extensions, maxDepth, depth + 1));
|
|
289
|
+
} else if (entry.isFile() && extensions.includes(path.extname(entry.name))) {
|
|
290
|
+
results.push(fp);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return results;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function hasTestFile(serviceFile: string, projectPath: string): boolean {
|
|
297
|
+
const basename = path.basename(serviceFile, path.extname(serviceFile));
|
|
298
|
+
const ext = path.extname(serviceFile);
|
|
299
|
+
|
|
300
|
+
// Possible test file patterns
|
|
301
|
+
const testPatterns = [
|
|
302
|
+
`${basename}.spec${ext}`,
|
|
303
|
+
`${basename}.test${ext}`,
|
|
304
|
+
`${basename}_spec${ext}`,
|
|
305
|
+
`${basename}_test${ext}`,
|
|
306
|
+
`test_${basename}${ext}`,
|
|
307
|
+
];
|
|
308
|
+
|
|
309
|
+
// Search in common test directories
|
|
310
|
+
const testRoots = [
|
|
311
|
+
path.join(projectPath, 'tests'),
|
|
312
|
+
path.join(projectPath, 'test'),
|
|
313
|
+
path.join(projectPath, 'spec'),
|
|
314
|
+
path.join(projectPath, '__tests__'),
|
|
315
|
+
path.join(projectPath, 'src', '__tests__'),
|
|
316
|
+
path.dirname(serviceFile), // Co-located tests
|
|
317
|
+
];
|
|
318
|
+
|
|
319
|
+
for (const testRoot of testRoots) {
|
|
320
|
+
if (!fs.existsSync(testRoot)) continue;
|
|
321
|
+
for (const pattern of testPatterns) {
|
|
322
|
+
const candidate = path.join(testRoot, pattern);
|
|
323
|
+
if (fs.existsSync(candidate)) return true;
|
|
324
|
+
}
|
|
325
|
+
// Also search recursively for the test file
|
|
326
|
+
const found = findFileByName(testRoot, testPatterns, 4);
|
|
327
|
+
if (found) return true;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function findFileByName(dir: string, names: string[], maxDepth: number, depth = 0): string | null {
|
|
334
|
+
if (depth > maxDepth) return null;
|
|
335
|
+
|
|
336
|
+
let entries: fs.Dirent[];
|
|
337
|
+
try {
|
|
338
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
339
|
+
} catch {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
for (const entry of entries) {
|
|
344
|
+
if (entry.name.startsWith('.') || SKIP_DIRS.has(entry.name)) continue;
|
|
345
|
+
const fp = path.join(dir, entry.name);
|
|
346
|
+
if (entry.isFile() && names.includes(entry.name)) return fp;
|
|
347
|
+
if (entry.isDirectory()) {
|
|
348
|
+
const found = findFileByName(fp, names, maxDepth, depth + 1);
|
|
349
|
+
if (found) return found;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function isTestFile(file: string): boolean {
|
|
356
|
+
return /\.(spec|test)\.[a-z]+$/.test(file) || /_spec\.[a-z]+$/.test(file) || /_test\.[a-z]+$/.test(file);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function findDIViolations(
|
|
360
|
+
file: string,
|
|
361
|
+
out: Array<{ file: string; line: number; content: string }>
|
|
362
|
+
): void {
|
|
363
|
+
let content: string;
|
|
364
|
+
try {
|
|
365
|
+
content = fs.readFileSync(file, 'utf-8');
|
|
366
|
+
} catch {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const lines = content.split('\n');
|
|
371
|
+
for (let i = 0; i < lines.length; i++) {
|
|
372
|
+
const line = lines[i];
|
|
373
|
+
const trimmed = line.trim();
|
|
374
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('#') || trimmed.startsWith('*')) continue;
|
|
375
|
+
|
|
376
|
+
for (const pattern of DI_VIOLATION_PATTERNS) {
|
|
377
|
+
if (pattern.test(line)) {
|
|
378
|
+
out.push({ file, line: i + 1, content: trimmed });
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function findHTTPInRepoViolations(
|
|
386
|
+
file: string,
|
|
387
|
+
out: Array<{ file: string; line: number; content: string }>
|
|
388
|
+
): void {
|
|
389
|
+
let content: string;
|
|
390
|
+
try {
|
|
391
|
+
content = fs.readFileSync(file, 'utf-8');
|
|
392
|
+
} catch {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const lines = content.split('\n');
|
|
397
|
+
for (let i = 0; i < lines.length; i++) {
|
|
398
|
+
const line = lines[i];
|
|
399
|
+
const trimmed = line.trim();
|
|
400
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('#') || trimmed.startsWith('*')) continue;
|
|
401
|
+
|
|
402
|
+
for (const pattern of HTTP_IN_REPO_PATTERNS) {
|
|
403
|
+
if (pattern.test(line)) {
|
|
404
|
+
out.push({ file, line: i + 1, content: trimmed });
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|