minovative-mind-cli 2.14.2 → 2.14.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.
@@ -0,0 +1,554 @@
1
+ import path from 'node:path';
2
+ /**
3
+ * List of critical root configuration files, lockfiles, and manifests that the agent
4
+ * is strictly forbidden from deleting or renaming as an error-bypassing evasion strategy.
5
+ */
6
+ const CRITICAL_CONFIG_FILES = new Set([
7
+ // Node / JavaScript / TypeScript
8
+ 'package.json',
9
+ 'package-lock.json',
10
+ 'pnpm-lock.yaml',
11
+ 'yarn.lock',
12
+ 'bun.lockb',
13
+ 'bun.lock',
14
+ 'tsconfig.json',
15
+ 'tsconfig.base.json',
16
+ 'tsconfig.build.json',
17
+ 'tsconfig.app.json',
18
+ 'tsconfig.node.json',
19
+ 'jsconfig.json',
20
+ 'next.config.js',
21
+ 'next.config.ts',
22
+ 'next.config.mjs',
23
+ 'next.config.cjs',
24
+ 'vite.config.js',
25
+ 'vite.config.ts',
26
+ 'vite.config.mjs',
27
+ 'vite.config.cjs',
28
+ 'webpack.config.js',
29
+ 'webpack.config.ts',
30
+ 'webpack.config.mjs',
31
+ 'rollup.config.js',
32
+ 'rollup.config.ts',
33
+ 'rollup.config.mjs',
34
+ 'babel.config.js',
35
+ 'babel.config.json',
36
+ '.babelrc',
37
+ 'svelte.config.js',
38
+ 'astro.config.mjs',
39
+ 'nuxt.config.ts',
40
+ 'remix.config.js',
41
+ 'angular.json',
42
+ 'vitest.config.ts',
43
+ 'vitest.config.js',
44
+ 'jest.config.js',
45
+ 'jest.config.ts',
46
+ '.eslintrc',
47
+ '.eslintrc.js',
48
+ '.eslintrc.json',
49
+ '.eslintrc.yaml',
50
+ '.eslintrc.yml',
51
+ 'eslint.config.js',
52
+ 'eslint.config.mjs',
53
+ 'eslint.config.ts',
54
+ // Rust
55
+ 'cargo.toml',
56
+ 'cargo.lock',
57
+ // Go
58
+ 'go.mod',
59
+ 'go.sum',
60
+ // Python
61
+ 'pyproject.toml',
62
+ 'setup.py',
63
+ 'setup.cfg',
64
+ 'requirements.txt',
65
+ 'pipfile',
66
+ 'pipfile.lock',
67
+ 'poetry.lock',
68
+ 'mypy.ini',
69
+ // Java / Kotlin
70
+ 'pom.xml',
71
+ 'build.gradle',
72
+ 'build.gradle.kts',
73
+ 'settings.gradle',
74
+ 'settings.gradle.kts',
75
+ // C / C++
76
+ 'cmakelists.txt',
77
+ 'makefile',
78
+ 'meson.build',
79
+ // Ruby
80
+ 'gemfile',
81
+ 'gemfile.lock',
82
+ // PHP
83
+ 'composer.json',
84
+ 'composer.lock',
85
+ // Swift & Dart
86
+ 'package.swift',
87
+ 'pubspec.yaml',
88
+ 'pubspec.lock',
89
+ ]);
90
+ /**
91
+ * Determines whether a given file path represents a critical project configuration
92
+ * file or root manifest that should never be deleted or renamed by the agent.
93
+ *
94
+ * @param filePath - Relative or absolute path to check
95
+ * @returns True if the file is a protected critical config file
96
+ */
97
+ export function isCriticalConfigFile(filePath) {
98
+ const base = path.basename(filePath).toLowerCase();
99
+ return CRITICAL_CONFIG_FILES.has(base);
100
+ }
101
+ /**
102
+ * Checks if a Next.js configuration contains error-ignoring flags.
103
+ * Handles variations like:
104
+ * typescript: { ignoreBuildErrors: true }
105
+ * eslint: { ignoreDuringBuilds: true }
106
+ */
107
+ function hasNextConfigCheats(content) {
108
+ // Check typescript.ignoreBuildErrors
109
+ if (/\bignoreBuildErrors\s*:\s*(?:true|1|Boolean\s*\(\s*true\s*\))/i.test(content)) {
110
+ return {
111
+ cheat: true,
112
+ reason: 'Attempted to set "ignoreBuildErrors: true" in Next.js configuration to bypass type checking.',
113
+ };
114
+ }
115
+ // Check eslint.ignoreDuringBuilds
116
+ if (/\bignoreDuringBuilds\s*:\s*(?:true|1|Boolean\s*\(\s*true\s*\))/i.test(content)) {
117
+ return {
118
+ cheat: true,
119
+ reason: 'Attempted to set "ignoreDuringBuilds: true" in Next.js configuration to bypass ESLint checks.',
120
+ };
121
+ }
122
+ return { cheat: false };
123
+ }
124
+ /**
125
+ * Checks if a TypeScript configuration (tsconfig*.json) was downgraded to disable type safety.
126
+ */
127
+ function hasTsConfigCheats(content, existingContent) {
128
+ // Check for strict: false replacing strict: true or being newly introduced
129
+ if (/"strict"\s*:\s*false/i.test(content)) {
130
+ if (!existingContent || !/"strict"\s*:\s*false/i.test(existingContent)) {
131
+ return {
132
+ cheat: true,
133
+ reason: 'Attempted to disable TypeScript strict mode ("strict": false) in tsconfig.',
134
+ };
135
+ }
136
+ }
137
+ // Check for suppressImplicitAnyIndexErrors
138
+ if (/"suppressImplicitAnyIndexErrors"\s*:\s*true/i.test(content)) {
139
+ if (!existingContent || !/"suppressImplicitAnyIndexErrors"\s*:\s*true/i.test(existingContent)) {
140
+ return {
141
+ cheat: true,
142
+ reason: 'Attempted to enable "suppressImplicitAnyIndexErrors: true" in tsconfig.',
143
+ };
144
+ }
145
+ }
146
+ // Check for noCheck: true
147
+ if (/"noCheck"\s*:\s*true/i.test(content)) {
148
+ if (!existingContent || !/"noCheck"\s*:\s*true/i.test(existingContent)) {
149
+ return {
150
+ cheat: true,
151
+ reason: 'Attempted to set "noCheck: true" in tsconfig.',
152
+ };
153
+ }
154
+ }
155
+ // Check for noEmitOnError: false being added when previously true or not false
156
+ if (/"noEmitOnError"\s*:\s*false/i.test(content)) {
157
+ if (!existingContent || !/"noEmitOnError"\s*:\s*false/i.test(existingContent)) {
158
+ return {
159
+ cheat: true,
160
+ reason: 'Attempted to set "noEmitOnError: false" in tsconfig to emit outputs despite compilation errors.',
161
+ };
162
+ }
163
+ }
164
+ return { cheat: false };
165
+ }
166
+ /**
167
+ * Checks if package.json scripts were modified to swallow errors (e.g. appending "|| true", "|| exit 0")
168
+ * or stripping type check commands (e.g. removing "tsc" from build).
169
+ */
170
+ function hasPackageJsonCheats(content, existingContent) {
171
+ // Check for script swallowing failures: "|| true", "|| exit 0", "; exit 0" inside scripts
172
+ const swallowMatch = /"(?:build|typecheck|test|lint|check)"\s*:\s*"[^"]*(?:\|\|\s*true|\|\|\s*exit\s+0|;\s*exit\s+0)/i.test(content);
173
+ if (swallowMatch) {
174
+ if (!existingContent || !/"(?:build|typecheck|test|lint|check)"\s*:\s*"[^"]*(?:\|\|\s*true|\|\|\s*exit\s+0|;\s*exit\s+0)/i.test(existingContent)) {
175
+ return {
176
+ cheat: true,
177
+ reason: 'Attempted to append error-swallowing directives ("|| true" or "|| exit 0") to package.json scripts.',
178
+ };
179
+ }
180
+ }
181
+ // Check if existing package.json had a build script with tsc / typecheck, and the new one removed it
182
+ if (existingContent) {
183
+ try {
184
+ const oldPkg = JSON.parse(existingContent);
185
+ const newPkg = JSON.parse(content);
186
+ const oldBuild = oldPkg?.scripts?.build;
187
+ const newBuild = newPkg?.scripts?.build;
188
+ if (typeof oldBuild === 'string' && typeof newBuild === 'string') {
189
+ const hadTsc = /\btsc\b/i.test(oldBuild);
190
+ const hasTsc = /\btsc\b/i.test(newBuild);
191
+ if (hadTsc && !hasTsc) {
192
+ return {
193
+ cheat: true,
194
+ reason: 'Attempted to strip "tsc" type checking from the "build" script in package.json to bypass errors.',
195
+ };
196
+ }
197
+ }
198
+ }
199
+ catch {
200
+ // Ignore JSON parse errors in partial content
201
+ }
202
+ }
203
+ return { cheat: false };
204
+ }
205
+ /**
206
+ * Checks ESLint configuration files (eslint.config.*, .eslintrc*) for attempts to
207
+ * disable all rules or ignore all files.
208
+ */
209
+ function hasLinterConfigCheats(content, existingContent) {
210
+ // Check for blanket ignores in flat config: ignores: ['**/*', '**', 'src/**']
211
+ if (/\bignores\s*:\s*\[\s*['"`](?:\*\*|\*\*\/\*|\*|src\/\*\*)['"`]\s*\]/i.test(content)) {
212
+ if (!existingContent || !/\bignores\s*:\s*\[\s*['"`](?:\*\*|\*\*\/\*|\*|src\/\*\*)['"`]\s*\]/i.test(existingContent)) {
213
+ return {
214
+ cheat: true,
215
+ reason: 'Attempted to ignore all source files in ESLint configuration.',
216
+ };
217
+ }
218
+ }
219
+ // Check for ignorePatterns in legacy .eslintrc: ignorePatterns: ["**/*", "*"]
220
+ if (/"ignorePatterns"\s*:\s*\[\s*["'](?:\*\*|\*\*\/\*|\*|src\/\*\*)["']\s*\]/i.test(content)) {
221
+ if (!existingContent || !/"ignorePatterns"\s*:\s*\[\s*["'](?:\*\*|\*\*\/\*|\*|src\/\*\*)["']\s*\]/i.test(existingContent)) {
222
+ return {
223
+ cheat: true,
224
+ reason: 'Attempted to add blanket ignorePatterns in ESLint configuration.',
225
+ };
226
+ }
227
+ }
228
+ return { cheat: false };
229
+ }
230
+ /**
231
+ * Checks linter and build ignore files (.eslintignore, .prettierignore) for blanket wildcard exclusions.
232
+ */
233
+ function hasIgnoreFileCheats(filePath, content, existingContent) {
234
+ const base = path.basename(filePath).toLowerCase();
235
+ if (base === '.eslintignore' || base === '.prettierignore') {
236
+ const lines = content.split('\n').map((l) => l.trim()).filter((l) => l && !l.startsWith('#'));
237
+ const oldLines = existingContent ? existingContent.split('\n').map((l) => l.trim()).filter((l) => l && !l.startsWith('#')) : [];
238
+ for (const line of lines) {
239
+ if (['*', '**', '**/*', 'src', 'src/*', 'src/**'].includes(line)) {
240
+ if (!oldLines.includes(line)) {
241
+ return {
242
+ cheat: true,
243
+ reason: `Attempted to add wildcard exclusion "${line}" to ${base} to silence verification.`,
244
+ };
245
+ }
246
+ }
247
+ }
248
+ }
249
+ return { cheat: false };
250
+ }
251
+ /**
252
+ * Checks environment files (.env, .env.local, .env.development, .env.production) for bypass variables.
253
+ */
254
+ function hasEnvFileCheats(filePath, content, existingContent) {
255
+ const base = path.basename(filePath).toLowerCase();
256
+ if (base === '.env' || base.startsWith('.env.')) {
257
+ const bypassVars = [
258
+ 'NEXT_DISABLE_ESLINT',
259
+ 'TSC_COMPILE_ON_ERROR',
260
+ 'ESLINT_NO_DEV_ERRORS',
261
+ ];
262
+ for (const v of bypassVars) {
263
+ const regex = new RegExp(`^\\s*${v}\\s*=\\s*(?:1|true)`, 'im');
264
+ if (regex.test(content)) {
265
+ if (!existingContent || !regex.test(existingContent)) {
266
+ return {
267
+ cheat: true,
268
+ reason: `Attempted to set bypass environment variable "${v}" in ${base}.`,
269
+ };
270
+ }
271
+ }
272
+ }
273
+ }
274
+ return { cheat: false };
275
+ }
276
+ /**
277
+ * Checks build tools and test runners (Vite, Vitest, Webpack, Jest) for error suppressions.
278
+ */
279
+ function hasBundlerOrRunnerCheats(content, existingContent) {
280
+ // Check for passWithNoTests: true or bail: false in Jest/Vitest configs to mask missing tests
281
+ if (/\bpassWithNoTests\s*:\s*true/i.test(content)) {
282
+ if (!existingContent || !/\bpassWithNoTests\s*:\s*true/i.test(existingContent)) {
283
+ return {
284
+ cheat: true,
285
+ reason: 'Attempted to enable "passWithNoTests: true" in test runner configuration.',
286
+ };
287
+ }
288
+ }
289
+ return { cheat: false };
290
+ }
291
+ /**
292
+ * Checks for blanket error suppressions in source code:
293
+ * - Newly introduced //@ts-nocheck
294
+ * - Unscoped /* eslint-disable *\/ at the file header
295
+ * - Go //go:build ignore
296
+ * - Rust #![allow(warnings)]
297
+ * - Python file-wide # flake8: noqa
298
+ */
299
+ function hasSourceCodeCheats(content, existingContent) {
300
+ // 1. Check for @ts-nocheck
301
+ if (/\/\/\s*@ts-nocheck\b/i.test(content) || /\/\*\s*@ts-nocheck\s*\*\//i.test(content)) {
302
+ if (!existingContent || (!/\/\/\s*@ts-nocheck\b/i.test(existingContent) && !/\/\*\s*@ts-nocheck\s*\*\//i.test(existingContent))) {
303
+ return {
304
+ cheat: true,
305
+ reason: 'Attempted to add "@ts-nocheck" to disable TypeScript type checking for this file.',
306
+ };
307
+ }
308
+ }
309
+ // 2. Check for unscoped /* eslint-disable */ (blanket disable without specific rule names)
310
+ if (/\/\*\s*eslint-disable\s*\*\//i.test(content)) {
311
+ if (!existingContent || !/\/\*\s*eslint-disable\s*\*\//i.test(existingContent)) {
312
+ return {
313
+ cheat: true,
314
+ reason: 'Attempted to add blanket "/* eslint-disable */" to silence all linter rules for this file.',
315
+ };
316
+ }
317
+ }
318
+ // 3. Rust crate-level #![allow(warnings)]
319
+ if (/#!\s*\[\s*allow\s*\(\s*warnings\s*\)\s*\]/i.test(content)) {
320
+ if (!existingContent || !/#!\s*\[\s*allow\s*\(\s*warnings\s*\)\s*\]/i.test(existingContent)) {
321
+ return {
322
+ cheat: true,
323
+ reason: 'Attempted to add "#![allow(warnings)]" to silence all Rust compiler warnings.',
324
+ };
325
+ }
326
+ }
327
+ // 4. Go //go:build ignore on existing source files
328
+ if (/\/\/\s*go:build\s+ignore\b/i.test(content)) {
329
+ if (!existingContent || !/\/\/\s*go:build\s+ignore\b/i.test(existingContent)) {
330
+ return {
331
+ cheat: true,
332
+ reason: 'Attempted to add "//go:build ignore" to exclude this Go file from compilation.',
333
+ };
334
+ }
335
+ }
336
+ // 5. Python # flake8: noqa file-wide disable
337
+ if (/#\s*flake8:\s*noqa\b/i.test(content)) {
338
+ if (!existingContent || !/#\s*flake8:\s*noqa\b/i.test(existingContent)) {
339
+ return {
340
+ cheat: true,
341
+ reason: 'Attempted to add "# flake8: noqa" to disable lint checking for this Python file.',
342
+ };
343
+ }
344
+ }
345
+ return { cheat: false };
346
+ }
347
+ /**
348
+ * Checks if a test file had its test assertions neutered or skipped (e.g. newly introduced it.skip/test.skip,
349
+ * or commenting out assertions).
350
+ */
351
+ function hasTestCheats(filePath, content, existingContent) {
352
+ const isTestFile = /\.(?:test|spec)\.[a-z0-9]+$/i.test(filePath) || filePath.includes('/__tests__/');
353
+ if (!isTestFile)
354
+ return { cheat: false };
355
+ // 1. Count newly introduced .skip calls
356
+ const oldSkipCount = existingContent ? (existingContent.match(/\b(?:it|test|describe)\.skip\b/g) || []).length : 0;
357
+ const newSkipCount = (content.match(/\b(?:it|test|describe)\.skip\b/g) || []).length;
358
+ if (newSkipCount > oldSkipCount) {
359
+ return {
360
+ cheat: true,
361
+ reason: `Attempted to skip failing test cases (${newSkipCount - oldSkipCount} newly added .skip calls) instead of fixing the implementation.`,
362
+ };
363
+ }
364
+ // 2. Detect commenting out assertions (e.g. converting `expect(...)` or `assert(...)` into `// expect(...)`)
365
+ if (existingContent) {
366
+ const oldExpectCount = (existingContent.match(/(?<!\/\/.*|\/\*.*)\b(?:expect|assert)\s*\(/g) || []).length;
367
+ const newExpectCount = (content.match(/(?<!\/\/.*|\/\*.*)\b(?:expect|assert)\s*\(/g) || []).length;
368
+ // If more than 50% of assertions were commented out/deleted while test cases remained
369
+ const oldTestCount = (existingContent.match(/\b(?:it|test)\s*\(/g) || []).length;
370
+ const newTestCount = (content.match(/\b(?:it|test)\s*\(/g) || []).length;
371
+ if (oldTestCount > 0 && newTestCount >= oldTestCount && oldExpectCount >= 2 && newExpectCount < Math.floor(oldExpectCount / 2)) {
372
+ return {
373
+ cheat: true,
374
+ reason: `Attempted to neuter test assertions (${oldExpectCount} assertions reduced to ${newExpectCount}) instead of fixing broken code.`,
375
+ };
376
+ }
377
+ }
378
+ return { cheat: false };
379
+ }
380
+ /**
381
+ * Checks Python configuration files (mypy.ini, pyproject.toml, setup.cfg) for type checking disables.
382
+ */
383
+ function hasPythonCheats(content, existingContent) {
384
+ // Check for mypy ignore_errors = true
385
+ if (/\bignore_errors\s*=\s*(?:true|1)\b/i.test(content)) {
386
+ if (!existingContent || !/\bignore_errors\s*=\s*(?:true|1)\b/i.test(existingContent)) {
387
+ return {
388
+ cheat: true,
389
+ reason: 'Attempted to set "ignore_errors = True" in Python typing configuration.',
390
+ };
391
+ }
392
+ }
393
+ // Check for ruff ignore all rules: ignore = ["ALL"] or select = []
394
+ if (/\bignore\s*=\s*\[\s*["']ALL["']\s*\]/i.test(content)) {
395
+ if (!existingContent || !/\bignore\s*=\s*\[\s*["']ALL["']\s*\]/i.test(existingContent)) {
396
+ return {
397
+ cheat: true,
398
+ reason: 'Attempted to set `ignore = ["ALL"]` in Python Ruff linter configuration.',
399
+ };
400
+ }
401
+ }
402
+ return { cheat: false };
403
+ }
404
+ /**
405
+ * Checks compiled and native build configurations (CMakeLists.txt, Makefile, Gradle, Maven)
406
+ * for error suppression flags.
407
+ */
408
+ function hasCompiledSystemsCheats(filePath, content, existingContent) {
409
+ const base = path.basename(filePath).toLowerCase();
410
+ // CMake: adding -w to CMAKE_C_FLAGS or CMAKE_CXX_FLAGS
411
+ if (base === 'cmakelists.txt') {
412
+ if (/(?:CMAKE_C_FLAGS|CMAKE_CXX_FLAGS)\s+[^)]*-w\b/i.test(content)) {
413
+ if (!existingContent || !/(?:CMAKE_C_FLAGS|CMAKE_CXX_FLAGS)\s+[^)]*-w\b/i.test(existingContent)) {
414
+ return {
415
+ cheat: true,
416
+ reason: 'Attempted to add "-w" warning suppression flag in CMakeLists.txt.',
417
+ };
418
+ }
419
+ }
420
+ }
421
+ // Gradle: test.ignoreFailures = true or ignoreFailures = true
422
+ if (base === 'build.gradle' || base === 'build.gradle.kts') {
423
+ if (/\bignoreFailures\s*=\s*true/i.test(content)) {
424
+ if (!existingContent || !/\bignoreFailures\s*=\s*true/i.test(existingContent)) {
425
+ return {
426
+ cheat: true,
427
+ reason: 'Attempted to set "ignoreFailures = true" in Gradle build script.',
428
+ };
429
+ }
430
+ }
431
+ }
432
+ // Maven: <testFailureIgnore>true</testFailureIgnore>
433
+ if (base === 'pom.xml') {
434
+ if (/<testFailureIgnore>\s*true\s*<\/testFailureIgnore>/i.test(content)) {
435
+ if (!existingContent || !/<testFailureIgnore>\s*true\s*<\/testFailureIgnore>/i.test(existingContent)) {
436
+ return {
437
+ cheat: true,
438
+ reason: 'Attempted to set "<testFailureIgnore>true</testFailureIgnore>" in Maven pom.xml.',
439
+ };
440
+ }
441
+ }
442
+ }
443
+ return { cheat: false };
444
+ }
445
+ /**
446
+ * Evaluates whether a proposed file modification or write introduces anti-cheating violations
447
+ * by attempting to silence, ignore, or bypass build, compiler, type, or lint errors.
448
+ *
449
+ * This function is strictly diff-aware: if the existing file already had an error-ignoring flag,
450
+ * modifying unrelated sections of the file will not trigger a false positive.
451
+ *
452
+ * @param filePath - Path to the file being created or modified
453
+ * @param newContent - The proposed new content of the file
454
+ * @param existingContent - The previous content of the file (if it already existed on disk)
455
+ * @returns An error message string if an anti-cheating violation is detected, or null if clean.
456
+ */
457
+ export function detectAntiCheatingViolations(filePath, newContent, existingContent) {
458
+ const base = path.basename(filePath).toLowerCase();
459
+ // 1. Next.js configuration files
460
+ if (base.startsWith('next.config.')) {
461
+ const nextCheck = hasNextConfigCheats(newContent);
462
+ if (nextCheck.cheat) {
463
+ if (!existingContent || !hasNextConfigCheats(existingContent).cheat) {
464
+ return (`Anti-cheating violation: ${nextCheck.reason}\n` +
465
+ `You are strictly forbidden from modifying build configuration files to ignore or bypass errors.\n` +
466
+ `Actionable guidance: You must fix all build, compiler, type, and lint errors head-on in the application source code.`);
467
+ }
468
+ }
469
+ }
470
+ // 2. TypeScript configuration files (tsconfig.json, tsconfig.*.json, jsconfig.json)
471
+ if (base === 'tsconfig.json' || base === 'jsconfig.json' || (base.startsWith('tsconfig.') && base.endsWith('.json'))) {
472
+ const tsCheck = hasTsConfigCheats(newContent, existingContent);
473
+ if (tsCheck.cheat) {
474
+ return (`Anti-cheating violation: ${tsCheck.reason}\n` +
475
+ `You are strictly forbidden from downgrading TypeScript configuration to bypass errors.\n` +
476
+ `Actionable guidance: Fix the type errors directly in the TypeScript source files.`);
477
+ }
478
+ }
479
+ // 3. package.json scripts
480
+ if (base === 'package.json') {
481
+ const pkgCheck = hasPackageJsonCheats(newContent, existingContent);
482
+ if (pkgCheck.cheat) {
483
+ return (`Anti-cheating violation: ${pkgCheck.reason}\n` +
484
+ `You are strictly forbidden from altering build scripts to swallow or skip verification failures.\n` +
485
+ `Actionable guidance: Ensure build scripts run proper verification and resolve the underlying code errors.`);
486
+ }
487
+ }
488
+ // 4. ESLint configuration files (eslint.config.*, .eslintrc*)
489
+ if (base.startsWith('eslint.config.') || base === '.eslintrc' || base.startsWith('.eslintrc.')) {
490
+ const linterCheck = hasLinterConfigCheats(newContent, existingContent);
491
+ if (linterCheck.cheat) {
492
+ return (`Anti-cheating violation: ${linterCheck.reason}\n` +
493
+ `You are strictly forbidden from disabling linter rules in ESLint configuration.\n` +
494
+ `Actionable guidance: Fix the code to conform to linting standards.`);
495
+ }
496
+ }
497
+ // 5. Ignore files (.eslintignore, .prettierignore)
498
+ const ignoreCheck = hasIgnoreFileCheats(filePath, newContent, existingContent);
499
+ if (ignoreCheck.cheat) {
500
+ return (`Anti-cheating violation: ${ignoreCheck.reason}\n` +
501
+ `You are strictly forbidden from adding wildcard ignore patterns to bypass verification.\n` +
502
+ `Actionable guidance: Fix the source code rather than excluding it from checks.`);
503
+ }
504
+ // 6. Environment files (.env, .env.*)
505
+ const envCheck = hasEnvFileCheats(filePath, newContent, existingContent);
506
+ if (envCheck.cheat) {
507
+ return (`Anti-cheating violation: ${envCheck.reason}\n` +
508
+ `You are strictly forbidden from setting bypass environment variables.\n` +
509
+ `Actionable guidance: Fix the underlying errors in your code.`);
510
+ }
511
+ // 7. Bundlers & test runners (vite.config.*, vitest.config.*, jest.config.*)
512
+ if (base.startsWith('vite.config.') || base.startsWith('vitest.config.') || base.startsWith('jest.config.')) {
513
+ const bundlerCheck = hasBundlerOrRunnerCheats(newContent, existingContent);
514
+ if (bundlerCheck.cheat) {
515
+ return (`Anti-cheating violation: ${bundlerCheck.reason}\n` +
516
+ `You are strictly forbidden from configuring test runners or bundlers to bypass checks.\n` +
517
+ `Actionable guidance: Address the root cause in the application or test code.`);
518
+ }
519
+ }
520
+ // 8. Python typing configs (mypy.ini, pyproject.toml, setup.cfg)
521
+ if (base === 'mypy.ini' || base === 'pyproject.toml' || base === 'setup.cfg') {
522
+ const pyCheck = hasPythonCheats(newContent, existingContent);
523
+ if (pyCheck.cheat) {
524
+ return (`Anti-cheating violation: ${pyCheck.reason}\n` +
525
+ `You are strictly forbidden from disabling Python type checking or linting.\n` +
526
+ `Actionable guidance: Resolve typing errors in the Python source code.`);
527
+ }
528
+ }
529
+ // 9. Compiled build systems (CMakeLists.txt, Makefile, build.gradle, pom.xml)
530
+ const compiledCheck = hasCompiledSystemsCheats(filePath, newContent, existingContent);
531
+ if (compiledCheck.cheat) {
532
+ return (`Anti-cheating violation: ${compiledCheck.reason}\n` +
533
+ `You are strictly forbidden from suppressing compilation warnings or test failures.\n` +
534
+ `Actionable guidance: Resolve compiler warnings and test failures in your code.`);
535
+ }
536
+ // 10. Source code file blanket suppressions (@ts-nocheck, /* eslint-disable */, #![allow(warnings)], etc.)
537
+ const ext = path.extname(filePath).toLowerCase();
538
+ if (['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.rs', '.go', '.py'].includes(ext)) {
539
+ const srcCheck = hasSourceCodeCheats(newContent, existingContent);
540
+ if (srcCheck.cheat) {
541
+ return (`Anti-cheating violation: ${srcCheck.reason}\n` +
542
+ `You are strictly forbidden from adding blanket error suppressions to silence errors.\n` +
543
+ `Actionable guidance: Fix the underlying type, syntax, and lint issues directly.`);
544
+ }
545
+ // 11. Test assertion neutering and skipping
546
+ const testCheck = hasTestCheats(filePath, newContent, existingContent);
547
+ if (testCheck.cheat) {
548
+ return (`Anti-cheating violation: ${testCheck.reason}\n` +
549
+ `You are strictly forbidden from skipping test cases or removing assertions to pass verification.\n` +
550
+ `Actionable guidance: Fix the application code so that the tests pass cleanly.`);
551
+ }
552
+ }
553
+ return null;
554
+ }
@@ -1,3 +1,12 @@
1
+ export declare function invalidateDependencyGraph(workspaceRoot?: string): void;
2
+ export declare function clearDependencyGraphCache(): void;
3
+ export declare function getDependencyGraphCacheStats(): Readonly<{
4
+ hits: number;
5
+ misses: number;
6
+ invalidations: number;
7
+ cachedWorkspaces: number;
8
+ }>;
9
+ export declare function resetDependencyGraphCacheStats(): void;
1
10
  export interface DependencyNode {
2
11
  /** Files this file directly imports (forward/downstream) */
3
12
  imports: Set<string>;
@@ -35,9 +44,13 @@ export interface DependencyGraph {
35
44
  *
36
45
  * Performance: This is pure regex + filesystem walking — no AST parsing.
37
46
  * For a typical project (<5,000 source files), this completes in <1s.
38
- * The graph is ephemeral: built once per `gatherContext` call and discarded.
47
+ * The graph is cached in-memory per workspaceRoot and automatically reused across
48
+ * subsequent queries until invalidated by file modifications.
49
+ *
50
+ * @param workspaceRoot - Root directory of the target project workspace
51
+ * @param forceRebuild - If true, bypasses the in-memory cache and forces a full rescan
39
52
  */
40
- export declare function buildDependencyGraph(workspaceRoot: string): Promise<DependencyGraph>;
53
+ export declare function buildDependencyGraph(workspaceRoot: string, forceRebuild?: boolean): Promise<DependencyGraph>;
41
54
  export interface FindDependenciesResult {
42
55
  filePath: string;
43
56
  forwardDeps: string[];
@@ -1,6 +1,47 @@
1
1
  import { promises as fs } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { EXCLUDED_EXTENSIONS } from './excludedExtensions.js';
4
+ import { debugLog } from './logger.js';
5
+ // ─── Dependency Graph Cache ──────────────────────────────────────────
6
+ const dependencyGraphCache = new Map();
7
+ const dependencyGraphCacheStats = {
8
+ hits: 0,
9
+ misses: 0,
10
+ invalidations: 0,
11
+ };
12
+ function normalizeWorkspaceKey(workspaceRoot) {
13
+ const resolved = path.resolve(workspaceRoot).replace(/\\/g, '/');
14
+ return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
15
+ }
16
+ export function invalidateDependencyGraph(workspaceRoot) {
17
+ if (workspaceRoot) {
18
+ const key = normalizeWorkspaceKey(workspaceRoot);
19
+ if (dependencyGraphCache.delete(key)) {
20
+ dependencyGraphCacheStats.invalidations++;
21
+ debugLog(`[DependencyTracer] Invalidated graph cache for workspace: ${workspaceRoot}`);
22
+ }
23
+ }
24
+ else {
25
+ const count = dependencyGraphCache.size;
26
+ dependencyGraphCache.clear();
27
+ dependencyGraphCacheStats.invalidations += count;
28
+ debugLog(`[DependencyTracer] Cleared graph cache for all workspaces (${count})`);
29
+ }
30
+ }
31
+ export function clearDependencyGraphCache() {
32
+ invalidateDependencyGraph();
33
+ }
34
+ export function getDependencyGraphCacheStats() {
35
+ return {
36
+ ...dependencyGraphCacheStats,
37
+ cachedWorkspaces: dependencyGraphCache.size,
38
+ };
39
+ }
40
+ export function resetDependencyGraphCacheStats() {
41
+ dependencyGraphCacheStats.hits = 0;
42
+ dependencyGraphCacheStats.misses = 0;
43
+ dependencyGraphCacheStats.invalidations = 0;
44
+ }
4
45
  // ─── Language Profile Registry ───────────────────────────────────────
5
46
  //
6
47
  // Each profile defines regex patterns that extract import specifiers for
@@ -503,9 +544,24 @@ async function walkWorkspace(workspaceRoot) {
503
544
  *
504
545
  * Performance: This is pure regex + filesystem walking — no AST parsing.
505
546
  * For a typical project (<5,000 source files), this completes in <1s.
506
- * The graph is ephemeral: built once per `gatherContext` call and discarded.
547
+ * The graph is cached in-memory per workspaceRoot and automatically reused across
548
+ * subsequent queries until invalidated by file modifications.
549
+ *
550
+ * @param workspaceRoot - Root directory of the target project workspace
551
+ * @param forceRebuild - If true, bypasses the in-memory cache and forces a full rescan
507
552
  */
508
- export async function buildDependencyGraph(workspaceRoot) {
553
+ export async function buildDependencyGraph(workspaceRoot, forceRebuild = false) {
554
+ const key = normalizeWorkspaceKey(workspaceRoot);
555
+ if (!forceRebuild) {
556
+ const cached = dependencyGraphCache.get(key);
557
+ if (cached) {
558
+ dependencyGraphCacheStats.hits++;
559
+ debugLog(`[DependencyTracer] Graph cache HIT for ${workspaceRoot} (${cached.graph.nodes.size} nodes)`);
560
+ return cached.graph;
561
+ }
562
+ }
563
+ dependencyGraphCacheStats.misses++;
564
+ debugLog(`[DependencyTracer] Graph cache MISS, building graph for ${workspaceRoot}`);
509
565
  const nodes = new Map();
510
566
  function getOrCreate(filePath) {
511
567
  let node = nodes.get(filePath);
@@ -540,7 +596,7 @@ export async function buildDependencyGraph(workspaceRoot) {
540
596
  }
541
597
  }));
542
598
  // 3. Return the graph with query methods
543
- return {
599
+ const graph = {
544
600
  nodes,
545
601
  getImports(filePath) {
546
602
  return Array.from(nodes.get(filePath)?.imports || []);
@@ -566,6 +622,8 @@ export async function buildDependencyGraph(workspaceRoot) {
566
622
  return computeGraphCentrality(nodes);
567
623
  },
568
624
  };
625
+ dependencyGraphCache.set(key, { graph, timestamp: Date.now() });
626
+ return graph;
569
627
  }
570
628
  /**
571
629
  * BFS traversal of the dependency graph in a given direction.