@lisa.ai/agent 1.1.6 → 1.1.7

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.
@@ -76,29 +76,51 @@ async function coverageCommand(command, modelProvider, attempt = 1, maxRetries =
76
76
  console.log(`✅ [Lisa.ai Coverage] 100% Logic Coverage Verified! No un-covered files remaining.`);
77
77
  return; // We exit the loop
78
78
  }
79
- if (attempt >= maxRetries) {
80
- console.error(`\n🚨 [Lisa.ai Error] Max coverage retries (${maxRetries}) reached with remaining gaps. Auto-generation limits exceeded.`);
81
- process.exit(1);
82
- }
79
+ // [Lisa.ai V2 Infinite Loop]
80
+ // We consciously remove the global retry cutoff. Lisa natively runs infinitely in the background
81
+ // resolving testing issues continuously until the pipeline hits 100% green code organically.
83
82
  console.log(`[Lisa.ai Coverage] Found ${uncoveredFiles.length} file(s) below 100% threshold:`, uncoveredFiles);
84
83
  // 3. Address the first uncovered file
85
84
  const targetFilePath = uncoveredFiles[0];
86
85
  const absoluteTarget = path.resolve(process.cwd(), targetFilePath);
87
- console.log(`[Lisa.ai Coverage] Requesting generated test suite for ${targetFilePath}...`);
88
- const fileContent = fs.readFileSync(absoluteTarget, 'utf-8');
89
- const testCode = await (0, llm_service_1.generateTestForFile)(targetFilePath, fileContent, modelProvider, apiKey);
90
- // Derive spec name from original file name (e.g. app.component.ts -> app.component.spec.ts)
91
86
  const parsedPath = path.parse(absoluteTarget);
92
- const specPath = path.join(parsedPath.dir, `${parsedPath.name}.spec${parsedPath.ext}`);
93
- fs.writeFileSync(specPath, testCode, 'utf-8');
94
- console.log(`[Lisa.ai Coverage] Generated and wrote Spec File to ${specPath}`);
87
+ // Dynamically detect existing Testing specs irrespective of framework
88
+ const possibleSpecs = [
89
+ path.join(parsedPath.dir, `${parsedPath.name}.spec${parsedPath.ext}`),
90
+ path.join(parsedPath.dir, `${parsedPath.name}.test${parsedPath.ext}`),
91
+ path.join(parsedPath.dir, `${parsedPath.name}.spec.js`),
92
+ path.join(parsedPath.dir, `${parsedPath.name}.test.js`),
93
+ path.join(parsedPath.dir, `${parsedPath.name}.spec.ts`),
94
+ path.join(parsedPath.dir, `${parsedPath.name}.test.ts`)
95
+ ];
96
+ let existingSpecContent = null;
97
+ let targetSpecPath = path.join(parsedPath.dir, `${parsedPath.name}.spec${parsedPath.ext}`); // Default
98
+ for (const p of possibleSpecs) {
99
+ if (fs.existsSync(p)) {
100
+ targetSpecPath = p;
101
+ existingSpecContent = fs.readFileSync(p, 'utf-8');
102
+ break;
103
+ }
104
+ }
105
+ let testCode = '';
106
+ const fileContent = fs.readFileSync(absoluteTarget, 'utf-8');
107
+ if (existingSpecContent) {
108
+ console.log(`[Lisa.ai Coverage] Existing test suite discovered for ${targetFilePath}. Requesting single-pass logic coverage append...`);
109
+ testCode = await (0, llm_service_1.updateTestForFile)(targetFilePath, fileContent, path.relative(process.cwd(), targetSpecPath), existingSpecContent, modelProvider, apiKey);
110
+ }
111
+ else {
112
+ console.log(`[Lisa.ai Coverage] Requesting newly generated test suite for ${targetFilePath}...`);
113
+ testCode = await (0, llm_service_1.generateTestForFile)(targetFilePath, fileContent, modelProvider, apiKey);
114
+ }
115
+ fs.writeFileSync(targetSpecPath, testCode, 'utf-8');
116
+ console.log(`[Lisa.ai Coverage] Wrote Spec File to ${targetSpecPath}`);
95
117
  await (0, telemetry_service_1.reportTelemetry)({
96
118
  projectId,
97
119
  type: 'coverage',
98
- filePath: targetFilePath, // Keeping original as the provided edit was syntactically incorrect for an object literal
120
+ filePath: targetFilePath,
99
121
  modelUsed: modelProvider,
100
122
  status: 'success',
101
- details: `### Missing Logic Coverage Detected\n**Auto-Generated Specification Suite (${modelProvider}):**\n\`\`\`typescript\n${testCode}\n\`\`\``
123
+ details: `### Logic Coverage Action Detected\n**Auto-Generated Specification Append (${modelProvider}):**\n\`\`\`typescript\n${testCode}\n\`\`\``
102
124
  });
