wiggum-cli 0.1.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/LICENSE +21 -0
- package/README.md +341 -0
- package/bin/ralph.js +8 -0
- package/dist/ai/enhancer.d.ts +100 -0
- package/dist/ai/enhancer.d.ts.map +1 -0
- package/dist/ai/enhancer.js +233 -0
- package/dist/ai/enhancer.js.map +1 -0
- package/dist/ai/index.d.ts +8 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +11 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/prompts.d.ts +26 -0
- package/dist/ai/prompts.d.ts.map +1 -0
- package/dist/ai/prompts.js +201 -0
- package/dist/ai/prompts.js.map +1 -0
- package/dist/ai/providers.d.ts +35 -0
- package/dist/ai/providers.d.ts.map +1 -0
- package/dist/ai/providers.js +104 -0
- package/dist/ai/providers.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +196 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/init.d.ts +16 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +124 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/monitor.d.ts +17 -0
- package/dist/commands/monitor.d.ts.map +1 -0
- package/dist/commands/monitor.js +342 -0
- package/dist/commands/monitor.js.map +1 -0
- package/dist/commands/new.d.ts +19 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +272 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/run.d.ts +16 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +175 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/generator/config.d.ts +59 -0
- package/dist/generator/config.d.ts.map +1 -0
- package/dist/generator/config.js +68 -0
- package/dist/generator/config.js.map +1 -0
- package/dist/generator/index.d.ts +64 -0
- package/dist/generator/index.d.ts.map +1 -0
- package/dist/generator/index.js +147 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/templates.d.ts +70 -0
- package/dist/generator/templates.d.ts.map +1 -0
- package/dist/generator/templates.js +296 -0
- package/dist/generator/templates.js.map +1 -0
- package/dist/generator/writer.d.ts +93 -0
- package/dist/generator/writer.d.ts.map +1 -0
- package/dist/generator/writer.js +213 -0
- package/dist/generator/writer.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/scanner/detectors/core/framework.d.ts +11 -0
- package/dist/scanner/detectors/core/framework.d.ts.map +1 -0
- package/dist/scanner/detectors/core/framework.js +275 -0
- package/dist/scanner/detectors/core/framework.js.map +1 -0
- package/dist/scanner/detectors/core/packageManager.d.ts +11 -0
- package/dist/scanner/detectors/core/packageManager.d.ts.map +1 -0
- package/dist/scanner/detectors/core/packageManager.js +74 -0
- package/dist/scanner/detectors/core/packageManager.js.map +1 -0
- package/dist/scanner/detectors/core/styling.d.ts +12 -0
- package/dist/scanner/detectors/core/styling.d.ts.map +1 -0
- package/dist/scanner/detectors/core/styling.js +230 -0
- package/dist/scanner/detectors/core/styling.js.map +1 -0
- package/dist/scanner/detectors/core/testing.d.ts +12 -0
- package/dist/scanner/detectors/core/testing.d.ts.map +1 -0
- package/dist/scanner/detectors/core/testing.js +190 -0
- package/dist/scanner/detectors/core/testing.js.map +1 -0
- package/dist/scanner/detectors/data/api.d.ts +12 -0
- package/dist/scanner/detectors/data/api.d.ts.map +1 -0
- package/dist/scanner/detectors/data/api.js +261 -0
- package/dist/scanner/detectors/data/api.js.map +1 -0
- package/dist/scanner/detectors/data/database.d.ts +12 -0
- package/dist/scanner/detectors/data/database.d.ts.map +1 -0
- package/dist/scanner/detectors/data/database.js +213 -0
- package/dist/scanner/detectors/data/database.js.map +1 -0
- package/dist/scanner/detectors/data/orm.d.ts +12 -0
- package/dist/scanner/detectors/data/orm.d.ts.map +1 -0
- package/dist/scanner/detectors/data/orm.js +160 -0
- package/dist/scanner/detectors/data/orm.js.map +1 -0
- package/dist/scanner/detectors/frontend/formHandling.d.ts +12 -0
- package/dist/scanner/detectors/frontend/formHandling.d.ts.map +1 -0
- package/dist/scanner/detectors/frontend/formHandling.js +211 -0
- package/dist/scanner/detectors/frontend/formHandling.js.map +1 -0
- package/dist/scanner/detectors/frontend/stateManagement.d.ts +12 -0
- package/dist/scanner/detectors/frontend/stateManagement.d.ts.map +1 -0
- package/dist/scanner/detectors/frontend/stateManagement.js +221 -0
- package/dist/scanner/detectors/frontend/stateManagement.js.map +1 -0
- package/dist/scanner/detectors/frontend/uiComponents.d.ts +12 -0
- package/dist/scanner/detectors/frontend/uiComponents.d.ts.map +1 -0
- package/dist/scanner/detectors/frontend/uiComponents.js +285 -0
- package/dist/scanner/detectors/frontend/uiComponents.js.map +1 -0
- package/dist/scanner/detectors/infra/deployment.d.ts +12 -0
- package/dist/scanner/detectors/infra/deployment.d.ts.map +1 -0
- package/dist/scanner/detectors/infra/deployment.js +301 -0
- package/dist/scanner/detectors/infra/deployment.js.map +1 -0
- package/dist/scanner/detectors/infra/monorepo.d.ts +12 -0
- package/dist/scanner/detectors/infra/monorepo.d.ts.map +1 -0
- package/dist/scanner/detectors/infra/monorepo.js +219 -0
- package/dist/scanner/detectors/infra/monorepo.js.map +1 -0
- package/dist/scanner/detectors/mcp/mcpProject.d.ts +12 -0
- package/dist/scanner/detectors/mcp/mcpProject.d.ts.map +1 -0
- package/dist/scanner/detectors/mcp/mcpProject.js +154 -0
- package/dist/scanner/detectors/mcp/mcpProject.js.map +1 -0
- package/dist/scanner/detectors/mcp/mcpServers.d.ts +17 -0
- package/dist/scanner/detectors/mcp/mcpServers.d.ts.map +1 -0
- package/dist/scanner/detectors/mcp/mcpServers.js +193 -0
- package/dist/scanner/detectors/mcp/mcpServers.js.map +1 -0
- package/dist/scanner/detectors/services/analytics.d.ts +12 -0
- package/dist/scanner/detectors/services/analytics.d.ts.map +1 -0
- package/dist/scanner/detectors/services/analytics.js +236 -0
- package/dist/scanner/detectors/services/analytics.js.map +1 -0
- package/dist/scanner/detectors/services/auth.d.ts +12 -0
- package/dist/scanner/detectors/services/auth.d.ts.map +1 -0
- package/dist/scanner/detectors/services/auth.js +217 -0
- package/dist/scanner/detectors/services/auth.js.map +1 -0
- package/dist/scanner/detectors/services/email.d.ts +12 -0
- package/dist/scanner/detectors/services/email.d.ts.map +1 -0
- package/dist/scanner/detectors/services/email.js +211 -0
- package/dist/scanner/detectors/services/email.js.map +1 -0
- package/dist/scanner/detectors/services/payments.d.ts +12 -0
- package/dist/scanner/detectors/services/payments.d.ts.map +1 -0
- package/dist/scanner/detectors/services/payments.js +185 -0
- package/dist/scanner/detectors/services/payments.js.map +1 -0
- package/dist/scanner/detectors/utils.d.ts +160 -0
- package/dist/scanner/detectors/utils.d.ts.map +1 -0
- package/dist/scanner/detectors/utils.js +222 -0
- package/dist/scanner/detectors/utils.js.map +1 -0
- package/dist/scanner/index.d.ts +42 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +282 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/registry.d.ts +43 -0
- package/dist/scanner/registry.d.ts.map +1 -0
- package/dist/scanner/registry.js +243 -0
- package/dist/scanner/registry.js.map +1 -0
- package/dist/scanner/types.d.ts +112 -0
- package/dist/scanner/types.d.ts.map +1 -0
- package/dist/scanner/types.js +6 -0
- package/dist/scanner/types.js.map +1 -0
- package/dist/templates/config/ralph.config.js.tmpl +38 -0
- package/dist/templates/guides/AGENTS.md.tmpl +100 -0
- package/dist/templates/guides/FRONTEND.md.tmpl +523 -0
- package/dist/templates/guides/PERFORMANCE.md.tmpl +264 -0
- package/dist/templates/guides/SECURITY.md.tmpl +100 -0
- package/dist/templates/prompts/PROMPT.md.tmpl +77 -0
- package/dist/templates/prompts/PROMPT_e2e.md.tmpl +234 -0
- package/dist/templates/prompts/PROMPT_feature.md.tmpl +83 -0
- package/dist/templates/prompts/PROMPT_review.md.tmpl +167 -0
- package/dist/templates/prompts/PROMPT_verify.md.tmpl +72 -0
- package/dist/templates/root/.gitignore.tmpl +5 -0
- package/dist/templates/root/LEARNINGS.md.tmpl +24 -0
- package/dist/templates/root/README.md.tmpl +61 -0
- package/dist/templates/scripts/feature-loop.sh.tmpl +267 -0
- package/dist/templates/scripts/loop.sh.tmpl +59 -0
- package/dist/templates/scripts/ralph-monitor.sh.tmpl +244 -0
- package/dist/templates/specs/README.md.tmpl +57 -0
- package/dist/templates/specs/_example.md.tmpl +71 -0
- package/dist/utils/config.d.ts +95 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +148 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/header.d.ts +5 -0
- package/dist/utils/header.d.ts.map +1 -0
- package/dist/utils/header.js +15 -0
- package/dist/utils/header.js.map +1 -0
- package/dist/utils/logger.d.ts +11 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +24 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +44 -0
- package/src/ai/enhancer.ts +350 -0
- package/src/ai/index.ts +38 -0
- package/src/ai/prompts.ts +217 -0
- package/src/ai/providers.ts +136 -0
- package/src/cli.ts +255 -0
- package/src/commands/init.ts +149 -0
- package/src/commands/monitor.ts +412 -0
- package/src/commands/new.ts +312 -0
- package/src/commands/run.ts +214 -0
- package/src/generator/config.ts +116 -0
- package/src/generator/index.ts +227 -0
- package/src/generator/templates.ts +412 -0
- package/src/generator/writer.ts +293 -0
- package/src/index.ts +41 -0
- package/src/scanner/detectors/core/framework.ts +332 -0
- package/src/scanner/detectors/core/packageManager.ts +91 -0
- package/src/scanner/detectors/core/styling.ts +261 -0
- package/src/scanner/detectors/core/testing.ts +221 -0
- package/src/scanner/detectors/data/api.ts +303 -0
- package/src/scanner/detectors/data/database.ts +245 -0
- package/src/scanner/detectors/data/orm.ts +180 -0
- package/src/scanner/detectors/frontend/formHandling.ts +244 -0
- package/src/scanner/detectors/frontend/stateManagement.ts +261 -0
- package/src/scanner/detectors/frontend/uiComponents.ts +328 -0
- package/src/scanner/detectors/infra/deployment.ts +343 -0
- package/src/scanner/detectors/infra/monorepo.ts +251 -0
- package/src/scanner/detectors/mcp/mcpProject.ts +176 -0
- package/src/scanner/detectors/mcp/mcpServers.ts +237 -0
- package/src/scanner/detectors/services/analytics.ts +273 -0
- package/src/scanner/detectors/services/auth.ts +254 -0
- package/src/scanner/detectors/services/email.ts +244 -0
- package/src/scanner/detectors/services/payments.ts +213 -0
- package/src/scanner/detectors/utils.ts +251 -0
- package/src/scanner/index.ts +354 -0
- package/src/scanner/registry.ts +301 -0
- package/src/scanner/types.ts +152 -0
- package/src/templates/config/ralph.config.js.tmpl +38 -0
- package/src/templates/guides/AGENTS.md.tmpl +100 -0
- package/src/templates/guides/FRONTEND.md.tmpl +523 -0
- package/src/templates/guides/PERFORMANCE.md.tmpl +264 -0
- package/src/templates/guides/SECURITY.md.tmpl +100 -0
- package/src/templates/prompts/PROMPT.md.tmpl +77 -0
- package/src/templates/prompts/PROMPT_e2e.md.tmpl +234 -0
- package/src/templates/prompts/PROMPT_feature.md.tmpl +83 -0
- package/src/templates/prompts/PROMPT_review.md.tmpl +167 -0
- package/src/templates/prompts/PROMPT_verify.md.tmpl +72 -0
- package/src/templates/root/.gitignore.tmpl +5 -0
- package/src/templates/root/LEARNINGS.md.tmpl +24 -0
- package/src/templates/root/README.md.tmpl +61 -0
- package/src/templates/scripts/feature-loop.sh.tmpl +267 -0
- package/src/templates/scripts/loop.sh.tmpl +59 -0
- package/src/templates/scripts/ralph-monitor.sh.tmpl +244 -0
- package/src/templates/specs/README.md.tmpl +57 -0
- package/src/templates/specs/_example.md.tmpl +71 -0
- package/src/utils/config.ts +221 -0
- package/src/utils/header.ts +15 -0
- package/src/utils/logger.ts +28 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scanner Orchestrator
|
|
3
|
+
* Main entry point for project scanning
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { resolve } from 'node:path';
|
|
8
|
+
import { createDefaultRegistry, DetectorRegistry } from './registry.js';
|
|
9
|
+
import type { ScanResult, ScannerOptions, DetectedStack, DetectionResult } from './types.js';
|
|
10
|
+
|
|
11
|
+
// Re-export types
|
|
12
|
+
export type {
|
|
13
|
+
DetectionResult,
|
|
14
|
+
DetectedStack,
|
|
15
|
+
MCPStack,
|
|
16
|
+
DetectorCategory,
|
|
17
|
+
Detector,
|
|
18
|
+
ScannerOptions,
|
|
19
|
+
ScanResult,
|
|
20
|
+
} from './types.js';
|
|
21
|
+
|
|
22
|
+
// Re-export registry
|
|
23
|
+
export { DetectorRegistry, createDefaultRegistry } from './registry.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Scanner class
|
|
27
|
+
* Orchestrates detection of project tech stack
|
|
28
|
+
*/
|
|
29
|
+
export class Scanner {
|
|
30
|
+
private registry: DetectorRegistry;
|
|
31
|
+
private options: ScannerOptions;
|
|
32
|
+
|
|
33
|
+
constructor(options: ScannerOptions = {}) {
|
|
34
|
+
this.registry = createDefaultRegistry();
|
|
35
|
+
this.options = {
|
|
36
|
+
includeLowConfidence: false,
|
|
37
|
+
minConfidence: 40,
|
|
38
|
+
...options,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Scan a project directory and detect its tech stack
|
|
44
|
+
*/
|
|
45
|
+
async scan(projectRoot: string): Promise<ScanResult> {
|
|
46
|
+
const startTime = Date.now();
|
|
47
|
+
const errors: string[] = [];
|
|
48
|
+
|
|
49
|
+
// Resolve to absolute path
|
|
50
|
+
const absolutePath = resolve(projectRoot);
|
|
51
|
+
|
|
52
|
+
// Validate project root exists
|
|
53
|
+
if (!existsSync(absolutePath)) {
|
|
54
|
+
throw new Error(`Project root does not exist: ${absolutePath}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Run all detectors
|
|
58
|
+
let stack: DetectedStack;
|
|
59
|
+
try {
|
|
60
|
+
stack = await this.registry.runAllDetectors(absolutePath);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
errors.push(`Detection error: ${error instanceof Error ? error.message : String(error)}`);
|
|
63
|
+
stack = {};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Filter by confidence threshold if configured
|
|
67
|
+
if (!this.options.includeLowConfidence && this.options.minConfidence) {
|
|
68
|
+
stack = this.filterByConfidence(stack, this.options.minConfidence);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const scanTime = Date.now() - startTime;
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
projectRoot: absolutePath,
|
|
75
|
+
stack,
|
|
76
|
+
scanTime,
|
|
77
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Filter array of detection results by confidence
|
|
83
|
+
*/
|
|
84
|
+
private filterArrayByConfidence(
|
|
85
|
+
results: DetectionResult[] | undefined,
|
|
86
|
+
minConfidence: number
|
|
87
|
+
): DetectionResult[] | undefined {
|
|
88
|
+
if (!results) return undefined;
|
|
89
|
+
const filtered = results.filter(r => r.confidence >= minConfidence);
|
|
90
|
+
return filtered.length > 0 ? filtered : undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Filter stack results by minimum confidence
|
|
95
|
+
*/
|
|
96
|
+
private filterByConfidence(stack: DetectedStack, minConfidence: number): DetectedStack {
|
|
97
|
+
const filtered: DetectedStack = {};
|
|
98
|
+
|
|
99
|
+
// Core
|
|
100
|
+
if (stack.framework && stack.framework.confidence >= minConfidence) {
|
|
101
|
+
filtered.framework = stack.framework;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (stack.packageManager && stack.packageManager.confidence >= minConfidence) {
|
|
105
|
+
filtered.packageManager = stack.packageManager;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (stack.testing) {
|
|
109
|
+
const testing: DetectedStack['testing'] = {};
|
|
110
|
+
if (stack.testing.unit && stack.testing.unit.confidence >= minConfidence) {
|
|
111
|
+
testing.unit = stack.testing.unit;
|
|
112
|
+
}
|
|
113
|
+
if (stack.testing.e2e && stack.testing.e2e.confidence >= minConfidence) {
|
|
114
|
+
testing.e2e = stack.testing.e2e;
|
|
115
|
+
}
|
|
116
|
+
if (testing.unit || testing.e2e) {
|
|
117
|
+
filtered.testing = testing;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (stack.styling && stack.styling.confidence >= minConfidence) {
|
|
122
|
+
filtered.styling = stack.styling;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Data Layer
|
|
126
|
+
if (stack.database && stack.database.confidence >= minConfidence) {
|
|
127
|
+
filtered.database = stack.database;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (stack.orm && stack.orm.confidence >= minConfidence) {
|
|
131
|
+
filtered.orm = stack.orm;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
filtered.api = this.filterArrayByConfidence(stack.api, minConfidence);
|
|
135
|
+
|
|
136
|
+
// Frontend
|
|
137
|
+
if (stack.stateManagement && stack.stateManagement.confidence >= minConfidence) {
|
|
138
|
+
filtered.stateManagement = stack.stateManagement;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
filtered.uiComponents = this.filterArrayByConfidence(stack.uiComponents, minConfidence);
|
|
142
|
+
filtered.formHandling = this.filterArrayByConfidence(stack.formHandling, minConfidence);
|
|
143
|
+
|
|
144
|
+
// Services
|
|
145
|
+
if (stack.auth && stack.auth.confidence >= minConfidence) {
|
|
146
|
+
filtered.auth = stack.auth;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
filtered.analytics = this.filterArrayByConfidence(stack.analytics, minConfidence);
|
|
150
|
+
|
|
151
|
+
if (stack.payments && stack.payments.confidence >= minConfidence) {
|
|
152
|
+
filtered.payments = stack.payments;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (stack.email && stack.email.confidence >= minConfidence) {
|
|
156
|
+
filtered.email = stack.email;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Infrastructure
|
|
160
|
+
filtered.deployment = this.filterArrayByConfidence(stack.deployment, minConfidence);
|
|
161
|
+
|
|
162
|
+
if (stack.monorepo && stack.monorepo.confidence >= minConfidence) {
|
|
163
|
+
filtered.monorepo = stack.monorepo;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// MCP (keep as-is since it has its own structure)
|
|
167
|
+
if (stack.mcp) {
|
|
168
|
+
filtered.mcp = stack.mcp;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return filtered;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get the detector registry for custom detector registration
|
|
176
|
+
*/
|
|
177
|
+
getRegistry(): DetectorRegistry {
|
|
178
|
+
return this.registry;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Convenience function to scan a project
|
|
184
|
+
*/
|
|
185
|
+
export async function scanProject(projectRoot: string, options?: ScannerOptions): Promise<ScanResult> {
|
|
186
|
+
const scanner = new Scanner(options);
|
|
187
|
+
return scanner.scan(projectRoot);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Helper to format a single detection result
|
|
192
|
+
*/
|
|
193
|
+
function formatDetection(label: string, result: DetectionResult | undefined, indent = ''): string[] {
|
|
194
|
+
const lines: string[] = [];
|
|
195
|
+
if (result) {
|
|
196
|
+
const version = result.version ? `@${result.version}` : '';
|
|
197
|
+
const variant = result.variant ? ` (${result.variant})` : '';
|
|
198
|
+
lines.push(`${indent}${label}: ${result.name}${version}${variant} [${result.confidence}%]`);
|
|
199
|
+
lines.push(`${indent} Evidence: ${result.evidence.join(', ')}`);
|
|
200
|
+
}
|
|
201
|
+
return lines;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Helper to format an array of detection results
|
|
206
|
+
*/
|
|
207
|
+
function formatDetectionArray(label: string, results: DetectionResult[] | undefined, indent = ''): string[] {
|
|
208
|
+
const lines: string[] = [];
|
|
209
|
+
if (results && results.length > 0) {
|
|
210
|
+
lines.push(`${indent}${label}:`);
|
|
211
|
+
for (const result of results) {
|
|
212
|
+
const version = result.version ? `@${result.version}` : '';
|
|
213
|
+
const variant = result.variant ? ` (${result.variant})` : '';
|
|
214
|
+
lines.push(`${indent} - ${result.name}${version}${variant} [${result.confidence}%]`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return lines;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Format a scan result for display
|
|
222
|
+
*/
|
|
223
|
+
export function formatScanResult(result: ScanResult): string {
|
|
224
|
+
const lines: string[] = [];
|
|
225
|
+
const { stack } = result;
|
|
226
|
+
|
|
227
|
+
lines.push(`Project: ${result.projectRoot}`);
|
|
228
|
+
lines.push(`Scan time: ${result.scanTime}ms`);
|
|
229
|
+
lines.push('');
|
|
230
|
+
|
|
231
|
+
// ============ Core ============
|
|
232
|
+
lines.push('=== Core ===');
|
|
233
|
+
|
|
234
|
+
if (stack.framework) {
|
|
235
|
+
lines.push(...formatDetection('Framework', stack.framework));
|
|
236
|
+
} else {
|
|
237
|
+
lines.push('Framework: Not detected');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (stack.packageManager) {
|
|
241
|
+
lines.push(...formatDetection('Package Manager', stack.packageManager));
|
|
242
|
+
} else {
|
|
243
|
+
lines.push('Package Manager: Not detected');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (stack.testing) {
|
|
247
|
+
if (stack.testing.unit) {
|
|
248
|
+
lines.push(...formatDetection('Unit Testing', stack.testing.unit));
|
|
249
|
+
}
|
|
250
|
+
if (stack.testing.e2e) {
|
|
251
|
+
lines.push(...formatDetection('E2E Testing', stack.testing.e2e));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (stack.styling) {
|
|
256
|
+
lines.push(...formatDetection('Styling', stack.styling));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ============ Data Layer ============
|
|
260
|
+
if (stack.database || stack.orm || stack.api) {
|
|
261
|
+
lines.push('');
|
|
262
|
+
lines.push('=== Data Layer ===');
|
|
263
|
+
|
|
264
|
+
if (stack.database) {
|
|
265
|
+
lines.push(...formatDetection('Database', stack.database));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (stack.orm) {
|
|
269
|
+
lines.push(...formatDetection('ORM', stack.orm));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
lines.push(...formatDetectionArray('API Patterns', stack.api));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ============ Frontend ============
|
|
276
|
+
if (stack.stateManagement || stack.uiComponents || stack.formHandling) {
|
|
277
|
+
lines.push('');
|
|
278
|
+
lines.push('=== Frontend ===');
|
|
279
|
+
|
|
280
|
+
if (stack.stateManagement) {
|
|
281
|
+
lines.push(...formatDetection('State Management', stack.stateManagement));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
lines.push(...formatDetectionArray('UI Components', stack.uiComponents));
|
|
285
|
+
lines.push(...formatDetectionArray('Form Handling', stack.formHandling));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ============ Services ============
|
|
289
|
+
if (stack.auth || stack.analytics || stack.payments || stack.email) {
|
|
290
|
+
lines.push('');
|
|
291
|
+
lines.push('=== Services ===');
|
|
292
|
+
|
|
293
|
+
if (stack.auth) {
|
|
294
|
+
lines.push(...formatDetection('Auth', stack.auth));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
lines.push(...formatDetectionArray('Analytics', stack.analytics));
|
|
298
|
+
|
|
299
|
+
if (stack.payments) {
|
|
300
|
+
lines.push(...formatDetection('Payments', stack.payments));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (stack.email) {
|
|
304
|
+
lines.push(...formatDetection('Email', stack.email));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ============ Infrastructure ============
|
|
309
|
+
if (stack.deployment || stack.monorepo) {
|
|
310
|
+
lines.push('');
|
|
311
|
+
lines.push('=== Infrastructure ===');
|
|
312
|
+
|
|
313
|
+
lines.push(...formatDetectionArray('Deployment', stack.deployment));
|
|
314
|
+
|
|
315
|
+
if (stack.monorepo) {
|
|
316
|
+
lines.push(...formatDetection('Monorepo', stack.monorepo));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ============ MCP ============
|
|
321
|
+
if (stack.mcp) {
|
|
322
|
+
lines.push('');
|
|
323
|
+
lines.push('=== MCP ===');
|
|
324
|
+
|
|
325
|
+
if (stack.mcp.isProject) {
|
|
326
|
+
lines.push('Type: MCP Server Project');
|
|
327
|
+
if (stack.mcp.projectInfo) {
|
|
328
|
+
lines.push(...formatDetection('Project Info', stack.mcp.projectInfo, ' '));
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (stack.mcp.detected && stack.mcp.detected.length > 0) {
|
|
333
|
+
lines.push('Configured MCP Servers:');
|
|
334
|
+
for (const server of stack.mcp.detected) {
|
|
335
|
+
lines.push(` - ${server.name}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (stack.mcp.recommended && stack.mcp.recommended.length > 0) {
|
|
340
|
+
lines.push(`Recommended MCP Servers: ${stack.mcp.recommended.join(', ')}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ============ Errors ============
|
|
345
|
+
if (result.errors && result.errors.length > 0) {
|
|
346
|
+
lines.push('');
|
|
347
|
+
lines.push('Errors:');
|
|
348
|
+
for (const error of result.errors) {
|
|
349
|
+
lines.push(` - ${error}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return lines.join('\n');
|
|
354
|
+
}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detector Registry
|
|
3
|
+
* Registers and manages all detectors for the scanner
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Detector, DetectorCategory, DetectionResult, DetectedStack, MCPStack } from './types.js';
|
|
7
|
+
|
|
8
|
+
// Import core detectors
|
|
9
|
+
import { frameworkDetector } from './detectors/core/framework.js';
|
|
10
|
+
import { packageManagerDetector } from './detectors/core/packageManager.js';
|
|
11
|
+
import { testingDetector } from './detectors/core/testing.js';
|
|
12
|
+
import { stylingDetector } from './detectors/core/styling.js';
|
|
13
|
+
|
|
14
|
+
// Import data layer detectors
|
|
15
|
+
import { databaseDetector } from './detectors/data/database.js';
|
|
16
|
+
import { ormDetector } from './detectors/data/orm.js';
|
|
17
|
+
import { apiDetector } from './detectors/data/api.js';
|
|
18
|
+
|
|
19
|
+
// Import frontend detectors
|
|
20
|
+
import { stateManagementDetector } from './detectors/frontend/stateManagement.js';
|
|
21
|
+
import { uiComponentsDetector } from './detectors/frontend/uiComponents.js';
|
|
22
|
+
import { formHandlingDetector } from './detectors/frontend/formHandling.js';
|
|
23
|
+
|
|
24
|
+
// Import services detectors
|
|
25
|
+
import { authDetector } from './detectors/services/auth.js';
|
|
26
|
+
import { analyticsDetector } from './detectors/services/analytics.js';
|
|
27
|
+
import { paymentsDetector } from './detectors/services/payments.js';
|
|
28
|
+
import { emailDetector } from './detectors/services/email.js';
|
|
29
|
+
|
|
30
|
+
// Import infrastructure detectors
|
|
31
|
+
import { deploymentDetector } from './detectors/infra/deployment.js';
|
|
32
|
+
import { monorepoDetector } from './detectors/infra/monorepo.js';
|
|
33
|
+
|
|
34
|
+
// Import MCP detectors
|
|
35
|
+
import { mcpServersDetector, getRecommendedMCPServers } from './detectors/mcp/mcpServers.js';
|
|
36
|
+
import { mcpProjectDetector } from './detectors/mcp/mcpProject.js';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* All detector categories
|
|
40
|
+
*/
|
|
41
|
+
const ALL_CATEGORIES: DetectorCategory[] = [
|
|
42
|
+
// Core
|
|
43
|
+
'framework',
|
|
44
|
+
'packageManager',
|
|
45
|
+
'testing',
|
|
46
|
+
'styling',
|
|
47
|
+
// Data Layer
|
|
48
|
+
'database',
|
|
49
|
+
'orm',
|
|
50
|
+
'api',
|
|
51
|
+
// Frontend
|
|
52
|
+
'stateManagement',
|
|
53
|
+
'uiComponents',
|
|
54
|
+
'formHandling',
|
|
55
|
+
// Services
|
|
56
|
+
'auth',
|
|
57
|
+
'analytics',
|
|
58
|
+
'payments',
|
|
59
|
+
'email',
|
|
60
|
+
// Infrastructure
|
|
61
|
+
'deployment',
|
|
62
|
+
'monorepo',
|
|
63
|
+
// MCP
|
|
64
|
+
'mcp',
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Detector Registry class
|
|
69
|
+
* Manages registration and execution of detectors
|
|
70
|
+
*/
|
|
71
|
+
export class DetectorRegistry {
|
|
72
|
+
private detectors: Map<DetectorCategory, Detector[]> = new Map();
|
|
73
|
+
|
|
74
|
+
constructor() {
|
|
75
|
+
// Initialize all categories
|
|
76
|
+
for (const category of ALL_CATEGORIES) {
|
|
77
|
+
this.detectors.set(category, []);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Register a detector
|
|
83
|
+
*/
|
|
84
|
+
register(detector: Detector): void {
|
|
85
|
+
const category = this.detectors.get(detector.category);
|
|
86
|
+
if (category) {
|
|
87
|
+
category.push(detector);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get all detectors for a category
|
|
93
|
+
*/
|
|
94
|
+
getDetectors(category: DetectorCategory): Detector[] {
|
|
95
|
+
return this.detectors.get(category) || [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get all registered detectors
|
|
100
|
+
*/
|
|
101
|
+
getAllDetectors(): Detector[] {
|
|
102
|
+
const all: Detector[] = [];
|
|
103
|
+
for (const detectors of this.detectors.values()) {
|
|
104
|
+
all.push(...detectors);
|
|
105
|
+
}
|
|
106
|
+
return all;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Run a single-result detector category
|
|
111
|
+
*/
|
|
112
|
+
private async runSingleDetector(
|
|
113
|
+
projectRoot: string,
|
|
114
|
+
category: DetectorCategory
|
|
115
|
+
): Promise<DetectionResult | null> {
|
|
116
|
+
for (const detector of this.getDetectors(category)) {
|
|
117
|
+
const result = await detector.detect(projectRoot);
|
|
118
|
+
if (result && !Array.isArray(result)) {
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Run a multi-result detector category
|
|
127
|
+
*/
|
|
128
|
+
private async runMultiDetector(
|
|
129
|
+
projectRoot: string,
|
|
130
|
+
category: DetectorCategory
|
|
131
|
+
): Promise<DetectionResult[] | null> {
|
|
132
|
+
for (const detector of this.getDetectors(category)) {
|
|
133
|
+
const result = await detector.detect(projectRoot);
|
|
134
|
+
if (result) {
|
|
135
|
+
return Array.isArray(result) ? result : [result];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Run all detectors for a project
|
|
143
|
+
*/
|
|
144
|
+
async runAllDetectors(projectRoot: string): Promise<DetectedStack> {
|
|
145
|
+
const stack: DetectedStack = {};
|
|
146
|
+
|
|
147
|
+
// ============ Core ============
|
|
148
|
+
|
|
149
|
+
// Run framework detector
|
|
150
|
+
stack.framework = await this.runSingleDetector(projectRoot, 'framework') || undefined;
|
|
151
|
+
|
|
152
|
+
// Run package manager detector
|
|
153
|
+
stack.packageManager = await this.runSingleDetector(projectRoot, 'packageManager') || undefined;
|
|
154
|
+
|
|
155
|
+
// Run testing detectors
|
|
156
|
+
for (const detector of this.getDetectors('testing')) {
|
|
157
|
+
const result = await detector.detect(projectRoot);
|
|
158
|
+
if (result) {
|
|
159
|
+
stack.testing = {};
|
|
160
|
+
if (Array.isArray(result)) {
|
|
161
|
+
for (const r of result) {
|
|
162
|
+
if (r.variant === 'unit') {
|
|
163
|
+
stack.testing.unit = r;
|
|
164
|
+
} else if (r.variant === 'e2e') {
|
|
165
|
+
stack.testing.e2e = r;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
// Single result, determine type from name
|
|
170
|
+
if (result.name === 'Jest' || result.name === 'Vitest') {
|
|
171
|
+
stack.testing.unit = result;
|
|
172
|
+
} else {
|
|
173
|
+
stack.testing.e2e = result;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Run styling detector
|
|
181
|
+
stack.styling = await this.runSingleDetector(projectRoot, 'styling') || undefined;
|
|
182
|
+
|
|
183
|
+
// ============ Data Layer ============
|
|
184
|
+
|
|
185
|
+
// Run database detector
|
|
186
|
+
stack.database = await this.runSingleDetector(projectRoot, 'database') || undefined;
|
|
187
|
+
|
|
188
|
+
// Run ORM detector
|
|
189
|
+
stack.orm = await this.runSingleDetector(projectRoot, 'orm') || undefined;
|
|
190
|
+
|
|
191
|
+
// Run API detector (multi-result)
|
|
192
|
+
stack.api = await this.runMultiDetector(projectRoot, 'api') || undefined;
|
|
193
|
+
|
|
194
|
+
// ============ Frontend ============
|
|
195
|
+
|
|
196
|
+
// Run state management detector
|
|
197
|
+
stack.stateManagement = await this.runSingleDetector(projectRoot, 'stateManagement') || undefined;
|
|
198
|
+
|
|
199
|
+
// Run UI components detector (multi-result)
|
|
200
|
+
stack.uiComponents = await this.runMultiDetector(projectRoot, 'uiComponents') || undefined;
|
|
201
|
+
|
|
202
|
+
// Run form handling detector (multi-result)
|
|
203
|
+
stack.formHandling = await this.runMultiDetector(projectRoot, 'formHandling') || undefined;
|
|
204
|
+
|
|
205
|
+
// ============ Services ============
|
|
206
|
+
|
|
207
|
+
// Run auth detector
|
|
208
|
+
stack.auth = await this.runSingleDetector(projectRoot, 'auth') || undefined;
|
|
209
|
+
|
|
210
|
+
// Run analytics detector (multi-result)
|
|
211
|
+
stack.analytics = await this.runMultiDetector(projectRoot, 'analytics') || undefined;
|
|
212
|
+
|
|
213
|
+
// Run payments detector
|
|
214
|
+
stack.payments = await this.runSingleDetector(projectRoot, 'payments') || undefined;
|
|
215
|
+
|
|
216
|
+
// Run email detector
|
|
217
|
+
stack.email = await this.runSingleDetector(projectRoot, 'email') || undefined;
|
|
218
|
+
|
|
219
|
+
// ============ Infrastructure ============
|
|
220
|
+
|
|
221
|
+
// Run deployment detector (multi-result)
|
|
222
|
+
stack.deployment = await this.runMultiDetector(projectRoot, 'deployment') || undefined;
|
|
223
|
+
|
|
224
|
+
// Run monorepo detector
|
|
225
|
+
stack.monorepo = await this.runSingleDetector(projectRoot, 'monorepo') || undefined;
|
|
226
|
+
|
|
227
|
+
// ============ MCP ============
|
|
228
|
+
|
|
229
|
+
// Run MCP detectors
|
|
230
|
+
const mcpStack: MCPStack = {};
|
|
231
|
+
|
|
232
|
+
// Detect configured MCP servers
|
|
233
|
+
const mcpServers = await this.runMultiDetector(projectRoot, 'mcp');
|
|
234
|
+
|
|
235
|
+
// Separate MCP server configs from MCP project detection
|
|
236
|
+
if (mcpServers) {
|
|
237
|
+
const serverConfigs = mcpServers.filter(r => r.name.startsWith('MCP:') || r.name === 'MCP Config Directory');
|
|
238
|
+
const projectInfo = mcpServers.find(r => r.name === 'MCP Server Project' || r.name === 'MCP Client');
|
|
239
|
+
|
|
240
|
+
if (serverConfigs.length > 0) {
|
|
241
|
+
mcpStack.detected = serverConfigs;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (projectInfo) {
|
|
245
|
+
mcpStack.isProject = projectInfo.name === 'MCP Server Project';
|
|
246
|
+
mcpStack.projectInfo = projectInfo;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Get recommended MCP servers based on stack
|
|
251
|
+
mcpStack.recommended = getRecommendedMCPServers(projectRoot);
|
|
252
|
+
|
|
253
|
+
// Only add MCP stack if there's meaningful data
|
|
254
|
+
if (mcpStack.detected || mcpStack.isProject || (mcpStack.recommended && mcpStack.recommended.length > 0)) {
|
|
255
|
+
stack.mcp = mcpStack;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return stack;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Create a registry with all core detectors pre-registered
|
|
264
|
+
*/
|
|
265
|
+
export function createDefaultRegistry(): DetectorRegistry {
|
|
266
|
+
const registry = new DetectorRegistry();
|
|
267
|
+
|
|
268
|
+
// Register core detectors
|
|
269
|
+
registry.register(frameworkDetector);
|
|
270
|
+
registry.register(packageManagerDetector);
|
|
271
|
+
registry.register(testingDetector);
|
|
272
|
+
registry.register(stylingDetector);
|
|
273
|
+
|
|
274
|
+
// Register data layer detectors
|
|
275
|
+
registry.register(databaseDetector);
|
|
276
|
+
registry.register(ormDetector);
|
|
277
|
+
registry.register(apiDetector);
|
|
278
|
+
|
|
279
|
+
// Register frontend detectors
|
|
280
|
+
registry.register(stateManagementDetector);
|
|
281
|
+
registry.register(uiComponentsDetector);
|
|
282
|
+
registry.register(formHandlingDetector);
|
|
283
|
+
|
|
284
|
+
// Register services detectors
|
|
285
|
+
registry.register(authDetector);
|
|
286
|
+
registry.register(analyticsDetector);
|
|
287
|
+
registry.register(paymentsDetector);
|
|
288
|
+
registry.register(emailDetector);
|
|
289
|
+
|
|
290
|
+
// Register infrastructure detectors
|
|
291
|
+
registry.register(deploymentDetector);
|
|
292
|
+
registry.register(monorepoDetector);
|
|
293
|
+
|
|
294
|
+
// Register MCP detectors
|
|
295
|
+
registry.register(mcpServersDetector);
|
|
296
|
+
registry.register(mcpProjectDetector);
|
|
297
|
+
|
|
298
|
+
return registry;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export { DetectorRegistry as Registry };
|