@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,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dare-layered-design — Layered Design skill
|
|
3
|
+
* Enforces Handler → Service → Repository → Model architecture.
|
|
4
|
+
* License: MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { LayeredDesignLinter } from './linter.js';
|
|
8
|
+
export { LayeredDesignGenerator } from './generator.js';
|
|
9
|
+
export { LayeredDesignMetrics } from './metrics.js';
|
|
10
|
+
export type {
|
|
11
|
+
Language,
|
|
12
|
+
LinterViolation,
|
|
13
|
+
LinterResult,
|
|
14
|
+
ScaffoldOptions,
|
|
15
|
+
ScaffoldResult,
|
|
16
|
+
MetricResult,
|
|
17
|
+
} from './types.js';
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dare-layered-design — LayeredDesignLinter
|
|
3
|
+
* Static analysis: detects Handler → Repository direct calls (violation of layered design).
|
|
4
|
+
* License: MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { Language, LinterViolation, LinterResult } from './types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Per-language rules for detecting Handler→Repository violations.
|
|
13
|
+
*
|
|
14
|
+
* Each rule defines:
|
|
15
|
+
* - `handlerDirs`: directories that contain handlers/controllers
|
|
16
|
+
* - `handlerFilePattern`: regex to identify handler files by name
|
|
17
|
+
* - `repositoryImportPattern`: regex that detects a Repository import/use in handler files
|
|
18
|
+
* - `repositoryUsagePattern`: regex that detects direct Repository instantiation or call
|
|
19
|
+
*/
|
|
20
|
+
interface LanguageRule {
|
|
21
|
+
language: Language;
|
|
22
|
+
extensions: string[];
|
|
23
|
+
/** Dirs relative to projectPath/srcPath where handlers live */
|
|
24
|
+
handlerDirs: string[];
|
|
25
|
+
/** Pattern matching handler file names */
|
|
26
|
+
handlerFilePattern: RegExp;
|
|
27
|
+
/**
|
|
28
|
+
* Patterns that, if found in a handler file, indicate a direct Repository reference.
|
|
29
|
+
* Each match = one violation (checked line-by-line).
|
|
30
|
+
*/
|
|
31
|
+
violationPatterns: Array<{
|
|
32
|
+
pattern: RegExp;
|
|
33
|
+
message: string;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const LANGUAGE_RULES: LanguageRule[] = [
|
|
38
|
+
// ── TypeScript / JavaScript ─────────────────────────────────────────────────
|
|
39
|
+
{
|
|
40
|
+
language: 'typescript',
|
|
41
|
+
extensions: ['.ts', '.tsx'],
|
|
42
|
+
handlerDirs: ['handlers', 'controllers', 'routes', 'router'],
|
|
43
|
+
handlerFilePattern: /handler|controller|route/i,
|
|
44
|
+
violationPatterns: [
|
|
45
|
+
{
|
|
46
|
+
pattern: /import\s+.*Repository/,
|
|
47
|
+
message:
|
|
48
|
+
'Handler imports Repository directly. Use a Service instead (Handler → Service → Repository).',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
pattern: /new\s+\w+Repository\s*\(/,
|
|
52
|
+
message:
|
|
53
|
+
'Handler instantiates Repository directly. Inject a Service instead.',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
pattern: /\w+Repository\.\w+\s*\(/,
|
|
57
|
+
message:
|
|
58
|
+
'Handler calls Repository method directly. Delegate to a Service.',
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
language: 'javascript',
|
|
64
|
+
extensions: ['.js', '.mjs'],
|
|
65
|
+
handlerDirs: ['handlers', 'controllers', 'routes', 'router'],
|
|
66
|
+
handlerFilePattern: /handler|controller|route/i,
|
|
67
|
+
violationPatterns: [
|
|
68
|
+
{
|
|
69
|
+
pattern: /require\s*\(\s*['"].*[Rr]epository/,
|
|
70
|
+
message: 'Handler requires Repository directly. Use a Service instead.',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
pattern: /import\s+.*Repository/,
|
|
74
|
+
message: 'Handler imports Repository directly. Use a Service instead.',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
pattern: /new\s+\w+Repository\s*\(/,
|
|
78
|
+
message: 'Handler instantiates Repository directly.',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
pattern: /\w+Repository\.\w+\s*\(/,
|
|
82
|
+
message: 'Handler calls Repository method directly. Delegate to a Service.',
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
// ── Ruby ────────────────────────────────────────────────────────────────────
|
|
87
|
+
{
|
|
88
|
+
language: 'ruby',
|
|
89
|
+
extensions: ['.rb'],
|
|
90
|
+
handlerDirs: ['app/handlers', 'app/controllers'],
|
|
91
|
+
handlerFilePattern: /handler|controller/i,
|
|
92
|
+
violationPatterns: [
|
|
93
|
+
{
|
|
94
|
+
pattern: /Repository\./,
|
|
95
|
+
message: 'Handler calls Repository directly. Route through a Service.',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
pattern: /Repository\.new/,
|
|
99
|
+
message: 'Handler instantiates Repository directly.',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
pattern: /\w+Repository\.find|\.create|\.save|\.update|\.destroy/,
|
|
103
|
+
message: 'Handler calls Repository data method directly.',
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
// ── Rust ────────────────────────────────────────────────────────────────────
|
|
108
|
+
{
|
|
109
|
+
language: 'rust',
|
|
110
|
+
extensions: ['.rs'],
|
|
111
|
+
handlerDirs: ['handlers', 'routes', 'api'],
|
|
112
|
+
handlerFilePattern: /handler|controller|route/i,
|
|
113
|
+
violationPatterns: [
|
|
114
|
+
{
|
|
115
|
+
pattern: /use\s+crate::(db|repository|repositories)::/,
|
|
116
|
+
message: 'Handler imports from repository/db module. Use a Service layer instead.',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
pattern: /\w+Repository\s*::\s*new/,
|
|
120
|
+
message: 'Handler instantiates Repository directly.',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
pattern: /\w+_repo\.\w+\s*\(/,
|
|
124
|
+
message: 'Handler calls repository method directly. Delegate to a service.',
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
// ── Python ──────────────────────────────────────────────────────────────────
|
|
129
|
+
{
|
|
130
|
+
language: 'python',
|
|
131
|
+
extensions: ['.py'],
|
|
132
|
+
handlerDirs: ['handlers', 'controllers', 'routes', 'api', 'views'],
|
|
133
|
+
handlerFilePattern: /handler|controller|route|view/i,
|
|
134
|
+
violationPatterns: [
|
|
135
|
+
{
|
|
136
|
+
pattern: /from\s+.*repository.*\s+import|import\s+.*Repository/i,
|
|
137
|
+
message: 'Handler imports Repository directly. Use a Service instead.',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
pattern: /Repository\(\)/,
|
|
141
|
+
message: 'Handler instantiates Repository directly.',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
pattern: /\w+_repository\.\w+\s*\(/,
|
|
145
|
+
message: 'Handler calls repository method directly.',
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
// ── Go ──────────────────────────────────────────────────────────────────────
|
|
150
|
+
{
|
|
151
|
+
language: 'go',
|
|
152
|
+
extensions: ['.go'],
|
|
153
|
+
handlerDirs: ['handlers', 'api', 'http', 'controller'],
|
|
154
|
+
handlerFilePattern: /handler|controller/i,
|
|
155
|
+
violationPatterns: [
|
|
156
|
+
{
|
|
157
|
+
pattern: /repository\.\w+/,
|
|
158
|
+
message: 'Handler accesses repository package directly. Use a service layer.',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
pattern: /New\w*Repository\s*\(/,
|
|
162
|
+
message: 'Handler instantiates Repository directly.',
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
// ── PHP ─────────────────────────────────────────────────────────────────────
|
|
167
|
+
{
|
|
168
|
+
language: 'php',
|
|
169
|
+
extensions: ['.php'],
|
|
170
|
+
handlerDirs: ['app/Http/Controllers', 'handlers', 'controllers'],
|
|
171
|
+
handlerFilePattern: /Handler|Controller/i,
|
|
172
|
+
violationPatterns: [
|
|
173
|
+
{
|
|
174
|
+
pattern: /use\s+\w+\\Repositories\\/,
|
|
175
|
+
message: 'Handler imports Repository namespace directly. Use a Service.',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
pattern: /new\s+\w+Repository\s*\(/,
|
|
179
|
+
message: 'Handler instantiates Repository directly.',
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
pattern: /\$\w+Repository->/,
|
|
183
|
+
message: 'Handler accesses repository object directly.',
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
|
+
];
|
|
188
|
+
|
|
189
|
+
export class LayeredDesignLinter {
|
|
190
|
+
private readonly maxDepth: number;
|
|
191
|
+
|
|
192
|
+
constructor(maxDepth = 6) {
|
|
193
|
+
this.maxDepth = maxDepth;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Lints a project directory for Handler→Repository violations.
|
|
198
|
+
*
|
|
199
|
+
* Detects language automatically or uses provided language.
|
|
200
|
+
* Scans all handler files in the project and checks for Repository imports/calls.
|
|
201
|
+
*
|
|
202
|
+
* @param projectPath - Absolute path to the project root.
|
|
203
|
+
* @param language - Optional language override (default: auto-detect).
|
|
204
|
+
* @returns LinterResult with violations and metadata.
|
|
205
|
+
*/
|
|
206
|
+
lint(projectPath: string, language?: Language): LinterResult {
|
|
207
|
+
const detectedLanguage = language ?? detectLanguage(projectPath);
|
|
208
|
+
const rules = LANGUAGE_RULES.filter(
|
|
209
|
+
(r) => r.language === detectedLanguage || detectedLanguage === 'unknown'
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// If unknown language, use all rules
|
|
213
|
+
const effectiveRules = rules.length > 0 ? rules : LANGUAGE_RULES;
|
|
214
|
+
|
|
215
|
+
const violations: LinterViolation[] = [];
|
|
216
|
+
let filesScanned = 0;
|
|
217
|
+
|
|
218
|
+
for (const rule of effectiveRules) {
|
|
219
|
+
const handlerFiles = this.findHandlerFiles(projectPath, rule);
|
|
220
|
+
filesScanned += handlerFiles.length;
|
|
221
|
+
|
|
222
|
+
for (const file of handlerFiles) {
|
|
223
|
+
const fileViolations = this.checkFile(file, rule);
|
|
224
|
+
violations.push(...fileViolations);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
violations,
|
|
230
|
+
filesScanned,
|
|
231
|
+
pass: violations.length === 0,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Lints a single file against all applicable rules.
|
|
237
|
+
* Useful for editor integrations and CI checks on specific files.
|
|
238
|
+
*/
|
|
239
|
+
lintFile(filePath: string, language?: Language): LinterResult {
|
|
240
|
+
const ext = path.extname(filePath);
|
|
241
|
+
const detectedLanguage = language ?? extensionToLanguage(ext);
|
|
242
|
+
|
|
243
|
+
const rules = LANGUAGE_RULES.filter(
|
|
244
|
+
(r) => r.language === detectedLanguage
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const violations: LinterViolation[] = [];
|
|
248
|
+
|
|
249
|
+
for (const rule of rules) {
|
|
250
|
+
// Only check handler files
|
|
251
|
+
if (!isHandlerFile(filePath, rule)) continue;
|
|
252
|
+
violations.push(...this.checkFile(filePath, rule));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
violations,
|
|
257
|
+
filesScanned: 1,
|
|
258
|
+
pass: violations.length === 0,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ── Private helpers ─────────────────────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
private findHandlerFiles(projectPath: string, rule: LanguageRule): string[] {
|
|
265
|
+
const results: string[] = [];
|
|
266
|
+
|
|
267
|
+
// Search in designated handler directories
|
|
268
|
+
for (const handlerDir of rule.handlerDirs) {
|
|
269
|
+
const dirPath = path.join(projectPath, handlerDir);
|
|
270
|
+
if (fs.existsSync(dirPath)) {
|
|
271
|
+
this.collectFiles(dirPath, rule.extensions, results, 0);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Also search within src/, app/, lib/
|
|
275
|
+
for (const srcRoot of ['src', 'app', 'lib']) {
|
|
276
|
+
const nestedDir = path.join(projectPath, srcRoot, handlerDir);
|
|
277
|
+
if (fs.existsSync(nestedDir)) {
|
|
278
|
+
this.collectFiles(nestedDir, rule.extensions, results, 0);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Also find files with handler patterns in top-level source dirs
|
|
284
|
+
for (const srcRoot of ['src', 'app', 'lib', 'cmd']) {
|
|
285
|
+
const srcPath = path.join(projectPath, srcRoot);
|
|
286
|
+
if (fs.existsSync(srcPath)) {
|
|
287
|
+
this.collectHandlerFilesByName(srcPath, rule, results, 0);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Deduplicate
|
|
292
|
+
return [...new Set(results)];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private collectFiles(
|
|
296
|
+
dir: string,
|
|
297
|
+
extensions: string[],
|
|
298
|
+
results: string[],
|
|
299
|
+
depth: number
|
|
300
|
+
): void {
|
|
301
|
+
if (depth > this.maxDepth) return;
|
|
302
|
+
|
|
303
|
+
let entries: fs.Dirent[];
|
|
304
|
+
try {
|
|
305
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
306
|
+
} catch {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
for (const entry of entries) {
|
|
311
|
+
if (entry.name.startsWith('.') || SKIP_DIRS.has(entry.name)) continue;
|
|
312
|
+
|
|
313
|
+
const fullPath = path.join(dir, entry.name);
|
|
314
|
+
if (entry.isDirectory()) {
|
|
315
|
+
this.collectFiles(fullPath, extensions, results, depth + 1);
|
|
316
|
+
} else if (entry.isFile() && extensions.includes(path.extname(entry.name))) {
|
|
317
|
+
results.push(fullPath);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private collectHandlerFilesByName(
|
|
323
|
+
dir: string,
|
|
324
|
+
rule: LanguageRule,
|
|
325
|
+
results: string[],
|
|
326
|
+
depth: number
|
|
327
|
+
): void {
|
|
328
|
+
if (depth > this.maxDepth) return;
|
|
329
|
+
|
|
330
|
+
let entries: fs.Dirent[];
|
|
331
|
+
try {
|
|
332
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
333
|
+
} catch {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
for (const entry of entries) {
|
|
338
|
+
if (entry.name.startsWith('.') || SKIP_DIRS.has(entry.name)) continue;
|
|
339
|
+
|
|
340
|
+
const fullPath = path.join(dir, entry.name);
|
|
341
|
+
if (entry.isDirectory()) {
|
|
342
|
+
this.collectHandlerFilesByName(fullPath, rule, results, depth + 1);
|
|
343
|
+
} else if (
|
|
344
|
+
entry.isFile() &&
|
|
345
|
+
rule.extensions.includes(path.extname(entry.name)) &&
|
|
346
|
+
rule.handlerFilePattern.test(entry.name)
|
|
347
|
+
) {
|
|
348
|
+
results.push(fullPath);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private checkFile(filePath: string, rule: LanguageRule): LinterViolation[] {
|
|
354
|
+
let content: string;
|
|
355
|
+
try {
|
|
356
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
357
|
+
} catch {
|
|
358
|
+
return [];
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const violations: LinterViolation[] = [];
|
|
362
|
+
const lines = content.split('\n');
|
|
363
|
+
|
|
364
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
365
|
+
const line = lines[lineNum];
|
|
366
|
+
|
|
367
|
+
// Skip comments
|
|
368
|
+
const trimmed = line.trim();
|
|
369
|
+
if (
|
|
370
|
+
trimmed.startsWith('//') ||
|
|
371
|
+
trimmed.startsWith('#') ||
|
|
372
|
+
trimmed.startsWith('*') ||
|
|
373
|
+
trimmed.startsWith('/*')
|
|
374
|
+
) {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
for (const { pattern, message } of rule.violationPatterns) {
|
|
379
|
+
if (pattern.test(line)) {
|
|
380
|
+
violations.push({
|
|
381
|
+
file: filePath,
|
|
382
|
+
line: lineNum + 1,
|
|
383
|
+
content: line.trim(),
|
|
384
|
+
rule: `handler-repository-direct-call`,
|
|
385
|
+
message,
|
|
386
|
+
});
|
|
387
|
+
break; // one violation per line
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return violations;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// ── Utility functions ────────────────────────────────────────────────────────
|
|
397
|
+
|
|
398
|
+
const SKIP_DIRS = new Set([
|
|
399
|
+
'node_modules',
|
|
400
|
+
'target',
|
|
401
|
+
'dist',
|
|
402
|
+
'.git',
|
|
403
|
+
'vendor',
|
|
404
|
+
'__pycache__',
|
|
405
|
+
'.venv',
|
|
406
|
+
'venv',
|
|
407
|
+
'coverage',
|
|
408
|
+
'.cache',
|
|
409
|
+
]);
|
|
410
|
+
|
|
411
|
+
function isHandlerFile(filePath: string, rule: LanguageRule): boolean {
|
|
412
|
+
return rule.handlerFilePattern.test(path.basename(filePath));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function extensionToLanguage(ext: string): Language {
|
|
416
|
+
switch (ext) {
|
|
417
|
+
case '.ts':
|
|
418
|
+
case '.tsx':
|
|
419
|
+
return 'typescript';
|
|
420
|
+
case '.js':
|
|
421
|
+
case '.mjs':
|
|
422
|
+
case '.cjs':
|
|
423
|
+
return 'javascript';
|
|
424
|
+
case '.rb':
|
|
425
|
+
return 'ruby';
|
|
426
|
+
case '.rs':
|
|
427
|
+
return 'rust';
|
|
428
|
+
case '.py':
|
|
429
|
+
return 'python';
|
|
430
|
+
case '.go':
|
|
431
|
+
return 'go';
|
|
432
|
+
case '.php':
|
|
433
|
+
return 'php';
|
|
434
|
+
default:
|
|
435
|
+
return 'unknown';
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function detectLanguage(projectPath: string): Language {
|
|
440
|
+
const indicators: Array<[string, Language]> = [
|
|
441
|
+
['tsconfig.json', 'typescript'],
|
|
442
|
+
['package.json', 'javascript'], // checked after tsconfig
|
|
443
|
+
['Gemfile', 'ruby'],
|
|
444
|
+
['Cargo.toml', 'rust'],
|
|
445
|
+
['requirements.txt', 'python'],
|
|
446
|
+
['pyproject.toml', 'python'],
|
|
447
|
+
['go.mod', 'go'],
|
|
448
|
+
['composer.json', 'php'],
|
|
449
|
+
];
|
|
450
|
+
|
|
451
|
+
for (const [file, lang] of indicators) {
|
|
452
|
+
if (fs.existsSync(path.join(projectPath, file))) {
|
|
453
|
+
// For package.json, check if tsconfig also exists (TypeScript wins)
|
|
454
|
+
if (lang === 'javascript' && fs.existsSync(path.join(projectPath, 'tsconfig.json'))) {
|
|
455
|
+
return 'typescript';
|
|
456
|
+
}
|
|
457
|
+
return lang;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return 'unknown';
|
|
462
|
+
}
|