103
125
  // 4. Recursive iteration to verify newly written test and hunt for next gap
104
126
  await coverageCommand(command, modelProvider, attempt + 1, maxRetries, projectId, apiKey);
@@ -41,30 +41,37 @@ const parser_1 = require("../utils/parser");
41
41
  const llm_service_1 = require("../services/llm.service");
42
42
  const git_service_1 = require("../services/git.service");
43
43
  const telemetry_service_1 = require("../services/telemetry.service");
44
+ function isolateTestCommand(globalCommand, targetFile) {
45
+ const cmd = globalCommand.toLowerCase();
46
+ // Angular/Karma
47
+ if (cmd.includes('ng test') || cmd.includes('karma')) {
48
+ return `${globalCommand} --include ${targetFile}`;
49
+ }
50
+ // Jest/Vitest/Playwright
51
+ if (cmd.includes('jest') || cmd.includes('vitest') || cmd.includes('playwright')) {
52
+ return `${globalCommand} ${targetFile}`;
53
+ }
54
+ // Cypress
55
+ if (cmd.includes('cypress')) {
56
+ return `${globalCommand} --spec ${targetFile}`;
57
+ }
58
+ return globalCommand;
59
+ }
44
60
  async function healCommand(command, modelProvider, attempt = 1, healedFilePath = null, maxRetries = 3, projectId = 'local', lastFixDetails, apiKey, skipLedger = [], consecutiveFails = 0, failTracker = {}) {
45
- console.log(`\n[Lisa.ai Executing] ${command} (Attempt ${attempt}/${maxRetries}) Using Model: ${modelProvider}`);
61
+ console.log(`\n[Lisa.ai Executing] ${command} (Global Engine) Using Model: ${modelProvider}`);
46
62
  try {
47
- // Execute command synchronously
48
- // stdio: 'pipe' captures stdout/stderr so they are thrown in error object on fail.
49
- // However, to show stream we could use inherit, but we need the error log.
50
- // For now, we capture and print it.
51
63
  const output = (0, child_process_1.execSync)(command, { encoding: 'utf-8', stdio: 'pipe' });
52
64
  console.log(output);
53
- console.log(`\n✅ [Lisa.ai Success] Command executed successfully on attempt ${attempt}.`);
54
- // If we successfully executed and had previously healed a file, PR it
65
+ console.log(`\n✅ [Lisa.ai Success] Global Command executed successfully.`);
55
66
  if (healedFilePath) {
56
67
  await (0, git_service_1.createPullRequestForHeal)(healedFilePath);
57
68
  }
69
+ return; // Successfully finished without throwing
58
70
  }
59
71
  catch (error) {
60
- console.log(`\n❌ [Lisa.ai Failure] Command failed.`);
61
- // Combine logs
72
+ console.log(`\n❌ [Lisa.ai Failure] Global Command failed.`);
62
73
  const errorLog = (error.stderr || '') + '\n' + (error.stdout || '') + '\n' + (error.message || '');
63
74
  console.error(errorLog);
64
- if (attempt >= maxRetries) {
65
- console.error(`\n🚨 [Lisa.ai Error] Max retries (${maxRetries}) reached. Auto-heal failed.`);
66
- process.exit(1);
67
- }
68
75
  console.log(`\n[Lisa.ai Auto-Heal] Analyzing errors...`);
69
76
  const filePath = (0, parser_1.extractFilePath)(errorLog, skipLedger, process.cwd());
70
77
  if (!filePath) {
@@ -76,51 +83,60 @@ async function healCommand(command, modelProvider, attempt = 1, healedFilePath =
76
83
  console.error(`\n🚨 [Lisa.ai Error] Extracted file path does not exist: ${absolutePath}`);
77
84
  process.exit(1);
78
85
  }
79
- console.log(`[Lisa.ai Auto-Heal] Identified failing file: ${filePath}`);
80
- // Read file contents to send to LLM
81
- const fileContent = fs.readFileSync(absolutePath, 'utf-8');
82
- // Increment global fail tracker for this specific file
83
- failTracker[filePath] = (failTracker[filePath] || 0) + 1;
84
- const totalFails = failTracker[filePath];
85
- if (totalFails >= 3) {
86
- console.warn(`\n[Lisa.ai Auto-Heal] ⚠️ Warning: Agent failed to heal ${filePath} after 3 overall attempts in this session. Marking as Skipped to avoid infinite Loop.`);
86
+ console.log(`[Lisa.ai Auto-Heal] Identified bleeding file: ${filePath}`);
87
+ // [Lisa.ai V2 Micro-Targeted Healing Loop]
88
+ // Instead of restarting the global CI engine (e.g. `npm run test`) and waiting for all 400 specs
89
+ // to run just to fetch the error for this one file, we trap it locally with its native CLI frame isolation arguments!
90
+ let localAttempt = 1;
91
+ let isFixed = false;
92
+ let currentErrorLog = errorLog;
93
+ let previousContext = undefined;
94
+ let generatedDetails = '';
95
+ const isolatedCommand = isolateTestCommand(command, filePath);
96
+ while (localAttempt <= maxRetries && !isFixed) {
97
+ console.log(`\n[Lisa.ai Micro-Heal] Isolating ${filePath} (Attempt ${localAttempt}/${maxRetries})`);
98
+ const fileContent = fs.readFileSync(absolutePath, 'utf-8');
99
+ const fixedCode = await (0, llm_service_1.askLisaForFix)(filePath, fileContent, currentErrorLog, modelProvider, apiKey, previousContext);
100
+ fs.writeFileSync(absolutePath, fixedCode, 'utf-8');
101
+ console.log(`[Lisa.ai Micro-Heal] Applied isolated patch to ${filePath}`);
102
+ generatedDetails = `### Auto-Heal Analysis Triggered\n**Caught Error:**\n\`\`\`bash\n${currentErrorLog}\n\`\`\`\n\n**Applied Fix (${modelProvider}):**\n\`\`\`typescript\n${fixedCode}\n\`\`\``;
103
+ // Dispatch telemetry instantly so the dashboard updates exactly when the file is patched natively
104
+ await (0, telemetry_service_1.reportTelemetry)({
105
+ projectId,
106
+ type: 'heal',
107
+ filePath: filePath,
108
+ modelUsed: modelProvider,
109
+ status: 'success',
110
+ details: generatedDetails
111
+ });
112
+ try {
113
+ // Verify the isolated file silently without exploding the Global terminal with 400 other spec errors
114
+ console.log(`[Lisa.ai Micro-Heal] Verifying fix with isolated command: ${isolatedCommand}`);
115
+ (0, child_process_1.execSync)(isolatedCommand, { encoding: 'utf-8', stdio: 'pipe' });
116
+ console.log(`✅ [Lisa.ai Micro-Heal] Isolated verification passed for ${filePath}!`);
117
+ isFixed = true;
118
+ }
119
+ catch (isolatedError) {
120
+ console.log(`❌ [Lisa.ai Micro-Heal] Isolated verification failed.`);
121
+ currentErrorLog = (isolatedError.stderr || '') + '\n' + (isolatedError.stdout || '') + '\n' + (isolatedError.message || '');
122
+ previousContext = `### Attempt ${localAttempt} Failed\n\`\`\`typescript\n${fixedCode}\n\`\`\`\n\n**New Error:**\n${currentErrorLog}`;
123
+ localAttempt++;
124
+ }
125
+ }
126
+ if (!isFixed) {
127
+ console.warn(`\n[Lisa.ai Auto-Heal] ⚠️ Agent failed to locally heal ${filePath} after ${maxRetries} isolated attempts. Marking as Skipped to avoid infinite Loop.`);
87
128
  skipLedger.push(filePath);
88
- // [Lisa.ai Advanced Path Neutralization]
89
- // If the LLM failed to heal a spec file 3 times, we MUST mathematically eliminate it from the glob path
90
- // otherwise Karma will STILL execute the broken spec, exit with Code 1, and eventually trap the Agent
91
- // when the parser exhausts all files. We rename it to .broken to physically bypass the test runner.
92
- if (filePath.endsWith('.spec.ts')) {
129
+ // Advanced Path Neutralization for generic JS tests across the V2 Ecosystem Architecture
130
+ if (filePath.match(/\.(spec|test)\.(ts|js|tsx|jsx|vue)$/)) {
93
131
  const brokenPath = absolutePath + '.broken';
94
132
  try {
95
133
  fs.renameSync(absolutePath, brokenPath);
96
- console.log(`[Lisa.ai Auto-Heal] 🚨 Renamed unhealable spec to ${filePath}.broken to force the test runner to skip it!`);
97
- }
98
- catch (renameErr) {
99
- console.error(`[Lisa.ai Warning] Could not rename ${filePath}: ${renameErr.message}`);
134
+ console.log(`[Lisa.ai Auto-Heal] 🚨 Renamed unhealable spec to ${filePath}.broken to force the test runner to bypass it!`);
100
135
  }
136
+ catch (e) { }
101
137
  }
102
- // Immediately retry the loop but force the parser to skip this broken file
103
- await healCommand(command, modelProvider, attempt + 1, healedFilePath, maxRetries, projectId, lastFixDetails, apiKey, skipLedger, 0, failTracker);
104
- return;
105
138
  }
106
- // Pass previous attempted fix if we are stuck in a loop on the same file
107
- const previousContext = (totalFails > 1) ? lastFixDetails : undefined;
108
- // Call LLM for a fix
109
- const fixedCode = await (0, llm_service_1.askLisaForFix)(filePath, fileContent, errorLog, modelProvider, apiKey, previousContext);
110
- // Write the fix to the file
111
- fs.writeFileSync(absolutePath, fixedCode, 'utf-8');
112
- console.log(`[Lisa.ai Auto-Heal] Applied patch to ${filePath}`);
113
- const generatedDetails = `### Auto-Heal Analysis Triggered\n**Caught Error:**\n\`\`\`bash\n${errorLog}\n\`\`\`\n\n**Applied Fix (${modelProvider}):**\n\`\`\`typescript\n${fixedCode}\n\`\`\``;
114
- // Dispatch telemetry instantly so the dashboard updates in real-time exactly when the file is patched
115
- await (0, telemetry_service_1.reportTelemetry)({
116
- projectId,
117
- type: 'heal',
118
- filePath: filePath,
119
- modelUsed: modelProvider,
120
- status: 'success',
121
- details: generatedDetails
122
- });
123
- // Recursive retry, passing the healed file path state and updated limits
124
- await healCommand(command, modelProvider, attempt + 1, filePath, maxRetries, projectId, generatedDetails, apiKey, skipLedger, 0, failTracker);
139
+ // Return to the Global Macro CI Engine to find the next error implicitly or gracefully green-light
140
+ await healCommand(command, modelProvider, 1, isFixed ? filePath : null, maxRetries, projectId, undefined, apiKey, skipLedger, 0, failTracker);
125
141
  }
126
142
  }
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.askLisaForFix = askLisaForFix;
37
37
  exports.generateTestForFile = generateTestForFile;
38
+ exports.updateTestForFile = updateTestForFile;
38
39
  const ai_1 = require("ai");
39
40
  const openai_1 = require("@ai-sdk/openai");
40
41
  const anthropic_1 = require("@ai-sdk/anthropic");
@@ -79,7 +80,7 @@ ${fileContent}
79
80
  2. Do not suppress TypeScript errors with @ts-ignore or any type assertions unless absolutely unavoidable.
80
81
  3. Fix the underlying type or logic issue.
81
82
  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.
82
- 5. ANGULAR TEST RULE: If fixing a ".spec.ts" file and the error is 'NullInjectorError: No provider for X', you MUST add X to the 'providers' array in 'TestBed.configureTestingModule' or import the required testing module (e.g., 'HttpClientTestingModule'). If the error is 'Unexpected "X" found in the "declarations" array...marked as standalone', you MUST move X from the 'declarations' array to the 'imports' array in 'TestBed.configureTestingModule'.
83
+ 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.
83
84
  6. Return the code wrapped in a markdown code block (\`\`\`typescript ... \`\`\`). Do not include any explanation or intro text.`;
84
85
  if (previousFixAttempt) {
85
86
  console.log(`[Lisa.ai Auto-Heal] Warning! Agent is looping on ${filePath}. Injecting previous failed context...`);
@@ -100,20 +101,44 @@ async function generateTestForFile(sourceFilePath, sourceFileContent, modelProvi
100
101
  console.log(`[Lisa.ai Coverage] Requesting test generation from ${modelProvider} for ${sourceFilePath}...`);
101
102
  const model = getProvider(modelProvider, apiKey);
102
103
  const prompt = `You are Lisa.ai, an autonomous CI/CD expert platform.
103
- A TypeScript/Angular file lacks 100% test coverage. Your task is to generate a comprehensive Jest unit test file (.spec.ts) covering all branches, lines, and functions.
104
+ A source file lacks 100% test coverage. Your task is to generate a comprehensive testing suite covering all branches, lines, and functions.
104
105
 
105
106
  --- Target File Content (${sourceFilePath}) ---
106
107
  ${sourceFileContent}
107
108
 
108
109
  --- Constraints ---
109
- 1. Return the generated TypeScript test code wrapped in a markdown code block (\`\`\`typescript ... \`\`\`).
110
+ 1. Return the generated test code wrapped in a markdown code block (\`\`\`typescript ... \`\`\`).
110
111
  2. Do not include any explanation or intro text.
111
- 3. Include all necessary imports assuming Jest is available.
112
+ 3. Include all necessary imports assuming a standard testing framework (Jest/Karma/Vitest) is available.
112
113
  4. Aim for 100% logic coverage.`;
113
114
  const { text } = await (0, ai_1.generateText)({
114
115
  model,
115
116
  prompt,
116
117
  });
117
- const match = text.match(/```(?:typescript|ts)?\n([\s\S]*?)```/);
118
+ const match = text.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);
119
+ return match ? match[1].trim() : text.trim();
120
+ }
121
+ async function updateTestForFile(sourceFilePath, sourceFileContent, testFilePath, existingTestContent, modelProvider, apiKey) {
122
+ console.log(`[Lisa.ai Coverage] Requesting test update from ${modelProvider} for ${sourceFilePath}...`);
123
+ const model = getProvider(modelProvider, apiKey);
124
+ const prompt = `You are Lisa.ai, an autonomous CI/CD expert platform.
125
+ A source file lacks 100% test coverage. You must update its existing test suite to achieve full coverage.
126
+
127
+ --- Target File Content (${sourceFilePath}) ---
128
+ ${sourceFileContent}
129
+
130
+ --- Existing Test Suite (${testFilePath}) ---
131
+ ${existingTestContent}
132
+
133
+ --- Constraints ---
134
+ 1. Return the updated complete test code wrapped in a markdown code block (\`\`\`typescript ... \`\`\`).
135
+ 2. Do not include any explanation or intro text.
136
+ 3. Append missing tests to the existing suite. Do not delete existing passing tests unless they are fundamentally broken.
137
+ 4. Aim for 100% logic coverage across branches, lines, and functions.`;
138
+ const { text } = await (0, ai_1.generateText)({
139
+ model,
140
+ prompt,
141
+ });
142
+ const match = text.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);
118
143
  return match ? match[1].trim() : text.trim();
119
144
  }
@@ -41,21 +41,18 @@ function extractFilePath(errorLog, skipFiles = [], searchDir = process.cwd()) {
41
41
  const cleanLog = errorLog.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, '');
42
42
  // Normalize skip files for reliable absolute path comparison
43
43
  const normalizedSkips = skipFiles.map(p => path.resolve(p));
44
- // 1. First Pass: Try to match typical TS/Angular error patterns
45
- // e.g., "src/app/app.component.ts:12:3 - error TS2322:"
46
- const tsErrorRegex = /([a-zA-Z0-9_.\-\/\\]+\.ts)(?:[:(])/g;
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
+ const exactErrorRegex = /([a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue))(?:\s*[:(])/g;
47
47
  let match;
48
- // Loop through all matches in the error log
49
- while ((match = tsErrorRegex.exec(cleanLog)) !== null) {
48
+ while ((match = exactErrorRegex.exec(cleanLog)) !== null) {
50
49
  const foundPath = match[1];
51
- // If this path is NOT in our skip ledger, we return it as the target
52
50
  if (foundPath && !normalizedSkips.includes(path.resolve(foundPath))) {
53
51
  return foundPath;
54
52
  }
55
53
  }
56
- // 2. Second Pass (Fallback): Find anything that looks like a .ts file
57
- // Note: We use the 'g' flag so we can iterate through multiple matches if the first is skipped
58
- const fallbackRegex = /([a-zA-Z0-9_.\-\/\\]+\.ts)/g;
54
+ // 2. Second Pass (Fallback): Find anything that looks like a source file
55
+ const fallbackRegex = /([a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue))/g;
59
56
  let fallbackMatch;
60
57
  while ((fallbackMatch = fallbackRegex.exec(cleanLog)) !== null) {
61
58
  const foundPath = fallbackMatch[1];
@@ -63,16 +60,19 @@ function extractFilePath(errorLog, skipFiles = [], searchDir = process.cwd()) {
63
60
  return foundPath;
64
61
  }
65
62
  }
66
- // 3. Third Pass (Karma/Jasmine Abstract Fallback): Match Angular Class names
67
- // Example: "Chrome 145.0.0.0 (Windows 10) ConfigureNotificationComponent should create FAILED"
68
- // Example: "NullInjectorError: R3InjectorError(Standalone[RegisterComponent])"
69
- const classRegex = /([A-Z][a-zA-Z0-9]+(?:Component|Service|Module|Directive|Pipe|Guard|Interceptor))/g;
70
- let classMatch;
71
- while ((classMatch = classRegex.exec(cleanLog)) !== null) {
72
- const className = classMatch[1];
73
- if (className) {
74
- console.log(`[Lisa.ai Parser] Discovered abstract class failure: ${className}. Scanning 'src/' tree...`);
75
- const matchedFile = findFileByClassName(className, path.join(searchDir, 'src'), normalizedSkips);
63
+ // 3. Third Pass (Abstract Testing Fallback): Match exported PascalCase symbols
64
+ // Framework-agnostic. Often testing tools say "Unexpected 'MyButton'" or "Chrome 120 MyService FAILED"
65
+ const symbolRegex = /\b([A-Z][a-zA-Z0-9]{3,})\b/g;
66
+ let symbolMatch;
67
+ const searchedSymbols = new Set();
68
+ // Filter out common JS/Browser built-ins and framework generics that are not user files
69
+ const ignoreList = ['Error', 'TypeError', 'SyntaxError', 'NullInjectorError', 'Object', 'Boolean', 'String', 'Number', 'Array', 'Chrome', 'Windows', 'Linux', 'Macintosh', 'UserContext', 'TestBed', 'Module'];
70
+ while ((symbolMatch = symbolRegex.exec(cleanLog)) !== null) {
71
+ const symbolName = symbolMatch[1];
72
+ if (symbolName && !ignoreList.includes(symbolName) && !searchedSymbols.has(symbolName)) {
73
+ searchedSymbols.add(symbolName);
74
+ console.log(`[Lisa.ai Parser] Discovered abstract symbol failure: ${symbolName}. Scanning 'src/' tree...`);
75
+ const matchedFile = findFileBySymbolName(symbolName, path.join(searchDir, 'src'), normalizedSkips);
76
76
  if (matchedFile) {
77
77
  // Return path relative to project root
78
78
  return path.relative(searchDir, matchedFile);
@@ -83,11 +83,11 @@ function extractFilePath(errorLog, skipFiles = [], searchDir = process.cwd()) {
83
83
  return null;
84
84
  }
85
85
  /**
86
- * Recursively walks a directory looking for a `.ts` file that exports the target class.
87
- * Designed to bypass Karma's frustrating habit of omitting absolute script paths in Spec exceptions.
88
- * We pass normalizedSkips to ensure we do not return a file that the Agent has explicitly blacklisted this session.
86
+ * Recursively walks a directory looking for a source file that strictly exports the target symbol.
87
+ * Designed to bypass test runners omitting absolute script paths in test exceptions.
88
+ * We pass normalizedSkips to ensure we do not return a file that the Agent has explicitly blacklisted.
89
89
  */
90
- function findFileByClassName(className, dir, skipLedgerAbs) {
90
+ function findFileBySymbolName(symbolName, dir, skipLedgerAbs) {
91
91
  if (!fs.existsSync(dir))
92
92
  return null;
93
93
  const files = fs.readdirSync(dir);
@@ -95,29 +95,41 @@ function findFileByClassName(className, dir, skipLedgerAbs) {
95
95
  const fullPath = path.join(dir, file);
96
96
  const stat = fs.statSync(fullPath);
97
97
  if (stat.isDirectory()) {
98
- // Recurse into subdirectories (ignore node_modules just in case)
99
- if (file !== 'node_modules') {
100
- const found = findFileByClassName(className, fullPath, skipLedgerAbs);
98
+ // Recurse into subdirectories
99
+ if (file !== 'node_modules' && file !== 'dist' && file !== 'build') {
100
+ const found = findFileBySymbolName(symbolName, fullPath, skipLedgerAbs);
101
101
  if (found)
102
102
  return found;
103
103
  }
104
104
  }
105
- else if (file.endsWith('.ts')) {
106
- // We ONLY care if the file natively exports the class declaration.
107
- // We MUST NOT match files that merely `import` the class.
105
+ else if (file.match(/\.(ts|tsx|js|jsx|vue)$/)) {
106
+ // We ONLY care if the file natively exports/declares the symbol.
107
+ // We MUST NOT match files that merely `import` the symbol.
108
108
  const content = fs.readFileSync(fullPath, 'utf8');
109
- if (content.includes(`class ${className}`)) {
110
- // For tests, if a spec fails, we want to heal the spec file first!
111
- // If the class is found in a non-spec file, and a spec file exists next to it, favor the spec.
109
+ if (content.includes(`class ${symbolName}`) ||
110
+ content.includes(`function ${symbolName}`) ||
111
+ content.includes(`const ${symbolName}`) ||
112
+ content.includes(`let ${symbolName}`)) {
112
113
  let targetPath = fullPath;
113
- if (!file.endsWith('.spec.ts')) {
114
- const hypotheticalSpec = fullPath.replace('.ts', '.spec.ts');
115
- if (fs.existsSync(hypotheticalSpec)) {
116
- targetPath = hypotheticalSpec;
114
+ // Find standard test extensions dynamically based on ecosystem
115
+ const ext = path.extname(fullPath);
116
+ const base = fullPath.slice(0, -ext.length);
117
+ // Check if a spec/test file already exists for this source code natively
118
+ if (!file.includes('.spec.') && !file.includes('.test.')) {
119
+ const possibleSpecs = [
120
+ `${base}.spec${ext}`,
121
+ `${base}.test${ext}`,
122
+ `${base}.spec.js`,
123
+ `${base}.test.js`
124
+ ];
125
+ for (const p of possibleSpecs) {
126
+ if (fs.existsSync(p)) {
127
+ targetPath = p;
128
+ break;
129
+ }
117
130
  }
118
131
  }
119
132
  // If the decided file is NOT in the skip ledger, return it.
120
- // Otherwise, safely ignore it and keep looping for other matches in the directory tree.
121
133
  if (!skipLedgerAbs.includes(path.resolve(targetPath))) {
122
134
  return targetPath;
123
135
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lisa.ai/agent",
3
- "version": "1.1.6",
3
+ "version": "1.1.7",
4
4
  "description": "Lisa.ai Autonomous CI/CD Worker Agent",
5
5
  "main": "dist/index.js",
6
6
  "bin": {