@lisa.ai/agent 2.2.1 → 2.3.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.
@@ -1,60 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AutoInstallerService = void 0;
4
- const child_process_1 = require("child_process");
5
- class AutoInstallerService {
6
- /**
7
- * Natively executes NPM installation commands if a framework is completely devoid of testing tooling.
8
- */
9
- static async installMissingFramework(fingerprint, projectPath = process.cwd()) {
10
- if (fingerprint.testingFramework !== 'none') {
11
- return fingerprint; // Already fully equipped
12
- }
13
- console.log(`\n[Lisa.ai Auto-Installer] 🚨 No testing framework detected for ${fingerprint.type} architecture.`);
14
- console.log(`[Lisa.ai Auto-Installer] šŸŖ„ Initiating Zero-Config Installation Protocol...`);
15
- let installCmd = '';
16
- let testingTarget = 'none';
17
- switch (fingerprint.type) {
18
- case 'angular':
19
- // Angular historically uses Karma/Jasmine natively, though modern might prefer Jest.
20
- // We'll provision standard Jasmine/Karma to align with standard Angular builders.
21
- console.log(`[Lisa.ai Auto-Installer] Provisioning Angular TestBed environment...`);
22
- installCmd = 'npm install --save-dev karma karma-chrome-launcher karma-coverage karma-jasmine jasmine-core @types/jasmine';
23
- testingTarget = 'karma';
24
- fingerprint.suggestedTestCommand = 'npm run test';
25
- break;
26
- case 'react':
27
- case 'node':
28
- default:
29
- // Jest is the safest, most ubiquitous generic fallback for React and Node JS.
30
- console.log(`[Lisa.ai Auto-Installer] Provisioning Universal Jest environment...`);
31
- installCmd = 'npm install --save-dev jest @types/jest ts-jest';
32
- testingTarget = 'jest';
33
- fingerprint.suggestedTestCommand = 'npx jest --coverage';
34
- break;
35
- case 'vue':
36
- // Vitest is the modern standard for Vue 3 / Vite ecosystems
37
- console.log(`[Lisa.ai Auto-Installer] Provisioning Vue Vitest environment...`);
38
- installCmd = 'npm install --save-dev vitest @vue/test-utils jsdom';
39
- testingTarget = 'vitest';
40
- fingerprint.suggestedTestCommand = 'npx vitest run --coverage';
41
- break;
42
- }
43
- if (installCmd) {
44
- console.log(`[Lisa.ai Executing] ${installCmd}`);
45
- const parts = installCmd.split(' ');
46
- const childTarget = process.platform === 'win32' ? `${parts[0]}.cmd` : parts[0];
47
- // Execute natively blocking so the repository is fully built before the agent tries to test it
48
- const result = (0, child_process_1.spawnSync)(childTarget, parts.slice(1), { cwd: projectPath, stdio: 'inherit' });
49
- if (result.status !== 0) {
50
- console.error(`\nāŒ [Lisa.ai Auto-Installer] Failed to construct dynamic testing environment.`);
51
- process.exit(1);
52
- }
53
- console.log(`āœ… [Lisa.ai Auto-Installer] Framework successfully injected.`);
54
- fingerprint.testingFramework = testingTarget;
55
- return fingerprint;
56
- }
57
- return fingerprint;
58
- }
59
- }
60
- exports.AutoInstallerService = AutoInstallerService;
@@ -1,173 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.askLisaForFix = askLisaForFix;
37
- exports.generateTestForFile = generateTestForFile;
38
- exports.updateTestForFile = updateTestForFile;
39
- exports.askLisaGeneral = askLisaGeneral;
40
- const ai_1 = require("ai");
41
- const openai_1 = require("@ai-sdk/openai");
42
- const anthropic_1 = require("@ai-sdk/anthropic");
43
- const google_1 = require("@ai-sdk/google");
44
- const path = __importStar(require("path"));
45
- const dotenv = __importStar(require("dotenv"));
46
- dotenv.config({ path: path.resolve(__dirname, '../../.env'), quiet: true });
47
- function getProvider(provider, apiKey) {
48
- if (provider === 'claude') {
49
- const key = apiKey || process.env.ANTHROPIC_API_KEY;
50
- if (!key)
51
- throw new Error('No Anthropic API key provided by local ENV or Control Plane');
52
- // Bug 2 fix: claude-3-haiku is too weak for complex DI/module fixes.
53
- // claude-3-5-sonnet is significantly better at understanding Angular/React test scaffolding.
54
- return (0, anthropic_1.createAnthropic)({ apiKey: key })('claude-3-5-sonnet-20241022');
55
- }
56
- if (provider === 'openai') {
57
- const key = apiKey || process.env.OPENAI_API_KEY;
58
- if (!key)
59
- throw new Error('No OpenAI API key provided by local ENV or Control Plane');
60
- // Bug 2 fix: gpt-3.5-turbo is deprecated and produces low-quality code fixes.
61
- return (0, openai_1.createOpenAI)({ apiKey: key })('gpt-4o-mini');
62
- }
63
- const key = apiKey || process.env.GOOGLE_GENERATIVE_AI_API_KEY;
64
- if (!key)
65
- throw new Error('No Google API key provided by local ENV or Control Plane');
66
- return (0, google_1.createGoogleGenerativeAI)({ apiKey: key })('gemini-1.5-pro-latest');
67
- }
68
- async function askLisaForFix(filePath, fileContent, errorLog, modelProvider, apiKey, previousFixAttempt, siblingContext) {
69
- console.log(`[Lisa.ai Auto-Heal] Requesting fix from ${modelProvider} for ${filePath}...`);
70
- const model = getProvider(modelProvider, apiKey);
71
- let prompt = "You are Lisa.ai, an autonomous CI/CD expert platform.\n" +
72
- "A build/compilation error occurred. Your task is to fix the provided file so that the error resolves.\n\n" +
73
- "--- Error Log ---\n" + errorLog + "\n\n" +
74
- "--- Target File Content (" + filePath + ") ---\n" + fileContent + "\n\n" +
75
- "--- Constraints ---\n" +
76
- "1. Do not delete business logic unless absolutely necessary to resolve a missing dependency.\n" +
77
- "2. Do not suppress TypeScript errors with @ts-ignore or any type assertions unless absolutely unavoidable.\n" +
78
- "3. Fix the underlying type or logic issue.\n" +
79
- "4. CRITICAL AGILE RULE: If the error is 'Cannot find module' or 'Property DOES NOT EXIST' and you do NOT know the correct import path or cannot resolve it from the context, you MUST silently comment out the failing import and completely comment out/stub the code that relies on it. Your ultimate goal is to make the file compile successfully so the Agent can proceed to the next file.\n" +
80
- "5. FRAMEWORK TEST RULE: If fixing a testing file ('.spec' or '.test') and an Angular error happens like 'NullInjectorError: No provider for X', you MUST add X to the 'providers' array in 'TestBed.configureTestingModule' or import the required testing module. If a React testing library error arises, fix the render context.\n" +
81
- "6. CRITICAL ANGULAR RULE: If an Angular component imports 'RouterLink' or uses '[routerLink]' in its template, you MUST provide 'ActivatedRoute' in the providers array or import 'RouterTestingModule'. If it uses HTTP, import 'HttpClientTestingModule'.\n" +
82
- "7. CRITICAL STANDALONE RULE: If a component has 'standalone: true' in its decorator (check the Sibling Context!), you MUST NOT add it to the 'declarations' array. Instead, you MUST add it to the 'imports' array within 'TestBed.configureTestingModule'.\n" +
83
- "8. Return the code wrapped in a markdown code block (```typescript ... ```). Do not include any explanation or intro text.";
84
- if (siblingContext) {
85
- const isStandalone = /standalone\s*:\s*true/.test(siblingContext);
86
- if (isStandalone) {
87
- const classMatch = siblingContext.match(/export class (\w+)/);
88
- const className = classMatch ? classMatch[1] : 'the component';
89
- prompt += "\n\n--- CRITICAL ARCHITECTURAL REQUIREMENT ---\n" +
90
- "The component " + className + " is marked as STANDALONE (standalone: true).\n" +
91
- "You MUST add " + className + " to the 'imports' array within 'TestBed.configureTestingModule'.\n" +
92
- "DO NOT add it to the 'declarations' array. If you omit " + className + " from 'imports', the test will fail.";
93
- }
94
- prompt += "\n\n--- Sibling Component / Service Context ---\n" +
95
- "You are fixing a '.spec' test file. Here is the actual implementation code for the component you are testing.\n" +
96
- "Use this to identify EXACTLY which imports, Services, and Variables need to be mocked inside your 'TestBed'.\n" +
97
- siblingContext;
98
- }
99
- if (previousFixAttempt) {
100
- console.log(`[Lisa.ai Auto-Heal] Warning! Agent is looping on ${filePath}. Injecting previous failed context...`);
101
- prompt += "\n\n--- CRITICAL WARNING ---\n" +
102
- "You previously attempted to fix this file but the compiler REJECTED your fix!\n" +
103
- "Here is the previous analysis and failed fix you attempted:\n" +
104
- previousFixAttempt + "\n\n" +
105
- "DO NOT repeat the identical code changes. Try a completely different programming approach, fix syntax typos, or check for missing imports.";
106
- }
107
- const { text } = await (0, ai_1.generateText)({
108
- model,
109
- prompt,
110
- });
111
- // Bug 1 fix: handle javascript/js code blocks in addition to typescript/ts.
112
- // Without this, JS file fixes from the LLM using ```javascript blocks are not extracted —
113
- // the raw markdown (including ``` markers) gets written to disk, permanently corrupting the file.
114
- const match = text.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);
115
- return match ? match[1].trim() : text.trim();
116
- }
117
- async function generateTestForFile(sourceFilePath, sourceFileContent, modelProvider, apiKey, framework) {
118
- console.log(`[Lisa.ai Coverage] Requesting test generation from ${modelProvider} for ${sourceFilePath}...`);
119
- const model = getProvider(modelProvider, apiKey);
120
- // Bug 3 fix: include the detected testing framework in the prompt so the LLM doesn't
121
- // randomly pick Jest syntax for a Karma project (or vice-versa), causing immediate
122
- // compile failures that trigger an unnecessary heal loop.
123
- const frameworkInstruction = framework
124
- ? `3. You MUST use the '${framework}' testing framework exclusively. All imports, describe/it/test blocks, and mock utilities must follow '${framework}' conventions.\n`
125
- : "3. Include all necessary imports assuming a standard testing framework (Jest/Karma/Vitest) is available.\n";
126
- const prompt = "You are Lisa.ai, an autonomous CI/CD expert platform.\n" +
127
- "A source file lacks 100% test coverage. Your task is to generate a comprehensive testing suite covering all branches, lines, and functions.\n\n" +
128
- "--- Target File Content (" + sourceFilePath + ") ---\n" + sourceFileContent + "\n\n" +
129
- "--- Constraints ---\n" +
130
- "1. Return the generated test code wrapped in a markdown code block (```typescript ... ```).\n" +
131
- "2. Do not include any explanation or intro text.\n" +
132
- frameworkInstruction +
133
- "4. Aim for 100% logic coverage.";
134
- const { text } = await (0, ai_1.generateText)({
135
- model,
136
- prompt,
137
- });
138
- const match = text.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);
139
- return match ? match[1].trim() : text.trim();
140
- }
141
- async function updateTestForFile(sourceFilePath, sourceFileContent, testFilePath, existingTestContent, modelProvider, apiKey, framework) {
142
- console.log(`[Lisa.ai Coverage] Requesting test update from ${modelProvider} for ${sourceFilePath}...`);
143
- const model = getProvider(modelProvider, apiKey);
144
- // Bug 3 fix: same as generateTestForFile — specify the framework so updates
145
- // don't introduce incompatible syntax into an existing test suite.
146
- const frameworkInstruction = framework
147
- ? `3. You MUST use the '${framework}' testing framework exclusively. All new tests must follow '${framework}' conventions and integrate cleanly with the existing suite.\n`
148
- : "3. Append missing tests to the existing suite. Do not delete existing passing tests unless they are fundamentally broken.\n";
149
- const prompt = "You are Lisa.ai, an autonomous CI/CD expert platform.\n" +
150
- "A source file lacks 100% test coverage. You must update its existing test suite to achieve full coverage.\n\n" +
151
- "--- Target File Content (" + sourceFilePath + ") ---\n" + sourceFileContent + "\n\n" +
152
- "--- Existing Test Suite (" + testFilePath + ") ---\n" + existingTestContent + "\n\n" +
153
- "--- Constraints ---\n" +
154
- "1. Return the updated complete test code wrapped in a markdown code block (```typescript ... ```).\n" +
155
- "2. Do not include any explanation or intro text.\n" +
156
- frameworkInstruction +
157
- "4. Aim for 100% logic coverage across branches, lines, and functions.";
158
- const { text } = await (0, ai_1.generateText)({
159
- model,
160
- prompt,
161
- });
162
- const match = text.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);
163
- return match ? match[1].trim() : text.trim();
164
- }
165
- async function askLisaGeneral(promptStr, modelProvider, apiKey) {
166
- const model = getProvider(modelProvider, apiKey);
167
- const { text } = await (0, ai_1.generateText)({
168
- model,
169
- prompt: promptStr,
170
- });
171
- const match = text.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);
172
- return match ? match[1].trim() : text.trim();
173
- }
@@ -1,60 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.reportTelemetry = reportTelemetry;
37
- const path = __importStar(require("path"));
38
- const dotenv = __importStar(require("dotenv"));
39
- // CP Bug 3 fix: load .env explicitly so LISA_CONTROL_PLANE_URL is always available.
40
- dotenv.config({ path: path.resolve(__dirname, '../../.env'), quiet: true });
41
- async function reportTelemetry(event) {
42
- const MASTER_CONTROL_URL = process.env.LISA_CONTROL_PLANE_URL || 'http://localhost:3000';
43
- const url = `${MASTER_CONTROL_URL}/api/telemetry`;
44
- // Telemetry Bug 1 fix: true fire-and-forget — attach a .catch() handler and return immediately.
45
- // The previous `await fetch(...)` contradicted the "don't block" comment: with 3 telemetry
46
- // calls per file and 50+ files in a large project, the heal loop was silently waiting for
47
- // 150+ network round-trips before doing any actual work.
48
- // Keeping the function signature as async+Promise<void> means existing `await reportTelemetry()`
49
- // callsites continue to compile without change — they just resolve instantly now.
50
- fetch(url, {
51
- method: 'POST',
52
- headers: {
53
- 'Content-Type': 'application/json'
54
- },
55
- body: JSON.stringify(event)
56
- }).catch((error) => {
57
- // Silently fail if control plane is down so agent can still work detached
58
- console.debug(`[Lisa.ai Agent Debug] Failed to report telemetry: ${error.message}`);
59
- });
60
- }
@@ -1,59 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.parseCoverageSummary = parseCoverageSummary;
37
- const fs = __importStar(require("fs"));
38
- const path = __importStar(require("path"));
39
- function parseCoverageSummary(coverageFilePath) {
40
- const absolutePath = path.resolve(process.cwd(), coverageFilePath);
41
- if (!fs.existsSync(absolutePath)) {
42
- throw new Error(`[Lisa.ai Coverage Error] Coverage file not found at ${absolutePath}`);
43
- }
44
- const rawData = fs.readFileSync(absolutePath, 'utf-8');
45
- const summary = JSON.parse(rawData);
46
- const uncoveredFiles = [];
47
- for (const [file, metrics] of Object.entries(summary)) {
48
- if (file === 'total')
49
- continue;
50
- // Check if any metric is below 100%
51
- if (metrics.lines.pct < 100 ||
52
- metrics.statements.pct < 100 ||
53
- metrics.functions.pct < 100 ||
54
- metrics.branches.pct < 100) {
55
- uncoveredFiles.push(file);
56
- }
57
- }
58
- return uncoveredFiles;
59
- }
@@ -1,174 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.extractFilePath = extractFilePath;
37
- const path = __importStar(require("path"));
38
- const fs = __importStar(require("fs"));
39
- function extractFilePath(errorLog, skipFiles = [], searchDir = process.cwd()) {
40
- // Sanitize invisible ANSI color codes (e.g. \x1b[96m) from CLI output logs
41
- const cleanLog = errorLog.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, '');
42
- // Normalize skip files for reliable absolute path comparison
43
- const normalizedSkips = skipFiles.map(p => path.resolve(p));
44
- // 1. First Pass: Try to match typical JS/TS/Vue error patterns with line numbers
45
- // e.g., "src/components/Button.tsx:12:3 - error"
46
- // Supporting Windows absolute paths (e.g. C:\...)
47
- const exactErrorRegex = /([a-zA-Z]:[a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue)|[a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue))(?:\s*[:(])/g;
48
- let match;
49
- while ((match = exactErrorRegex.exec(cleanLog)) !== null) {
50
- const foundPath = match[1];
51
- if (foundPath) {
52
- // Bug 4 fix: skip paths that live inside node_modules or build output directories.
53
- // Without this check a non-scoped package path (e.g. node_modules/jest-config/build/index.js)
54
- // satisfies the regex AND exists on disk, so Lisa would attempt to "heal" a library file.
55
- if (/[/\\](node_modules|dist|build)[/\\]/.test(foundPath))
56
- continue;
57
- const absoluteFoundPath = path.isAbsolute(foundPath) ? foundPath : path.resolve(searchDir, foundPath);
58
- if (!normalizedSkips.includes(absoluteFoundPath) && fs.existsSync(absoluteFoundPath)) {
59
- return foundPath;
60
- }
61
- }
62
- }
63
- // 1.5 Special Pass for Jest Headers (FAIL src/path/to/test.js)
64
- const jestFailRegex = /FAIL\s+([a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue))/gi;
65
- let jestMatch;
66
- while ((jestMatch = jestFailRegex.exec(cleanLog)) !== null) {
67
- const foundPath = jestMatch[1];
68
- const absoluteFoundPath = path.isAbsolute(foundPath) ? foundPath : path.resolve(searchDir, foundPath);
69
- if (!normalizedSkips.includes(absoluteFoundPath) && fs.existsSync(absoluteFoundPath)) {
70
- return foundPath;
71
- }
72
- }
73
- // 2. Second Pass (Fallback): Find anything that looks like a source file
74
- // Loosened regex: Doesn't require trailing colon/paren
75
- const fallbackRegex = /([a-zA-Z]:[a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue)|[a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue))/g;
76
- let fallbackMatch;
77
- while ((fallbackMatch = fallbackRegex.exec(cleanLog)) !== null) {
78
- const foundPath = fallbackMatch[1];
79
- if (foundPath) {
80
- // Bug 4 fix: same node_modules/dist guard as pass 1.
81
- if (/[/\\](node_modules|dist|build)[/\\]/.test(foundPath))
82
- continue;
83
- const absoluteFoundPath = path.isAbsolute(foundPath) ? foundPath : path.resolve(searchDir, foundPath);
84
- if (!normalizedSkips.includes(absoluteFoundPath) && fs.existsSync(absoluteFoundPath)) {
85
- return foundPath;
86
- }
87
- }
88
- }
89
- // 3. Third Pass (Abstract Testing Fallback): Match exported PascalCase symbols
90
- // Framework-agnostic. Often testing tools say "Unexpected 'MyButton'" or "Chrome 120 MyService FAILED"
91
- const symbolRegex = /\b([A-Z][a-zA-Z0-9]{3,})\b/g;
92
- let symbolMatch;
93
- const searchedSymbols = new Set();
94
- // Filter out common JS/Browser built-ins and framework generics that are not user files
95
- const ignoreList = ['Error', 'TypeError', 'SyntaxError', 'ReferenceError', 'RangeError', 'NullInjectorError', 'Object', 'Boolean', 'String', 'Number', 'Array', 'Chrome', 'Windows', 'Linux', 'Macintosh', 'UserContext', 'TestBed', 'Module', 'Unexpected', 'Expected', 'ChromeHeadless', 'Users', 'AppData', 'Local', 'Temp', 'Process', 'Component', 'Validation', 'Directory', 'Configuration', 'Documentation'];
96
- while ((symbolMatch = symbolRegex.exec(cleanLog)) !== null) {
97
- const symbolName = symbolMatch[1];
98
- if (symbolName && !ignoreList.includes(symbolName) && !searchedSymbols.has(symbolName)) {
99
- searchedSymbols.add(symbolName);
100
- console.log(`[Lisa.ai Parser] Discovered abstract symbol failure: ${symbolName}. Scanning project tree...`);
101
- // SEARCH ENTIRE ROOT instead of just 'src/' to support diverse project structures
102
- const matchedFile = findFileBySymbolName(symbolName, searchDir, normalizedSkips);
103
- if (matchedFile) {
104
- // Return path relative to project root
105
- return path.relative(searchDir, matchedFile);
106
- }
107
- }
108
- }
109
- // Exhausted all options and couldn't find an un-skipped file
110
- return null;
111
- }
112
- /**
113
- * Recursively walks a directory looking for a source file that strictly exports the target symbol.
114
- * Designed to bypass test runners omitting absolute script paths in test exceptions.
115
- * We pass normalizedSkips to ensure we do not return a file that the Agent has explicitly blacklisted.
116
- */
117
- function findFileBySymbolName(symbolName, dir, skipLedgerAbs) {
118
- if (!fs.existsSync(dir))
119
- return null;
120
- const files = fs.readdirSync(dir);
121
- for (const file of files) {
122
- const fullPath = path.join(dir, file);
123
- if (file === 'node_modules' || file === 'dist' || file === 'build' || file === '.git' || file === '.angular')
124
- continue;
125
- let stat;
126
- try {
127
- stat = fs.statSync(fullPath);
128
- }
129
- catch (e) {
130
- continue;
131
- }
132
- if (stat.isDirectory()) {
133
- const found = findFileBySymbolName(symbolName, fullPath, skipLedgerAbs);
134
- if (found)
135
- return found;
136
- }
137
- else if (file.match(/\.(ts|tsx|js|jsx|vue)$/)) {
138
- // We ONLY care if the file natively exports/declares the symbol.
139
- // We MUST NOT match files that merely `import` the symbol.
140
- const content = fs.readFileSync(fullPath, 'utf8');
141
- if (content.includes(`class ${symbolName}`) ||
142
- content.includes(`function ${symbolName}`) ||
143
- content.includes(`const ${symbolName}`) ||
144
- content.includes(`let ${symbolName}`) ||
145
- content.includes(`exports.${symbolName}`) ||
146
- content.includes(`module.exports.${symbolName}`)) {
147
- let targetPath = fullPath;
148
- // Find standard test extensions dynamically based on ecosystem
149
- const ext = path.extname(fullPath);
150
- const base = fullPath.slice(0, -ext.length);
151
- // Check if a spec/test file already exists for this source code natively
152
- if (!file.includes('.spec.') && !file.includes('.test.')) {
153
- const possibleSpecs = [
154
- `${base}.spec${ext}`,
155
- `${base}.test${ext}`,
156
- `${base}.spec.js`,
157
- `${base}.test.js`
158
- ];
159
- for (const p of possibleSpecs) {
160
- if (fs.existsSync(p)) {
161
- targetPath = p;
162
- break;
163
- }
164
- }
165
- }
166
- // If the decided file is NOT in the skip ledger, return it.
167
- if (!skipLedgerAbs.includes(path.resolve(targetPath))) {
168
- return targetPath;
169
- }
170
- }
171
- }
172
- }
173
- return null;
174
- }