@vibecheckai/cli 3.0.2 → 3.0.3
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/package.json +9 -1
- package/bin/cli-hygiene.js +0 -241
- package/bin/guardrail.js +0 -834
- package/bin/runners/cli-utils.js +0 -1070
- package/bin/runners/context/ai-task-decomposer.js +0 -337
- package/bin/runners/context/analyzer.js +0 -462
- package/bin/runners/context/api-contracts.js +0 -427
- package/bin/runners/context/context-diff.js +0 -342
- package/bin/runners/context/context-pruner.js +0 -291
- package/bin/runners/context/dependency-graph.js +0 -414
- package/bin/runners/context/generators/claude.js +0 -107
- package/bin/runners/context/generators/codex.js +0 -108
- package/bin/runners/context/generators/copilot.js +0 -119
- package/bin/runners/context/generators/cursor.js +0 -514
- package/bin/runners/context/generators/mcp.js +0 -151
- package/bin/runners/context/generators/windsurf.js +0 -180
- package/bin/runners/context/git-context.js +0 -302
- package/bin/runners/context/index.js +0 -1042
- package/bin/runners/context/insights.js +0 -173
- package/bin/runners/context/mcp-server/generate-rules.js +0 -337
- package/bin/runners/context/mcp-server/index.js +0 -1176
- package/bin/runners/context/mcp-server/package.json +0 -24
- package/bin/runners/context/memory.js +0 -200
- package/bin/runners/context/monorepo.js +0 -215
- package/bin/runners/context/multi-repo-federation.js +0 -404
- package/bin/runners/context/patterns.js +0 -253
- package/bin/runners/context/proof-context.js +0 -972
- package/bin/runners/context/security-scanner.js +0 -303
- package/bin/runners/context/semantic-search.js +0 -350
- package/bin/runners/context/shared.js +0 -264
- package/bin/runners/context/team-conventions.js +0 -310
- package/bin/runners/lib/ai-bridge.js +0 -416
- package/bin/runners/lib/analysis-core.js +0 -271
- package/bin/runners/lib/analyzers.js +0 -541
- package/bin/runners/lib/audit-bridge.js +0 -391
- package/bin/runners/lib/auth-truth.js +0 -193
- package/bin/runners/lib/auth.js +0 -215
- package/bin/runners/lib/backup.js +0 -62
- package/bin/runners/lib/billing.js +0 -107
- package/bin/runners/lib/claims.js +0 -118
- package/bin/runners/lib/cli-ui.js +0 -540
- package/bin/runners/lib/compliance-bridge-new.js +0 -0
- package/bin/runners/lib/compliance-bridge.js +0 -165
- package/bin/runners/lib/contracts/auth-contract.js +0 -194
- package/bin/runners/lib/contracts/env-contract.js +0 -178
- package/bin/runners/lib/contracts/external-contract.js +0 -198
- package/bin/runners/lib/contracts/guard.js +0 -168
- package/bin/runners/lib/contracts/index.js +0 -89
- package/bin/runners/lib/contracts/plan-validator.js +0 -311
- package/bin/runners/lib/contracts/route-contract.js +0 -192
- package/bin/runners/lib/detect.js +0 -89
- package/bin/runners/lib/doctor/autofix.js +0 -254
- package/bin/runners/lib/doctor/index.js +0 -37
- package/bin/runners/lib/doctor/modules/dependencies.js +0 -325
- package/bin/runners/lib/doctor/modules/index.js +0 -46
- package/bin/runners/lib/doctor/modules/network.js +0 -250
- package/bin/runners/lib/doctor/modules/project.js +0 -312
- package/bin/runners/lib/doctor/modules/runtime.js +0 -224
- package/bin/runners/lib/doctor/modules/security.js +0 -348
- package/bin/runners/lib/doctor/modules/system.js +0 -213
- package/bin/runners/lib/doctor/modules/vibecheck.js +0 -394
- package/bin/runners/lib/doctor/reporter.js +0 -262
- package/bin/runners/lib/doctor/service.js +0 -262
- package/bin/runners/lib/doctor/types.js +0 -113
- package/bin/runners/lib/doctor/ui.js +0 -263
- package/bin/runners/lib/doctor-enhanced.js +0 -233
- package/bin/runners/lib/doctor-v2.js +0 -608
- package/bin/runners/lib/enforcement.js +0 -72
|
@@ -1,342 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Context Diff Module
|
|
3
|
-
* Tracks changes in context between generations
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const fs = require("fs");
|
|
7
|
-
const path = require("path");
|
|
8
|
-
const crypto = require("crypto");
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Generate hash for context snapshot
|
|
12
|
-
*/
|
|
13
|
-
function generateContextHash(context) {
|
|
14
|
-
const content = JSON.stringify(context, null, 2);
|
|
15
|
-
return crypto.createHash("sha256").update(content).digest("hex");
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Save context snapshot
|
|
20
|
-
*/
|
|
21
|
-
function saveSnapshot(projectPath, context) {
|
|
22
|
-
const vibecheckDir = path.join(projectPath, ".vibecheck");
|
|
23
|
-
if (!fs.existsSync(vibecheckDir)) {
|
|
24
|
-
fs.mkdirSync(vibecheckDir, { recursive: true });
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const snapshot = {
|
|
28
|
-
timestamp: new Date().toISOString(),
|
|
29
|
-
hash: generateContextHash(context),
|
|
30
|
-
context: {
|
|
31
|
-
version: context.version,
|
|
32
|
-
project: context.project,
|
|
33
|
-
techStack: context.techStack,
|
|
34
|
-
structure: {
|
|
35
|
-
directories: context.structure?.directories || [],
|
|
36
|
-
componentsCount: context.structure?.components?.length || 0,
|
|
37
|
-
apiRoutesCount: context.structure?.apiRoutes?.length || 0,
|
|
38
|
-
},
|
|
39
|
-
patterns: {
|
|
40
|
-
hooks: context.patterns?.hooks || [],
|
|
41
|
-
stateManagement: context.patterns?.stateManagement,
|
|
42
|
-
validation: context.patterns?.validation,
|
|
43
|
-
},
|
|
44
|
-
monorepo: context.monorepo,
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const snapshotFile = path.join(vibecheckDir, "context-snapshot.json");
|
|
49
|
-
fs.writeFileSync(snapshotFile, JSON.stringify(snapshot, null, 2));
|
|
50
|
-
|
|
51
|
-
return snapshot;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Load previous snapshot
|
|
56
|
-
*/
|
|
57
|
-
function loadSnapshot(projectPath) {
|
|
58
|
-
const snapshotFile = path.join(projectPath, ".vibecheck", "context-snapshot.json");
|
|
59
|
-
|
|
60
|
-
if (!fs.existsSync(snapshotFile)) {
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
return JSON.parse(fs.readFileSync(snapshotFile, "utf-8"));
|
|
66
|
-
} catch {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Generate diff between contexts
|
|
73
|
-
*/
|
|
74
|
-
function generateContextDiff(previous, current) {
|
|
75
|
-
if (!previous) {
|
|
76
|
-
return {
|
|
77
|
-
isFirstRun: true,
|
|
78
|
-
changes: {
|
|
79
|
-
added: [],
|
|
80
|
-
removed: [],
|
|
81
|
-
modified: [],
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const diff = {
|
|
87
|
-
timestamp: new Date().toISOString(),
|
|
88
|
-
previousTimestamp: previous.timestamp,
|
|
89
|
-
changes: {
|
|
90
|
-
added: [],
|
|
91
|
-
removed: [],
|
|
92
|
-
modified: [],
|
|
93
|
-
},
|
|
94
|
-
summary: {
|
|
95
|
-
totalChanges: 0,
|
|
96
|
-
breakingChanges: 0,
|
|
97
|
-
additions: 0,
|
|
98
|
-
removals: 0,
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
// Compare tech stack
|
|
103
|
-
if (previous.context.techStack && current.techStack) {
|
|
104
|
-
for (const [key, value] of Object.entries(current.techStack)) {
|
|
105
|
-
if (previous.context.techStack[key] !== value) {
|
|
106
|
-
diff.changes.modified.push({
|
|
107
|
-
type: "techStack",
|
|
108
|
-
field: key,
|
|
109
|
-
from: previous.context.techStack[key],
|
|
110
|
-
to: value,
|
|
111
|
-
});
|
|
112
|
-
diff.summary.totalChanges++;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Compare directories
|
|
118
|
-
const prevDirs = new Set(previous.context.structure?.directories || []);
|
|
119
|
-
const currDirs = new Set(current.structure?.directories || []);
|
|
120
|
-
|
|
121
|
-
for (const dir of currDirs) {
|
|
122
|
-
if (!prevDirs.has(dir)) {
|
|
123
|
-
diff.changes.added.push({
|
|
124
|
-
type: "directory",
|
|
125
|
-
name: dir,
|
|
126
|
-
});
|
|
127
|
-
diff.summary.additions++;
|
|
128
|
-
diff.summary.totalChanges++;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
for (const dir of prevDirs) {
|
|
133
|
-
if (!currDirs.has(dir)) {
|
|
134
|
-
diff.changes.removed.push({
|
|
135
|
-
type: "directory",
|
|
136
|
-
name: dir,
|
|
137
|
-
});
|
|
138
|
-
diff.summary.removals++;
|
|
139
|
-
diff.summary.totalChanges++;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Compare components
|
|
144
|
-
const prevComponents = new Set(previous.context.structure?.components || []);
|
|
145
|
-
const currComponents = new Set(current.structure?.components || []);
|
|
146
|
-
|
|
147
|
-
for (const comp of currComponents) {
|
|
148
|
-
if (!prevComponents.has(comp)) {
|
|
149
|
-
diff.changes.added.push({
|
|
150
|
-
type: "component",
|
|
151
|
-
name: comp,
|
|
152
|
-
});
|
|
153
|
-
diff.summary.additions++;
|
|
154
|
-
diff.summary.totalChanges++;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
for (const comp of prevComponents) {
|
|
159
|
-
if (!currComponents.has(comp)) {
|
|
160
|
-
diff.changes.removed.push({
|
|
161
|
-
type: "component",
|
|
162
|
-
name: comp,
|
|
163
|
-
});
|
|
164
|
-
diff.summary.removals++;
|
|
165
|
-
diff.summary.totalChanges++;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Compare API routes
|
|
170
|
-
const prevRoutes = new Set(previous.context.structure?.apiRoutes || []);
|
|
171
|
-
const currRoutes = new Set(current.structure?.apiRoutes || []);
|
|
172
|
-
|
|
173
|
-
for (const route of currRoutes) {
|
|
174
|
-
if (!prevRoutes.has(route)) {
|
|
175
|
-
diff.changes.added.push({
|
|
176
|
-
type: "apiRoute",
|
|
177
|
-
name: route,
|
|
178
|
-
});
|
|
179
|
-
diff.summary.additions++;
|
|
180
|
-
diff.summary.totalChanges++;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
for (const route of prevRoutes) {
|
|
185
|
-
if (!currRoutes.has(route)) {
|
|
186
|
-
diff.changes.removed.push({
|
|
187
|
-
type: "apiRoute",
|
|
188
|
-
name: route,
|
|
189
|
-
});
|
|
190
|
-
diff.summary.removals++;
|
|
191
|
-
diff.summary.totalChanges++;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Compare patterns
|
|
196
|
-
const prevHooks = new Set(previous.context.patterns?.hooks || []);
|
|
197
|
-
const currHooks = new Set(current.patterns?.hooks || []);
|
|
198
|
-
|
|
199
|
-
for (const hook of currHooks) {
|
|
200
|
-
if (!prevHooks.has(hook)) {
|
|
201
|
-
diff.changes.added.push({
|
|
202
|
-
type: "hook",
|
|
203
|
-
name: hook,
|
|
204
|
-
});
|
|
205
|
-
diff.summary.additions++;
|
|
206
|
-
diff.summary.totalChanges++;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Check for breaking changes
|
|
211
|
-
if (previous.context.patterns?.stateManagement !== current.patterns?.stateManagement) {
|
|
212
|
-
diff.changes.modified.push({
|
|
213
|
-
type: "breaking",
|
|
214
|
-
field: "stateManagement",
|
|
215
|
-
from: previous.context.patterns?.stateManagement,
|
|
216
|
-
to: current.patterns?.stateManagement,
|
|
217
|
-
});
|
|
218
|
-
diff.summary.breakingChanges++;
|
|
219
|
-
diff.summary.totalChanges++;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if (previous.context.patterns?.validation !== current.patterns?.validation) {
|
|
223
|
-
diff.changes.modified.push({
|
|
224
|
-
type: "breaking",
|
|
225
|
-
field: "validation",
|
|
226
|
-
from: previous.context.patterns?.validation,
|
|
227
|
-
to: current.patterns?.validation,
|
|
228
|
-
});
|
|
229
|
-
diff.summary.breakingChanges++;
|
|
230
|
-
diff.summary.totalChanges++;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return diff;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Generate diff report
|
|
238
|
-
*/
|
|
239
|
-
function generateDiffReport(diff) {
|
|
240
|
-
if (diff.isFirstRun) {
|
|
241
|
-
return `
|
|
242
|
-
# First Context Generation
|
|
243
|
-
|
|
244
|
-
This is the first time context has been generated for this project.
|
|
245
|
-
`;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
let report = `# Context Changes Report
|
|
249
|
-
Generated: ${new Date(diff.timestamp).toLocaleString()}
|
|
250
|
-
Since: ${new Date(diff.previousTimestamp).toLocaleString()}
|
|
251
|
-
|
|
252
|
-
## Summary
|
|
253
|
-
- Total Changes: ${diff.summary.totalChanges}
|
|
254
|
-
- Additions: ${diff.summary.additions}
|
|
255
|
-
- Removals: ${diff.summary.removals}
|
|
256
|
-
- Breaking Changes: ${diff.summary.breakingChanges}
|
|
257
|
-
|
|
258
|
-
`;
|
|
259
|
-
|
|
260
|
-
if (diff.changes.added.length > 0) {
|
|
261
|
-
report += "## Added\n\n";
|
|
262
|
-
for (const change of diff.changes.added) {
|
|
263
|
-
const icon = {
|
|
264
|
-
directory: "📁",
|
|
265
|
-
component: "🧩",
|
|
266
|
-
apiRoute: "🔌",
|
|
267
|
-
hook: "🪝",
|
|
268
|
-
}[change.type] || "➕";
|
|
269
|
-
|
|
270
|
-
report += `- ${icon} **${change.type}**: ${change.name}\n`;
|
|
271
|
-
}
|
|
272
|
-
report += "\n";
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (diff.changes.removed.length > 0) {
|
|
276
|
-
report += "## Removed\n\n";
|
|
277
|
-
for (const change of diff.changes.removed) {
|
|
278
|
-
const icon = {
|
|
279
|
-
directory: "📁",
|
|
280
|
-
component: "🧩",
|
|
281
|
-
apiRoute: "🔌",
|
|
282
|
-
hook: "🪝",
|
|
283
|
-
}[change.type] || "➖";
|
|
284
|
-
|
|
285
|
-
report += `- ${icon} **${change.type}**: ${change.name}\n`;
|
|
286
|
-
}
|
|
287
|
-
report += "\n";
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (diff.changes.modified.length > 0) {
|
|
291
|
-
report += "## Modified\n\n";
|
|
292
|
-
for (const change of diff.changes.modified) {
|
|
293
|
-
const icon = change.type === "breaking" ? "⚠️" : "🔄";
|
|
294
|
-
report += `- ${icon} **${change.field}**: \`${change.from}\` → \`${change.to}\`\n`;
|
|
295
|
-
}
|
|
296
|
-
report += "\n";
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
if (diff.summary.breakingChanges > 0) {
|
|
300
|
-
report += "## ⚠️ Breaking Changes\n\n";
|
|
301
|
-
report += "Breaking changes detected. Review AI rules files to ensure compatibility.\n\n";
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return report;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Save diff report
|
|
309
|
-
*/
|
|
310
|
-
function saveDiffReport(projectPath, diff) {
|
|
311
|
-
const vibecheckDir = path.join(projectPath, ".vibecheck");
|
|
312
|
-
if (!fs.existsSync(vibecheckDir)) {
|
|
313
|
-
fs.mkdirSync(vibecheckDir, { recursive: true });
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const report = generateDiffReport(diff);
|
|
317
|
-
const reportFile = path.join(vibecheckDir, "context-diff.md");
|
|
318
|
-
|
|
319
|
-
fs.writeFileSync(reportFile, report);
|
|
320
|
-
|
|
321
|
-
return reportFile;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Check if context needs regeneration
|
|
326
|
-
*/
|
|
327
|
-
function needsRegeneration(projectPath, currentContext) {
|
|
328
|
-
const previous = loadSnapshot(projectPath);
|
|
329
|
-
if (!previous) return true;
|
|
330
|
-
|
|
331
|
-
const currentHash = generateContextHash(currentContext);
|
|
332
|
-
return previous.hash !== currentHash;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
module.exports = {
|
|
336
|
-
saveSnapshot,
|
|
337
|
-
loadSnapshot,
|
|
338
|
-
generateContextDiff,
|
|
339
|
-
generateDiffReport,
|
|
340
|
-
saveDiffReport,
|
|
341
|
-
needsRegeneration,
|
|
342
|
-
};
|
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Smart Context Pruning Module
|
|
3
|
-
* Reduces context to most relevant subset based on current file/task
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const fs = require("fs");
|
|
7
|
-
const path = require("path");
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Find files recursively (local helper)
|
|
11
|
-
*/
|
|
12
|
-
function findFiles(dir, extensions, maxDepth = 5, currentDepth = 0) {
|
|
13
|
-
if (currentDepth >= maxDepth || !fs.existsSync(dir)) return [];
|
|
14
|
-
|
|
15
|
-
const files = [];
|
|
16
|
-
try {
|
|
17
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
18
|
-
for (const entry of entries) {
|
|
19
|
-
const fullPath = path.join(dir, entry.name);
|
|
20
|
-
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
21
|
-
files.push(...findFiles(fullPath, extensions, maxDepth, currentDepth + 1));
|
|
22
|
-
} else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
|
|
23
|
-
files.push(fullPath);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
} catch {}
|
|
27
|
-
return files;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Calculate relevance score for a file based on context
|
|
32
|
-
*/
|
|
33
|
-
function calculateRelevanceScore(filePath, context, analysis) {
|
|
34
|
-
let score = 0;
|
|
35
|
-
const relativePath = path.relative(process.cwd(), filePath).replace(/\\/g, "/");
|
|
36
|
-
|
|
37
|
-
// Base score for all files
|
|
38
|
-
score += 1;
|
|
39
|
-
|
|
40
|
-
// Boost for same directory
|
|
41
|
-
if (context.currentFile) {
|
|
42
|
-
const currentDir = path.dirname(context.currentFile);
|
|
43
|
-
const fileDir = path.dirname(relativePath);
|
|
44
|
-
if (currentDir === fileDir) score += 5;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Boost for shared directory levels
|
|
48
|
-
if (context.currentFile) {
|
|
49
|
-
const currentParts = context.currentFile.split("/");
|
|
50
|
-
const fileParts = relativePath.split("/");
|
|
51
|
-
const sharedLevels = Math.min(
|
|
52
|
-
currentParts.length - 1,
|
|
53
|
-
fileParts.length - 1
|
|
54
|
-
);
|
|
55
|
-
for (let i = 0; i < sharedLevels; i++) {
|
|
56
|
-
if (currentParts[i] === fileParts[i]) {
|
|
57
|
-
score += 2;
|
|
58
|
-
} else {
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Boost for commonly imported files
|
|
65
|
-
if (analysis.imports?.internalAliases) {
|
|
66
|
-
for (const alias of analysis.imports.internalAliases) {
|
|
67
|
-
if (relativePath.startsWith(alias.replace("@", "src/"))) {
|
|
68
|
-
score += 3;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Boost based on file type
|
|
74
|
-
const ext = path.extname(relativePath);
|
|
75
|
-
const baseName = path.basename(relativePath, ext);
|
|
76
|
-
|
|
77
|
-
// Components are highly relevant
|
|
78
|
-
if (relativePath.includes("/components/") && /^[A-Z]/.test(baseName)) {
|
|
79
|
-
score += 4;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Hooks are moderately relevant
|
|
83
|
-
if (relativePath.includes("/hooks/") || baseName.startsWith("use")) {
|
|
84
|
-
score += 3;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// API routes for API-related tasks
|
|
88
|
-
if (context.task === "api" && relativePath.includes("/api/")) {
|
|
89
|
-
score += 5;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Utils for utility tasks
|
|
93
|
-
if (context.task === "utility" && (relativePath.includes("/utils/") || relativePath.includes("/lib/"))) {
|
|
94
|
-
score += 4;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Boost for files with many imports (likely core files)
|
|
98
|
-
const fileStats = analysis.stats?.byExtension || {};
|
|
99
|
-
if (fileStats[ext]) {
|
|
100
|
-
score += Math.min(2, fileStats[ext] / 10);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Boost for recently modified files (if git info available)
|
|
104
|
-
if (context.recentFiles && context.recentFiles.includes(relativePath)) {
|
|
105
|
-
score += 3;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Boost for files mentioned in custom hooks
|
|
109
|
-
if (analysis.patterns?.hooks?.includes(baseName)) {
|
|
110
|
-
score += 2;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return score;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Prune context to most relevant files
|
|
118
|
-
*/
|
|
119
|
-
function pruneContext(analysis, context = {}) {
|
|
120
|
-
const {
|
|
121
|
-
maxTokens = 8000,
|
|
122
|
-
currentFile = "",
|
|
123
|
-
task = "general",
|
|
124
|
-
includeTypes = ["components", "hooks", "utils", "api"],
|
|
125
|
-
excludePatterns = ["*.test.*", "*.spec.*", "node_modules"],
|
|
126
|
-
} = context;
|
|
127
|
-
|
|
128
|
-
// Get all relevant files
|
|
129
|
-
const allFiles = findFiles(process.cwd(), [".ts", ".tsx", ".js", ".jsx"], 5);
|
|
130
|
-
|
|
131
|
-
// Filter by type and exclude patterns
|
|
132
|
-
const filteredFiles = allFiles.filter(file => {
|
|
133
|
-
const relativePath = path.relative(process.cwd(), file).replace(/\\/g, "/");
|
|
134
|
-
|
|
135
|
-
// Check exclude patterns
|
|
136
|
-
for (const pattern of excludePatterns) {
|
|
137
|
-
if (relativePath.includes(pattern.replace("*", ""))) {
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Check include types
|
|
143
|
-
const isInComponents = relativePath.includes("/components/");
|
|
144
|
-
const isInHooks = relativePath.includes("/hooks/") || path.basename(file).startsWith("use");
|
|
145
|
-
const isInUtils = relativePath.includes("/utils/") || relativePath.includes("/lib/");
|
|
146
|
-
const isInApi = relativePath.includes("/api/") || relativePath.includes("/routes/");
|
|
147
|
-
|
|
148
|
-
return (
|
|
149
|
-
(includeTypes.includes("components") && isInComponents) ||
|
|
150
|
-
(includeTypes.includes("hooks") && isInHooks) ||
|
|
151
|
-
(includeTypes.includes("utils") && isInUtils) ||
|
|
152
|
-
(includeTypes.includes("api") && isInApi) ||
|
|
153
|
-
!includeTypes.length
|
|
154
|
-
);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// Score and rank files
|
|
158
|
-
const scoredFiles = filteredFiles.map(file => ({
|
|
159
|
-
path: file,
|
|
160
|
-
relativePath: path.relative(process.cwd(), file).replace(/\\/g, "/"),
|
|
161
|
-
score: calculateRelevanceScore(file, context, analysis),
|
|
162
|
-
}));
|
|
163
|
-
|
|
164
|
-
// Sort by score (descending)
|
|
165
|
-
scoredFiles.sort((a, b) => b.score - a.score);
|
|
166
|
-
|
|
167
|
-
// Estimate token usage (rough estimate: ~4 tokens per character)
|
|
168
|
-
let totalTokens = 0;
|
|
169
|
-
const selectedFiles = [];
|
|
170
|
-
|
|
171
|
-
for (const file of scoredFiles) {
|
|
172
|
-
try {
|
|
173
|
-
const content = fs.readFileSync(file.path, "utf-8");
|
|
174
|
-
const fileTokens = content.length / 4;
|
|
175
|
-
|
|
176
|
-
if (totalTokens + fileTokens <= maxTokens) {
|
|
177
|
-
selectedFiles.push({
|
|
178
|
-
...file,
|
|
179
|
-
content,
|
|
180
|
-
tokens: Math.round(fileTokens),
|
|
181
|
-
});
|
|
182
|
-
totalTokens += fileTokens;
|
|
183
|
-
} else {
|
|
184
|
-
// Try to include partial content for important files
|
|
185
|
-
if (file.score > 5) {
|
|
186
|
-
const remainingTokens = maxTokens - totalTokens;
|
|
187
|
-
const charsToFit = remainingTokens * 4;
|
|
188
|
-
const partialContent = content.slice(0, charsToFit) + "\n... [truncated]";
|
|
189
|
-
|
|
190
|
-
selectedFiles.push({
|
|
191
|
-
...file,
|
|
192
|
-
content: partialContent,
|
|
193
|
-
tokens: Math.round(partialContent.length / 4),
|
|
194
|
-
truncated: true,
|
|
195
|
-
});
|
|
196
|
-
totalTokens += partialContent.length / 4;
|
|
197
|
-
}
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
} catch {}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return {
|
|
204
|
-
files: selectedFiles,
|
|
205
|
-
totalTokens: Math.round(totalTokens),
|
|
206
|
-
maxTokens,
|
|
207
|
-
includedCount: selectedFiles.length,
|
|
208
|
-
totalCount: filteredFiles.length,
|
|
209
|
-
pruned: filteredFiles.length - selectedFiles.length,
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Generate pruned context JSON for MCP
|
|
215
|
-
*/
|
|
216
|
-
function generatePrunedContext(analysis, context = {}) {
|
|
217
|
-
const pruned = pruneContext(analysis, context);
|
|
218
|
-
|
|
219
|
-
return {
|
|
220
|
-
version: "3.0.0-pruned",
|
|
221
|
-
generatedAt: new Date().toISOString(),
|
|
222
|
-
context: {
|
|
223
|
-
currentFile: context.currentFile || "",
|
|
224
|
-
task: context.task || "general",
|
|
225
|
-
maxTokens: context.maxTokens || 8000,
|
|
226
|
-
},
|
|
227
|
-
summary: {
|
|
228
|
-
totalFiles: pruned.totalCount,
|
|
229
|
-
includedFiles: pruned.includedCount,
|
|
230
|
-
prunedFiles: pruned.pruned,
|
|
231
|
-
estimatedTokens: pruned.totalTokens,
|
|
232
|
-
},
|
|
233
|
-
files: pruned.files.map(f => ({
|
|
234
|
-
path: f.relativePath,
|
|
235
|
-
score: f.score,
|
|
236
|
-
tokens: f.tokens,
|
|
237
|
-
truncated: f.truncated || false,
|
|
238
|
-
content: f.content,
|
|
239
|
-
})),
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Get context for specific file
|
|
245
|
-
*/
|
|
246
|
-
function getContextForFile(filePath, analysis) {
|
|
247
|
-
const relativePath = path.relative(process.cwd(), filePath).replace(/\\/g, "/");
|
|
248
|
-
|
|
249
|
-
// Extract imports from current file
|
|
250
|
-
let imports = [];
|
|
251
|
-
try {
|
|
252
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
253
|
-
const importMatches = content.match(/import\s+.*?\s+from\s+['"]([^'"]+)['"]/g) || [];
|
|
254
|
-
|
|
255
|
-
for (const match of importMatches) {
|
|
256
|
-
const source = match.match(/from\s+['"]([^'"]+)['"]/)?.[1];
|
|
257
|
-
if (source && source.startsWith(".")) {
|
|
258
|
-
// Resolve relative import
|
|
259
|
-
const fromDir = path.dirname(relativePath);
|
|
260
|
-
const resolvedPath = path.resolve(process.cwd(), fromDir, source);
|
|
261
|
-
imports.push(path.relative(process.cwd(), resolvedPath).replace(/\\/g, "/"));
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
} catch {}
|
|
265
|
-
|
|
266
|
-
// Prune with focus on imported files
|
|
267
|
-
const context = generatePrunedContext(analysis, {
|
|
268
|
-
currentFile: relativePath,
|
|
269
|
-
maxTokens: 6000,
|
|
270
|
-
task: "file-specific",
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
// Boost imported files in the results
|
|
274
|
-
for (const file of context.files) {
|
|
275
|
-
if (imports.includes(file.path)) {
|
|
276
|
-
file.score += 10;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Re-sort
|
|
281
|
-
context.files.sort((a, b) => b.score - a.score);
|
|
282
|
-
|
|
283
|
-
return context;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
module.exports = {
|
|
287
|
-
pruneContext,
|
|
288
|
-
generatePrunedContext,
|
|
289
|
-
getContextForFile,
|
|
290
|
-
calculateRelevanceScore,
|
|
291
|
-
};
|