@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,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dare-layered-design — metrics tests (M-01 to M-04)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { LayeredDesignMetrics } from '../metrics.js';
|
|
10
|
+
|
|
11
|
+
describe('LayeredDesignMetrics', () => {
|
|
12
|
+
let tmpDir: string;
|
|
13
|
+
let metrics: LayeredDesignMetrics;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dare-ld-metrics-test-'));
|
|
17
|
+
metrics = new LayeredDesignMetrics();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// ── M-01 ──────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
describe('M-01: Services have unit tests', () => {
|
|
27
|
+
it('passes when every service has a spec file', () => {
|
|
28
|
+
const servicesDir = path.join(tmpDir, 'src', 'services');
|
|
29
|
+
const testsDir = path.join(tmpDir, 'tests');
|
|
30
|
+
fs.mkdirSync(servicesDir, { recursive: true });
|
|
31
|
+
fs.mkdirSync(testsDir, { recursive: true });
|
|
32
|
+
|
|
33
|
+
fs.writeFileSync(path.join(servicesDir, 'create_user_service.ts'), 'export class CreateUserService {}', 'utf-8');
|
|
34
|
+
fs.writeFileSync(path.join(testsDir, 'create_user_service.spec.ts'), 'describe("CreateUserService", () => {});', 'utf-8');
|
|
35
|
+
|
|
36
|
+
const result = metrics.collectM01(tmpDir);
|
|
37
|
+
|
|
38
|
+
expect(result.id).toBe('M-01');
|
|
39
|
+
expect(result.pass).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('fails when a service has no test file', () => {
|
|
43
|
+
const servicesDir = path.join(tmpDir, 'src', 'services');
|
|
44
|
+
fs.mkdirSync(servicesDir, { recursive: true });
|
|
45
|
+
fs.writeFileSync(
|
|
46
|
+
path.join(servicesDir, 'create_order_service.ts'),
|
|
47
|
+
'export class CreateOrderService {}',
|
|
48
|
+
'utf-8'
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const result = metrics.collectM01(tmpDir);
|
|
52
|
+
|
|
53
|
+
expect(result.pass).toBe(false);
|
|
54
|
+
expect(result.detail).toContain('create_order_service');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('fails when no services directory exists', () => {
|
|
58
|
+
const result = metrics.collectM01(tmpDir);
|
|
59
|
+
|
|
60
|
+
expect(result.pass).toBe(false);
|
|
61
|
+
expect(result.detail).toContain('No service files found');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('passes with co-located test file', () => {
|
|
65
|
+
const servicesDir = path.join(tmpDir, 'src', 'services');
|
|
66
|
+
fs.mkdirSync(servicesDir, { recursive: true });
|
|
67
|
+
|
|
68
|
+
fs.writeFileSync(
|
|
69
|
+
path.join(servicesDir, 'send_email_service.ts'),
|
|
70
|
+
'export class SendEmailService {}',
|
|
71
|
+
'utf-8'
|
|
72
|
+
);
|
|
73
|
+
// Co-located spec
|
|
74
|
+
fs.writeFileSync(
|
|
75
|
+
path.join(servicesDir, 'send_email_service.spec.ts'),
|
|
76
|
+
'describe("SendEmailService", () => {});',
|
|
77
|
+
'utf-8'
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const result = metrics.collectM01(tmpDir);
|
|
81
|
+
expect(result.pass).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// ── M-02 ──────────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
describe('M-02: 0% Handler→Repository calls', () => {
|
|
88
|
+
it('passes when no handler files exist', () => {
|
|
89
|
+
const result = metrics.collectM02(tmpDir);
|
|
90
|
+
|
|
91
|
+
expect(result.id).toBe('M-02');
|
|
92
|
+
expect(result.pass).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('passes when handlers only import services', () => {
|
|
96
|
+
const handlersDir = path.join(tmpDir, 'src', 'handlers');
|
|
97
|
+
fs.mkdirSync(handlersDir, { recursive: true });
|
|
98
|
+
fs.writeFileSync(
|
|
99
|
+
path.join(handlersDir, 'user_handler.ts'),
|
|
100
|
+
`import { CreateUserService } from '../services/create_user_service';
|
|
101
|
+
export class UserHandler {
|
|
102
|
+
constructor(private service: CreateUserService) {}
|
|
103
|
+
async create(req: any, res: any) {
|
|
104
|
+
const user = await this.service.execute(req.body);
|
|
105
|
+
res.json(user);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
`,
|
|
109
|
+
'utf-8'
|
|
110
|
+
);
|
|
111
|
+
fs.writeFileSync(path.join(tmpDir, 'tsconfig.json'), '{}', 'utf-8');
|
|
112
|
+
|
|
113
|
+
const result = metrics.collectM02(tmpDir);
|
|
114
|
+
expect(result.pass).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('fails when handler imports Repository', () => {
|
|
118
|
+
const handlersDir = path.join(tmpDir, 'src', 'handlers');
|
|
119
|
+
fs.mkdirSync(handlersDir, { recursive: true });
|
|
120
|
+
fs.writeFileSync(
|
|
121
|
+
path.join(handlersDir, 'user_handler.ts'),
|
|
122
|
+
`import { UserRepository } from '../repositories/user_repository';
|
|
123
|
+
export class UserHandler {
|
|
124
|
+
async get(req: any, res: any) {
|
|
125
|
+
const repo = new UserRepository();
|
|
126
|
+
res.json(await repo.findById(req.params.id));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
`,
|
|
130
|
+
'utf-8'
|
|
131
|
+
);
|
|
132
|
+
fs.writeFileSync(path.join(tmpDir, 'tsconfig.json'), '{}', 'utf-8');
|
|
133
|
+
|
|
134
|
+
const result = metrics.collectM02(tmpDir);
|
|
135
|
+
expect(result.pass).toBe(false);
|
|
136
|
+
expect(result.detail).toContain('violation');
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// ── M-03 ──────────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
describe('M-03: Handlers use dependency injection', () => {
|
|
143
|
+
it('passes when no handlers exist', () => {
|
|
144
|
+
const result = metrics.collectM03(tmpDir);
|
|
145
|
+
|
|
146
|
+
expect(result.id).toBe('M-03');
|
|
147
|
+
expect(result.pass).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('passes when handler receives service via constructor', () => {
|
|
151
|
+
const handlersDir = path.join(tmpDir, 'src', 'handlers');
|
|
152
|
+
fs.mkdirSync(handlersDir, { recursive: true });
|
|
153
|
+
fs.writeFileSync(
|
|
154
|
+
path.join(handlersDir, 'order_handler.ts'),
|
|
155
|
+
`export class OrderHandler {
|
|
156
|
+
constructor(private readonly createOrderService: CreateOrderService) {}
|
|
157
|
+
async create(req: any, res: any) {
|
|
158
|
+
const order = await this.createOrderService.execute(req.body);
|
|
159
|
+
res.json(order);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
`,
|
|
163
|
+
'utf-8'
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const result = metrics.collectM03(tmpDir);
|
|
167
|
+
expect(result.pass).toBe(true);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('fails when handler instantiates service with new', () => {
|
|
171
|
+
const handlersDir = path.join(tmpDir, 'src', 'handlers');
|
|
172
|
+
fs.mkdirSync(handlersDir, { recursive: true });
|
|
173
|
+
fs.writeFileSync(
|
|
174
|
+
path.join(handlersDir, 'product_handler.ts'),
|
|
175
|
+
`export class ProductHandler {
|
|
176
|
+
async create(req: any, res: any) {
|
|
177
|
+
const service = new CreateProductService(); // VIOLATION
|
|
178
|
+
const product = await service.execute(req.body);
|
|
179
|
+
res.json(product);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
`,
|
|
183
|
+
'utf-8'
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const result = metrics.collectM03(tmpDir);
|
|
187
|
+
expect(result.pass).toBe(false);
|
|
188
|
+
expect(result.detail).toContain('new');
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// ── M-04 ──────────────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
describe('M-04: Repositories agnostic to upper layers', () => {
|
|
195
|
+
it('passes when no repository directories exist', () => {
|
|
196
|
+
const result = metrics.collectM04(tmpDir);
|
|
197
|
+
|
|
198
|
+
expect(result.id).toBe('M-04');
|
|
199
|
+
expect(result.pass).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('passes when repository has no HTTP concerns', () => {
|
|
203
|
+
const reposDir = path.join(tmpDir, 'src', 'repositories');
|
|
204
|
+
fs.mkdirSync(reposDir, { recursive: true });
|
|
205
|
+
fs.writeFileSync(
|
|
206
|
+
path.join(reposDir, 'user_repository.ts'),
|
|
207
|
+
`export class UserRepository {
|
|
208
|
+
async findById(id: string) {
|
|
209
|
+
return this.db.query('SELECT * FROM users WHERE id = $1', [id]);
|
|
210
|
+
}
|
|
211
|
+
async save(user: any) {
|
|
212
|
+
return this.db.query('INSERT INTO users ...', [user.id, user.email]);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
`,
|
|
216
|
+
'utf-8'
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
const result = metrics.collectM04(tmpDir);
|
|
220
|
+
expect(result.pass).toBe(true);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('fails when repository returns HTTP status code', () => {
|
|
224
|
+
const reposDir = path.join(tmpDir, 'src', 'repositories');
|
|
225
|
+
fs.mkdirSync(reposDir, { recursive: true });
|
|
226
|
+
fs.writeFileSync(
|
|
227
|
+
path.join(reposDir, 'order_repository.ts'),
|
|
228
|
+
`export class OrderRepository {
|
|
229
|
+
async findById(id: string) {
|
|
230
|
+
const order = await this.db.query('SELECT * FROM orders WHERE id = $1', [id]);
|
|
231
|
+
if (!order) {
|
|
232
|
+
return { status: 404, error: 'Not found' }; // VIOLATION — repo should not know about HTTP
|
|
233
|
+
}
|
|
234
|
+
return order;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
`,
|
|
238
|
+
'utf-8'
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const result = metrics.collectM04(tmpDir);
|
|
242
|
+
expect(result.pass).toBe(false);
|
|
243
|
+
expect(result.detail).toContain('HTTP');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('fails when repository throws NestJS HttpException', () => {
|
|
247
|
+
const reposDir = path.join(tmpDir, 'src', 'repositories');
|
|
248
|
+
fs.mkdirSync(reposDir, { recursive: true });
|
|
249
|
+
fs.writeFileSync(
|
|
250
|
+
path.join(reposDir, 'user_repository.ts'),
|
|
251
|
+
`import { HttpException } from '@nestjs/common';
|
|
252
|
+
export class UserRepository {
|
|
253
|
+
async findById(id: string) {
|
|
254
|
+
const user = await this.db.find(id);
|
|
255
|
+
if (!user) throw new HttpException('Not found', 404); // VIOLATION
|
|
256
|
+
return user;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
`,
|
|
260
|
+
'utf-8'
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
const result = metrics.collectM04(tmpDir);
|
|
264
|
+
expect(result.pass).toBe(false);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// ── collect() ─────────────────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
describe('collect()', () => {
|
|
271
|
+
it('returns 4 metric results', () => {
|
|
272
|
+
const results = metrics.collect(tmpDir);
|
|
273
|
+
expect(results).toHaveLength(4);
|
|
274
|
+
expect(results.map((r) => r.id)).toEqual(['M-01', 'M-02', 'M-03', 'M-04']);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('M-02, M-03, M-04 pass for empty project (no files to scan)', () => {
|
|
278
|
+
const results = metrics.collect(tmpDir);
|
|
279
|
+
|
|
280
|
+
// M-01 fails (no services), M-02/M-03/M-04 pass (nothing to scan)
|
|
281
|
+
expect(results.find((r) => r.id === 'M-02')!.pass).toBe(true);
|
|
282
|
+
expect(results.find((r) => r.id === 'M-03')!.pass).toBe(true);
|
|
283
|
+
expect(results.find((r) => r.id === 'M-04')!.pass).toBe(true);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"rootDir": ".",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"sourceMap": true
|
|
9
|
+
},
|
|
10
|
+
"include": [
|
|
11
|
+
"*.ts",
|
|
12
|
+
"tests/**/*.ts"
|
|
13
|
+
],
|
|
14
|
+
"exclude": [
|
|
15
|
+
"node_modules",
|
|
16
|
+
"dist"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dare-layered-design — shared types
|
|
3
|
+
* License: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type Language = 'typescript' | 'javascript' | 'ruby' | 'rust' | 'python' | 'go' | 'php' | 'unknown';
|
|
7
|
+
|
|
8
|
+
export interface LinterViolation {
|
|
9
|
+
/** File where violation was found */
|
|
10
|
+
file: string;
|
|
11
|
+
/** 1-based line number */
|
|
12
|
+
line: number;
|
|
13
|
+
/** The violating line content */
|
|
14
|
+
content: string;
|
|
15
|
+
/** Violation rule ID */
|
|
16
|
+
rule: string;
|
|
17
|
+
/** Human-readable message */
|
|
18
|
+
message: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LinterResult {
|
|
22
|
+
violations: LinterViolation[];
|
|
23
|
+
filesScanned: number;
|
|
24
|
+
pass: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ScaffoldOptions {
|
|
28
|
+
/** Root source directory (default: 'src') */
|
|
29
|
+
srcDir?: string;
|
|
30
|
+
/** Language (affects file extensions and naming conventions) */
|
|
31
|
+
language?: Language;
|
|
32
|
+
/** Whether to create example files (default: false — only .gitkeep) */
|
|
33
|
+
withExamples?: boolean;
|
|
34
|
+
/** Entity name for example files (default: 'example') */
|
|
35
|
+
exampleEntity?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ScaffoldResult {
|
|
39
|
+
createdDirs: string[];
|
|
40
|
+
createdFiles: string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface MetricResult {
|
|
44
|
+
id: string;
|
|
45
|
+
pass: boolean;
|
|
46
|
+
description: string;
|
|
47
|
+
detail?: string;
|
|
48
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dare-llm-integration — LLMCache
|
|
3
|
+
* In-memory cache with TTL per key.
|
|
4
|
+
* Key: hash(model + prompt)
|
|
5
|
+
* License: MIT
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { CacheEntry, CompletionResponse, EmbeddingResponse } from '../types.js';
|
|
9
|
+
|
|
10
|
+
export class LLMCache {
|
|
11
|
+
private readonly store: Map<string, CacheEntry> = new Map();
|
|
12
|
+
private _hits = 0;
|
|
13
|
+
private _misses = 0;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Retrieve a cache entry. Returns null if missing or expired.
|
|
17
|
+
*/
|
|
18
|
+
get(key: string): CacheEntry | null {
|
|
19
|
+
const entry = this.store.get(key);
|
|
20
|
+
if (!entry) {
|
|
21
|
+
this._misses++;
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (Date.now() > entry.expiresAt) {
|
|
26
|
+
this.store.delete(key);
|
|
27
|
+
this._misses++;
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Increment hit counter on the entry
|
|
32
|
+
entry.hits++;
|
|
33
|
+
this._hits++;
|
|
34
|
+
return entry;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Store a value with TTL in milliseconds.
|
|
39
|
+
*/
|
|
40
|
+
set(key: string, value: CompletionResponse | EmbeddingResponse, ttlMs: number): void {
|
|
41
|
+
const now = Date.now();
|
|
42
|
+
this.store.set(key, {
|
|
43
|
+
value,
|
|
44
|
+
expiresAt: now + ttlMs,
|
|
45
|
+
createdAt: now,
|
|
46
|
+
hits: 0,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Remove a specific key.
|
|
52
|
+
*/
|
|
53
|
+
invalidate(key: string): void {
|
|
54
|
+
this.store.delete(key);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Remove all entries.
|
|
59
|
+
*/
|
|
60
|
+
clear(): void {
|
|
61
|
+
this.store.clear();
|
|
62
|
+
this._hits = 0;
|
|
63
|
+
this._misses = 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Number of entries currently stored (including potentially stale).
|
|
68
|
+
*/
|
|
69
|
+
get size(): number {
|
|
70
|
+
return this.store.size;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Cache hit count since last clear().
|
|
75
|
+
*/
|
|
76
|
+
get hits(): number {
|
|
77
|
+
return this._hits;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Cache miss count since last clear().
|
|
82
|
+
*/
|
|
83
|
+
get misses(): number {
|
|
84
|
+
return this._misses;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Hit rate as a fraction (0–1). Returns 0 if no requests yet.
|
|
89
|
+
*/
|
|
90
|
+
get hitRate(): number {
|
|
91
|
+
const total = this._hits + this._misses;
|
|
92
|
+
return total === 0 ? 0 : this._hits / total;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Remove all expired entries.
|
|
97
|
+
*/
|
|
98
|
+
purgeExpired(): number {
|
|
99
|
+
const now = Date.now();
|
|
100
|
+
let purged = 0;
|
|
101
|
+
for (const [key, entry] of this.store.entries()) {
|
|
102
|
+
if (now > entry.expiresAt) {
|
|
103
|
+
this.store.delete(key);
|
|
104
|
+
purged++;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return purged;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if key exists and is not expired.
|
|
112
|
+
*/
|
|
113
|
+
has(key: string): boolean {
|
|
114
|
+
const entry = this.store.get(key);
|
|
115
|
+
if (!entry) return false;
|
|
116
|
+
if (Date.now() > entry.expiresAt) {
|
|
117
|
+
this.store.delete(key);
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dare-llm-integration — public API
|
|
3
|
+
* License: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Types
|
|
7
|
+
export type {
|
|
8
|
+
CompletionRequest,
|
|
9
|
+
CompletionResponse,
|
|
10
|
+
EmbedRequest,
|
|
11
|
+
EmbeddingResponse,
|
|
12
|
+
LLMProvider,
|
|
13
|
+
PromptTemplate,
|
|
14
|
+
CacheEntry,
|
|
15
|
+
RateLimiterConfig,
|
|
16
|
+
OutputValidationResult,
|
|
17
|
+
OutputValidationError,
|
|
18
|
+
MetricResult,
|
|
19
|
+
LLMUsageStats,
|
|
20
|
+
} from './types.js';
|
|
21
|
+
|
|
22
|
+
// Providers
|
|
23
|
+
export { OpenAIProvider } from './providers/openai_provider.js';
|
|
24
|
+
export type { OpenAIProviderConfig } from './providers/openai_provider.js';
|
|
25
|
+
|
|
26
|
+
export { AnthropicProvider } from './providers/anthropic_provider.js';
|
|
27
|
+
export type { AnthropicProviderConfig } from './providers/anthropic_provider.js';
|
|
28
|
+
|
|
29
|
+
export { DummyProvider } from './providers/dummy_provider.js';
|
|
30
|
+
export type { DummyProviderConfig } from './providers/dummy_provider.js';
|
|
31
|
+
|
|
32
|
+
// Cache
|
|
33
|
+
export { LLMCache } from './cache/llm_cache.js';
|
|
34
|
+
|
|
35
|
+
// Rate limit
|
|
36
|
+
export { TokenBucket } from './rate_limit/token_bucket.js';
|
|
37
|
+
export type { TokenBucketConfig } from './rate_limit/token_bucket.js';
|
|
38
|
+
|
|
39
|
+
// Prompts
|
|
40
|
+
export { PromptLoader } from './prompts/prompt_loader.js';
|
|
41
|
+
export type { PromptLoaderConfig } from './prompts/prompt_loader.js';
|
|
42
|
+
|
|
43
|
+
// Validators
|
|
44
|
+
export { OutputValidator } from './validators/output_validator.js';
|
|
45
|
+
export type { JsonSchema } from './validators/output_validator.js';
|
|
46
|
+
|
|
47
|
+
// Metrics
|
|
48
|
+
export { collectLLMIntegrationMetrics } from './metrics.js';
|
|
49
|
+
export type { LLMIntegrationMetricsInput } from './metrics.js';
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dare-llm-integration — Metrics collector
|
|
3
|
+
* Evaluates M-01 to M-04 for the dare-llm-integration skill.
|
|
4
|
+
* License: MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { MetricResult } from './types.js';
|
|
8
|
+
import type { LLMCache } from './cache/llm_cache.js';
|
|
9
|
+
import type { TokenBucket } from './rate_limit/token_bucket.js';
|
|
10
|
+
|
|
11
|
+
export interface LLMIntegrationMetricsInput {
|
|
12
|
+
/** M-01: total LLM calls made through LLMProvider */
|
|
13
|
+
totalCallsViaProvider: number;
|
|
14
|
+
/** M-01: total LLM calls made directly (bypassing LLMProvider) */
|
|
15
|
+
totalDirectCalls: number;
|
|
16
|
+
/** M-02: cache instance used (null = cache not configured) */
|
|
17
|
+
cache: LLMCache | null;
|
|
18
|
+
/** M-03: rate limiter instance used (null = not configured) */
|
|
19
|
+
rateLimiter: TokenBucket | null;
|
|
20
|
+
/** M-04: count of responses that went through output validation */
|
|
21
|
+
validatedResponseCount: number;
|
|
22
|
+
/** M-04: count of responses that did NOT go through output validation */
|
|
23
|
+
unvalidatedResponseCount: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function collectLLMIntegrationMetrics(input: LLMIntegrationMetricsInput): MetricResult[] {
|
|
27
|
+
return [
|
|
28
|
+
checkM01(input),
|
|
29
|
+
checkM02(input),
|
|
30
|
+
checkM03(input),
|
|
31
|
+
checkM04(input),
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* M-01: 100% of LLM calls come via LLMProvider injected (no direct SDK calls).
|
|
37
|
+
*/
|
|
38
|
+
function checkM01(input: LLMIntegrationMetricsInput): MetricResult {
|
|
39
|
+
const total = input.totalCallsViaProvider + input.totalDirectCalls;
|
|
40
|
+
const pass = total > 0
|
|
41
|
+
? input.totalDirectCalls === 0
|
|
42
|
+
: true; // if no calls at all, trivially pass
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
id: 'M-01',
|
|
46
|
+
pass,
|
|
47
|
+
description: '100% of LLM calls via LLMProvider (no direct SDK calls)',
|
|
48
|
+
detail: total === 0
|
|
49
|
+
? 'No LLM calls recorded'
|
|
50
|
+
: pass
|
|
51
|
+
? `${input.totalCallsViaProvider}/${total} calls via provider`
|
|
52
|
+
: `${input.totalDirectCalls} direct call(s) detected — use LLMProvider`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* M-02: Cache is configured and has at least one hit (or is set up for caching).
|
|
58
|
+
*/
|
|
59
|
+
function checkM02(input: LLMIntegrationMetricsInput): MetricResult {
|
|
60
|
+
const pass = input.cache !== null;
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
id: 'M-02',
|
|
64
|
+
pass,
|
|
65
|
+
description: '100% of LLM responses are cached (cache configured)',
|
|
66
|
+
detail: pass
|
|
67
|
+
? `Cache configured — hit rate: ${(input.cache!.hitRate * 100).toFixed(1)}% (${input.cache!.hits} hits / ${input.cache!.hits + input.cache!.misses} total)`
|
|
68
|
+
: 'No cache instance provided — configure LLMCache to reduce costs',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* M-03: Rate limiter is configured.
|
|
74
|
+
*/
|
|
75
|
+
function checkM03(input: LLMIntegrationMetricsInput): MetricResult {
|
|
76
|
+
const pass = input.rateLimiter !== null;
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
id: 'M-03',
|
|
80
|
+
pass,
|
|
81
|
+
description: '100% of LLM requests have rate limit configured',
|
|
82
|
+
detail: pass
|
|
83
|
+
? `Rate limiter configured at ${input.rateLimiter!.requestsPerSecond} req/sec`
|
|
84
|
+
: 'No rate limiter provided — configure TokenBucket to prevent 429s',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* M-04: 100% of LLM responses are validated against schema.
|
|
90
|
+
*/
|
|
91
|
+
function checkM04(input: LLMIntegrationMetricsInput): MetricResult {
|
|
92
|
+
const total = input.validatedResponseCount + input.unvalidatedResponseCount;
|
|
93
|
+
const pass = total > 0
|
|
94
|
+
? input.unvalidatedResponseCount === 0
|
|
95
|
+
: true;
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
id: 'M-04',
|
|
99
|
+
pass,
|
|
100
|
+
description: '100% of LLM responses validated against schema before use',
|
|
101
|
+
detail: total === 0
|
|
102
|
+
? 'No responses recorded'
|
|
103
|
+
: pass
|
|
104
|
+
? `All ${input.validatedResponseCount} response(s) validated`
|
|
105
|
+
: `${input.unvalidatedResponseCount} response(s) used without validation`,
|
|
106
|
+
};
|
|
107
|
+
}
|