@vibecheckai/cli 3.7.0 → 3.8.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 +135 -63
- package/bin/_deprecations.js +447 -19
- package/bin/_router.js +1 -1
- package/bin/registry.js +347 -280
- package/bin/runners/context/generators/cursor-enhanced.js +2439 -0
- package/bin/runners/lib/agent-firewall/enforcement/gateway.js +1059 -0
- package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -0
- package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -0
- package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -0
- package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/change-event.schema.json +173 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/intent.schema.json +181 -0
- package/bin/runners/lib/agent-firewall/enforcement/schemas/verdict.schema.json +222 -0
- package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -0
- package/bin/runners/lib/agent-firewall/index.js +200 -0
- package/bin/runners/lib/agent-firewall/integration/index.js +20 -0
- package/bin/runners/lib/agent-firewall/integration/ship-gate.js +437 -0
- package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +622 -0
- package/bin/runners/lib/agent-firewall/intent/auto-detect.js +426 -0
- package/bin/runners/lib/agent-firewall/intent/index.js +102 -0
- package/bin/runners/lib/agent-firewall/intent/schema.js +352 -0
- package/bin/runners/lib/agent-firewall/intent/store.js +283 -0
- package/bin/runners/lib/agent-firewall/interception/fs-interceptor.js +502 -0
- package/bin/runners/lib/agent-firewall/interception/index.js +23 -0
- package/bin/runners/lib/agent-firewall/session/collector.js +451 -0
- package/bin/runners/lib/agent-firewall/session/index.js +26 -0
- package/bin/runners/lib/artifact-envelope.js +540 -0
- package/bin/runners/lib/auth-shared.js +977 -0
- package/bin/runners/lib/checkpoint.js +941 -0
- package/bin/runners/lib/cleanup/engine.js +571 -0
- package/bin/runners/lib/cleanup/index.js +53 -0
- package/bin/runners/lib/cleanup/output.js +375 -0
- package/bin/runners/lib/cleanup/rules.js +1060 -0
- package/bin/runners/lib/doctor/diagnosis-receipt.js +454 -0
- package/bin/runners/lib/doctor/failure-signatures.js +526 -0
- package/bin/runners/lib/doctor/fix-script.js +336 -0
- package/bin/runners/lib/doctor/modules/build-tools.js +453 -0
- package/bin/runners/lib/doctor/modules/index.js +62 -3
- package/bin/runners/lib/doctor/modules/os-quirks.js +706 -0
- package/bin/runners/lib/doctor/modules/repo-integrity.js +485 -0
- package/bin/runners/lib/doctor/safe-repair.js +384 -0
- package/bin/runners/lib/engines/attack-detector.js +1192 -0
- package/bin/runners/lib/entitlements-v2.js +2 -2
- package/bin/runners/lib/missions/briefing.js +427 -0
- package/bin/runners/lib/missions/checkpoint.js +753 -0
- package/bin/runners/lib/missions/hardening.js +851 -0
- package/bin/runners/lib/missions/plan.js +421 -32
- package/bin/runners/lib/missions/safety-gates.js +645 -0
- package/bin/runners/lib/missions/schema.js +478 -0
- package/bin/runners/lib/packs/bundle.js +675 -0
- package/bin/runners/lib/packs/evidence-pack.js +671 -0
- package/bin/runners/lib/packs/pack-factory.js +837 -0
- package/bin/runners/lib/packs/permissions-pack.js +686 -0
- package/bin/runners/lib/packs/proof-graph-pack.js +779 -0
- package/bin/runners/lib/safelist/index.js +96 -0
- package/bin/runners/lib/safelist/integration.js +334 -0
- package/bin/runners/lib/safelist/matcher.js +696 -0
- package/bin/runners/lib/safelist/schema.js +948 -0
- package/bin/runners/lib/safelist/store.js +438 -0
- package/bin/runners/lib/schemas/ship-manifest.schema.json +251 -0
- package/bin/runners/lib/ship-gate.js +832 -0
- package/bin/runners/lib/ship-manifest.js +1153 -0
- package/bin/runners/lib/ship-output.js +1 -1
- package/bin/runners/lib/unified-cli-output.js +710 -383
- package/bin/runners/lib/upsell.js +3 -3
- package/bin/runners/lib/why-tree.js +650 -0
- package/bin/runners/runAllowlist.js +33 -4
- package/bin/runners/runApprove.js +240 -1122
- package/bin/runners/runAudit.js +692 -0
- package/bin/runners/runAuth.js +325 -29
- package/bin/runners/runCheckpoint.js +442 -494
- package/bin/runners/runCleanup.js +343 -0
- package/bin/runners/runDoctor.js +269 -19
- package/bin/runners/runFix.js +411 -32
- package/bin/runners/runForge.js +411 -0
- package/bin/runners/runIntent.js +906 -0
- package/bin/runners/runKickoff.js +878 -0
- package/bin/runners/runLaunch.js +2000 -0
- package/bin/runners/runLink.js +785 -0
- package/bin/runners/runMcp.js +1741 -837
- package/bin/runners/runPacks.js +2089 -0
- package/bin/runners/runPolish.js +41 -0
- package/bin/runners/runSafelist.js +1190 -0
- package/bin/runners/runScan.js +21 -9
- package/bin/runners/runShield.js +1282 -0
- package/bin/runners/runShip.js +395 -16
- package/bin/vibecheck.js +34 -6
- package/mcp-server/README.md +117 -158
- package/mcp-server/handlers/tool-handler.ts +3 -3
- package/mcp-server/index.js +16 -0
- package/mcp-server/intent-firewall-interceptor.js +529 -0
- package/mcp-server/manifest.json +473 -0
- package/mcp-server/package.json +1 -1
- package/mcp-server/registry/tool-registry.js +315 -523
- package/mcp-server/registry/tools.json +442 -428
- package/mcp-server/tier-auth.js +68 -11
- package/mcp-server/tools-v3.js +70 -16
- package/package.json +1 -1
- package/bin/runners/runProof.zip +0 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repository Integrity Diagnostics Module
|
|
3
|
+
*
|
|
4
|
+
* Checks git state, lock files, caches, and repo health
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { execSync } = require('child_process');
|
|
10
|
+
const { SEVERITY, CATEGORY, FIX_TYPE } = require('../types');
|
|
11
|
+
|
|
12
|
+
const MODULE_ID = 'repo';
|
|
13
|
+
|
|
14
|
+
function createDiagnostics(projectPath) {
|
|
15
|
+
return [
|
|
16
|
+
{
|
|
17
|
+
id: `${MODULE_ID}.git_status`,
|
|
18
|
+
name: 'Git Repository',
|
|
19
|
+
category: CATEGORY.PROJECT,
|
|
20
|
+
parallel: true,
|
|
21
|
+
check: async () => {
|
|
22
|
+
const gitDir = path.join(projectPath, '.git');
|
|
23
|
+
|
|
24
|
+
if (!fs.existsSync(gitDir)) {
|
|
25
|
+
return {
|
|
26
|
+
severity: SEVERITY.INFO,
|
|
27
|
+
message: 'Not a git repository',
|
|
28
|
+
metadata: { isGitRepo: false },
|
|
29
|
+
fixes: [{
|
|
30
|
+
type: FIX_TYPE.COMMAND,
|
|
31
|
+
description: 'Initialize git repository',
|
|
32
|
+
command: 'git init',
|
|
33
|
+
autoFixable: true,
|
|
34
|
+
}],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// Check git status
|
|
40
|
+
const status = execSync('git status --porcelain', {
|
|
41
|
+
cwd: projectPath,
|
|
42
|
+
encoding: 'utf8',
|
|
43
|
+
timeout: 10000,
|
|
44
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const lines = status.trim().split('\n').filter(Boolean);
|
|
48
|
+
const untracked = lines.filter(l => l.startsWith('??')).length;
|
|
49
|
+
const modified = lines.filter(l => l.startsWith(' M') || l.startsWith('M ')).length;
|
|
50
|
+
const staged = lines.filter(l => l.startsWith('A ') || l.startsWith('M ') || l.startsWith('D ')).length;
|
|
51
|
+
|
|
52
|
+
const metadata = { isGitRepo: true, untracked, modified, staged, total: lines.length };
|
|
53
|
+
|
|
54
|
+
if (lines.length === 0) {
|
|
55
|
+
return {
|
|
56
|
+
severity: SEVERITY.PASS,
|
|
57
|
+
message: 'Clean working directory',
|
|
58
|
+
metadata,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
severity: SEVERITY.INFO,
|
|
64
|
+
message: `${lines.length} uncommitted changes`,
|
|
65
|
+
detail: `${modified} modified, ${untracked} untracked, ${staged} staged`,
|
|
66
|
+
metadata,
|
|
67
|
+
};
|
|
68
|
+
} catch (err) {
|
|
69
|
+
return {
|
|
70
|
+
severity: SEVERITY.WARNING,
|
|
71
|
+
message: 'Git command failed',
|
|
72
|
+
detail: err.message,
|
|
73
|
+
metadata: { isGitRepo: true },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: `${MODULE_ID}.git_hooks`,
|
|
80
|
+
name: 'Git Hooks',
|
|
81
|
+
category: CATEGORY.PROJECT,
|
|
82
|
+
parallel: true,
|
|
83
|
+
check: async () => {
|
|
84
|
+
// Check for Husky
|
|
85
|
+
const huskyDir = path.join(projectPath, '.husky');
|
|
86
|
+
const hasHusky = fs.existsSync(huskyDir);
|
|
87
|
+
|
|
88
|
+
// Check for lefthook
|
|
89
|
+
const leftHookConfig = ['lefthook.yml', 'lefthook.yaml', '.lefthook.yml'];
|
|
90
|
+
const hasLefthook = leftHookConfig.some(f => fs.existsSync(path.join(projectPath, f)));
|
|
91
|
+
|
|
92
|
+
// Check for simple-git-hooks
|
|
93
|
+
const pkgPath = path.join(projectPath, 'package.json');
|
|
94
|
+
let hasSimpleHooks = false;
|
|
95
|
+
if (fs.existsSync(pkgPath)) {
|
|
96
|
+
try {
|
|
97
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
98
|
+
hasSimpleHooks = !!pkg['simple-git-hooks'];
|
|
99
|
+
} catch {}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const metadata = { husky: hasHusky, lefthook: hasLefthook, simpleGitHooks: hasSimpleHooks };
|
|
103
|
+
|
|
104
|
+
if (hasHusky) {
|
|
105
|
+
return { severity: SEVERITY.PASS, message: 'Husky configured', metadata };
|
|
106
|
+
}
|
|
107
|
+
if (hasLefthook) {
|
|
108
|
+
return { severity: SEVERITY.PASS, message: 'Lefthook configured', metadata };
|
|
109
|
+
}
|
|
110
|
+
if (hasSimpleHooks) {
|
|
111
|
+
return { severity: SEVERITY.PASS, message: 'simple-git-hooks configured', metadata };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
severity: SEVERITY.INFO,
|
|
116
|
+
message: 'No git hooks configured',
|
|
117
|
+
metadata,
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: `${MODULE_ID}.lock_file`,
|
|
123
|
+
name: 'Lock File',
|
|
124
|
+
category: CATEGORY.PROJECT,
|
|
125
|
+
parallel: true,
|
|
126
|
+
check: async () => {
|
|
127
|
+
const lockFiles = {
|
|
128
|
+
'pnpm-lock.yaml': 'pnpm',
|
|
129
|
+
'package-lock.json': 'npm',
|
|
130
|
+
'yarn.lock': 'yarn',
|
|
131
|
+
'bun.lockb': 'bun',
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const found = [];
|
|
135
|
+
for (const [file, manager] of Object.entries(lockFiles)) {
|
|
136
|
+
if (fs.existsSync(path.join(projectPath, file))) {
|
|
137
|
+
found.push({ file, manager });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const metadata = { lockFiles: found };
|
|
142
|
+
|
|
143
|
+
if (found.length === 0) {
|
|
144
|
+
return {
|
|
145
|
+
severity: SEVERITY.WARNING,
|
|
146
|
+
message: 'No lock file found',
|
|
147
|
+
detail: 'Lock files ensure reproducible builds',
|
|
148
|
+
metadata,
|
|
149
|
+
fixes: [{
|
|
150
|
+
type: FIX_TYPE.COMMAND,
|
|
151
|
+
description: 'Generate lock file',
|
|
152
|
+
command: 'npm install',
|
|
153
|
+
autoFixable: true,
|
|
154
|
+
}],
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (found.length > 1) {
|
|
159
|
+
return {
|
|
160
|
+
severity: SEVERITY.WARNING,
|
|
161
|
+
message: `Multiple lock files: ${found.map(f => f.file).join(', ')}`,
|
|
162
|
+
detail: 'Having multiple lock files can cause confusion and inconsistent installs',
|
|
163
|
+
metadata,
|
|
164
|
+
fixes: [{
|
|
165
|
+
type: FIX_TYPE.MANUAL,
|
|
166
|
+
description: 'Remove extra lock files, keep only one for your chosen package manager',
|
|
167
|
+
autoFixable: false,
|
|
168
|
+
}],
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const primary = found[0];
|
|
173
|
+
return {
|
|
174
|
+
severity: SEVERITY.PASS,
|
|
175
|
+
message: `${primary.file} (${primary.manager})`,
|
|
176
|
+
metadata,
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: `${MODULE_ID}.lock_file_sync`,
|
|
182
|
+
name: 'Lock File Sync',
|
|
183
|
+
category: CATEGORY.PROJECT,
|
|
184
|
+
parallel: false,
|
|
185
|
+
dependsOn: [`${MODULE_ID}.lock_file`],
|
|
186
|
+
check: async () => {
|
|
187
|
+
const pkgPath = path.join(projectPath, 'package.json');
|
|
188
|
+
const lockPath = path.join(projectPath, 'package-lock.json');
|
|
189
|
+
const pnpmLockPath = path.join(projectPath, 'pnpm-lock.yaml');
|
|
190
|
+
const yarnLockPath = path.join(projectPath, 'yarn.lock');
|
|
191
|
+
|
|
192
|
+
if (!fs.existsSync(pkgPath)) {
|
|
193
|
+
return { severity: SEVERITY.INFO, message: 'No package.json' };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let lockFile = null;
|
|
197
|
+
let command = null;
|
|
198
|
+
|
|
199
|
+
if (fs.existsSync(pnpmLockPath)) {
|
|
200
|
+
lockFile = pnpmLockPath;
|
|
201
|
+
command = 'pnpm install --frozen-lockfile';
|
|
202
|
+
} else if (fs.existsSync(lockPath)) {
|
|
203
|
+
lockFile = lockPath;
|
|
204
|
+
command = 'npm ci';
|
|
205
|
+
} else if (fs.existsSync(yarnLockPath)) {
|
|
206
|
+
lockFile = yarnLockPath;
|
|
207
|
+
command = 'yarn install --frozen-lockfile';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!lockFile) {
|
|
211
|
+
return { severity: SEVERITY.INFO, message: 'No lock file to check' };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check modification times
|
|
215
|
+
const pkgStat = fs.statSync(pkgPath);
|
|
216
|
+
const lockStat = fs.statSync(lockFile);
|
|
217
|
+
|
|
218
|
+
const metadata = {
|
|
219
|
+
packageJsonModified: pkgStat.mtime.toISOString(),
|
|
220
|
+
lockFileModified: lockStat.mtime.toISOString(),
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// If package.json is newer than lock file, might be out of sync
|
|
224
|
+
if (pkgStat.mtime > lockStat.mtime) {
|
|
225
|
+
const hoursDiff = (pkgStat.mtime - lockStat.mtime) / (1000 * 60 * 60);
|
|
226
|
+
|
|
227
|
+
if (hoursDiff > 1) {
|
|
228
|
+
return {
|
|
229
|
+
severity: SEVERITY.WARNING,
|
|
230
|
+
message: 'Lock file may be outdated',
|
|
231
|
+
detail: `package.json modified ${Math.round(hoursDiff)}h after lock file`,
|
|
232
|
+
metadata,
|
|
233
|
+
fixes: [{
|
|
234
|
+
type: FIX_TYPE.COMMAND,
|
|
235
|
+
description: 'Regenerate lock file',
|
|
236
|
+
command: command.replace('--frozen-lockfile', '').replace(' ci', ' install'),
|
|
237
|
+
autoFixable: true,
|
|
238
|
+
}],
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
severity: SEVERITY.PASS,
|
|
245
|
+
message: 'Lock file appears synchronized',
|
|
246
|
+
metadata,
|
|
247
|
+
};
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
id: `${MODULE_ID}.node_modules`,
|
|
252
|
+
name: 'node_modules',
|
|
253
|
+
category: CATEGORY.PROJECT,
|
|
254
|
+
parallel: true,
|
|
255
|
+
check: async () => {
|
|
256
|
+
const nmPath = path.join(projectPath, 'node_modules');
|
|
257
|
+
|
|
258
|
+
if (!fs.existsSync(nmPath)) {
|
|
259
|
+
return {
|
|
260
|
+
severity: SEVERITY.ERROR,
|
|
261
|
+
message: 'node_modules not found',
|
|
262
|
+
detail: 'Dependencies not installed',
|
|
263
|
+
fixes: [{
|
|
264
|
+
type: FIX_TYPE.COMMAND,
|
|
265
|
+
description: 'Install dependencies',
|
|
266
|
+
command: 'npm install',
|
|
267
|
+
autoFixable: true,
|
|
268
|
+
}],
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Check if .bin exists
|
|
273
|
+
const binPath = path.join(nmPath, '.bin');
|
|
274
|
+
const hasBin = fs.existsSync(binPath);
|
|
275
|
+
|
|
276
|
+
// Get approximate size (count directories)
|
|
277
|
+
try {
|
|
278
|
+
const entries = fs.readdirSync(nmPath);
|
|
279
|
+
const packageCount = entries.filter(e => !e.startsWith('.')).length;
|
|
280
|
+
|
|
281
|
+
const metadata = { packageCount, hasBin };
|
|
282
|
+
|
|
283
|
+
if (packageCount === 0) {
|
|
284
|
+
return {
|
|
285
|
+
severity: SEVERITY.WARNING,
|
|
286
|
+
message: 'node_modules is empty',
|
|
287
|
+
metadata,
|
|
288
|
+
fixes: [{
|
|
289
|
+
type: FIX_TYPE.COMMAND,
|
|
290
|
+
description: 'Reinstall dependencies',
|
|
291
|
+
command: 'rm -rf node_modules && npm install',
|
|
292
|
+
autoFixable: false,
|
|
293
|
+
dangerous: true,
|
|
294
|
+
}],
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
severity: SEVERITY.PASS,
|
|
300
|
+
message: `${packageCount} packages`,
|
|
301
|
+
metadata,
|
|
302
|
+
};
|
|
303
|
+
} catch (err) {
|
|
304
|
+
return {
|
|
305
|
+
severity: SEVERITY.WARNING,
|
|
306
|
+
message: 'Could not read node_modules',
|
|
307
|
+
detail: err.message,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
id: `${MODULE_ID}.cache_dirs`,
|
|
314
|
+
name: 'Build Caches',
|
|
315
|
+
category: CATEGORY.PROJECT,
|
|
316
|
+
parallel: true,
|
|
317
|
+
check: async () => {
|
|
318
|
+
const cacheDirs = [
|
|
319
|
+
{ name: '.next', desc: 'Next.js cache' },
|
|
320
|
+
{ name: '.nuxt', desc: 'Nuxt cache' },
|
|
321
|
+
{ name: '.turbo', desc: 'Turborepo cache' },
|
|
322
|
+
{ name: '.cache', desc: 'General cache' },
|
|
323
|
+
{ name: 'dist', desc: 'Build output' },
|
|
324
|
+
{ name: 'build', desc: 'Build output' },
|
|
325
|
+
{ name: '.vibecheck', desc: 'Vibecheck cache' },
|
|
326
|
+
{ name: 'coverage', desc: 'Test coverage' },
|
|
327
|
+
{ name: '.nyc_output', desc: 'NYC coverage' },
|
|
328
|
+
{ name: '.eslintcache', desc: 'ESLint cache', isFile: true },
|
|
329
|
+
{ name: '.stylelintcache', desc: 'Stylelint cache', isFile: true },
|
|
330
|
+
{ name: 'tsconfig.tsbuildinfo', desc: 'TypeScript incremental', isFile: true },
|
|
331
|
+
];
|
|
332
|
+
|
|
333
|
+
const found = [];
|
|
334
|
+
let totalSizeBytes = 0;
|
|
335
|
+
|
|
336
|
+
for (const cache of cacheDirs) {
|
|
337
|
+
const cachePath = path.join(projectPath, cache.name);
|
|
338
|
+
if (fs.existsSync(cachePath)) {
|
|
339
|
+
try {
|
|
340
|
+
const stat = fs.statSync(cachePath);
|
|
341
|
+
found.push({
|
|
342
|
+
...cache,
|
|
343
|
+
path: cachePath,
|
|
344
|
+
sizeBytes: stat.isDirectory() ? getDirectorySize(cachePath) : stat.size,
|
|
345
|
+
});
|
|
346
|
+
totalSizeBytes += found[found.length - 1].sizeBytes;
|
|
347
|
+
} catch {
|
|
348
|
+
found.push({ ...cache, path: cachePath, sizeBytes: 0 });
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const totalSizeMB = (totalSizeBytes / (1024 * 1024)).toFixed(1);
|
|
354
|
+
const metadata = { caches: found, totalSizeBytes, totalSizeMB };
|
|
355
|
+
|
|
356
|
+
if (found.length === 0) {
|
|
357
|
+
return {
|
|
358
|
+
severity: SEVERITY.PASS,
|
|
359
|
+
message: 'No cache directories',
|
|
360
|
+
metadata,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Warn if caches are very large (> 500MB)
|
|
365
|
+
if (totalSizeBytes > 500 * 1024 * 1024) {
|
|
366
|
+
return {
|
|
367
|
+
severity: SEVERITY.WARNING,
|
|
368
|
+
message: `${found.length} caches (${totalSizeMB}MB) — consider clearing`,
|
|
369
|
+
metadata,
|
|
370
|
+
fixes: [{
|
|
371
|
+
type: FIX_TYPE.COMMAND,
|
|
372
|
+
description: 'Clear all caches',
|
|
373
|
+
command: found.map(c => c.isFile ? `rm -f "${c.name}"` : `rm -rf "${c.name}"`).join(' && '),
|
|
374
|
+
autoFixable: false,
|
|
375
|
+
dangerous: true,
|
|
376
|
+
}],
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
severity: SEVERITY.PASS,
|
|
382
|
+
message: `${found.length} caches (${totalSizeMB}MB)`,
|
|
383
|
+
metadata,
|
|
384
|
+
};
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
id: `${MODULE_ID}.gitignore`,
|
|
389
|
+
name: '.gitignore Coverage',
|
|
390
|
+
category: CATEGORY.PROJECT,
|
|
391
|
+
parallel: true,
|
|
392
|
+
check: async () => {
|
|
393
|
+
const gitignorePath = path.join(projectPath, '.gitignore');
|
|
394
|
+
|
|
395
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
396
|
+
return {
|
|
397
|
+
severity: SEVERITY.WARNING,
|
|
398
|
+
message: 'No .gitignore file',
|
|
399
|
+
fixes: [{
|
|
400
|
+
type: FIX_TYPE.COMMAND,
|
|
401
|
+
description: 'Create .gitignore from template',
|
|
402
|
+
command: 'npx gitignore node',
|
|
403
|
+
autoFixable: true,
|
|
404
|
+
}],
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
410
|
+
const lines = content.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'));
|
|
411
|
+
|
|
412
|
+
const essentialPatterns = [
|
|
413
|
+
{ pattern: 'node_modules', desc: 'node_modules directory' },
|
|
414
|
+
{ pattern: '.env', desc: 'Environment files' },
|
|
415
|
+
{ pattern: 'dist', desc: 'Build output' },
|
|
416
|
+
{ pattern: '.next', desc: 'Next.js cache' },
|
|
417
|
+
{ pattern: 'coverage', desc: 'Test coverage' },
|
|
418
|
+
];
|
|
419
|
+
|
|
420
|
+
const missing = [];
|
|
421
|
+
for (const essential of essentialPatterns) {
|
|
422
|
+
const hasPattern = lines.some(l =>
|
|
423
|
+
l === essential.pattern ||
|
|
424
|
+
l.startsWith(essential.pattern + '/') ||
|
|
425
|
+
l === '/' + essential.pattern ||
|
|
426
|
+
l.includes(essential.pattern)
|
|
427
|
+
);
|
|
428
|
+
if (!hasPattern) {
|
|
429
|
+
missing.push(essential);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const metadata = { lineCount: lines.length, missing: missing.map(m => m.pattern) };
|
|
434
|
+
|
|
435
|
+
if (missing.length > 0) {
|
|
436
|
+
return {
|
|
437
|
+
severity: SEVERITY.WARNING,
|
|
438
|
+
message: `Missing: ${missing.map(m => m.pattern).join(', ')}`,
|
|
439
|
+
detail: `Consider adding these patterns to .gitignore`,
|
|
440
|
+
metadata,
|
|
441
|
+
fixes: [{
|
|
442
|
+
type: FIX_TYPE.MANUAL,
|
|
443
|
+
description: `Add missing patterns to .gitignore: ${missing.map(m => m.pattern).join(', ')}`,
|
|
444
|
+
autoFixable: false,
|
|
445
|
+
}],
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return {
|
|
450
|
+
severity: SEVERITY.PASS,
|
|
451
|
+
message: `${lines.length} patterns`,
|
|
452
|
+
metadata,
|
|
453
|
+
};
|
|
454
|
+
} catch (err) {
|
|
455
|
+
return {
|
|
456
|
+
severity: SEVERITY.WARNING,
|
|
457
|
+
message: 'Could not parse .gitignore',
|
|
458
|
+
detail: err.message,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
];
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Helper to get directory size
|
|
467
|
+
function getDirectorySize(dirPath) {
|
|
468
|
+
let size = 0;
|
|
469
|
+
try {
|
|
470
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
471
|
+
for (const entry of entries) {
|
|
472
|
+
const entryPath = path.join(dirPath, entry.name);
|
|
473
|
+
if (entry.isDirectory()) {
|
|
474
|
+
size += getDirectorySize(entryPath);
|
|
475
|
+
} else {
|
|
476
|
+
try {
|
|
477
|
+
size += fs.statSync(entryPath).size;
|
|
478
|
+
} catch {}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
} catch {}
|
|
482
|
+
return size;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
module.exports = { MODULE_ID, createDiagnostics };
|