@lisa.ai/agent 2.0.4 → 2.1.1

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.
@@ -64,6 +64,14 @@ async function coverageCommand(command, modelProvider, attempt = 1, maxRetries =
64
64
  }
65
65
  console.log(`\n[Lisa.ai Coverage] ${command} (Attempt ${attempt}/${maxRetries}) Using Model: ${modelProvider}`);
66
66
  let executionPassed = true;
67
+ await (0, telemetry_service_1.reportTelemetry)({
68
+ projectId,
69
+ type: 'coverage',
70
+ filePath: 'global-test-suite',
71
+ modelUsed: modelProvider,
72
+ status: 'running',
73
+ details: 'Agent is currently executing testing suite and validating coverage drops...'
74
+ });
67
75
  const runSilentlyAndIntercept = (cmd, printProgress = false) => {
68
76
  return new Promise((resolve, reject) => {
69
77
  // Node 21+ Deprecation Fix: When shell is true, pass the entire raw command string
@@ -91,8 +99,6 @@ async function coverageCommand(command, modelProvider, attempt = 1, maxRetries =
91
99
  stderrData += data.toString();
92
100
  });
93
101
  child.on('close', (code) => {
94
- if (printProgress)
95
- process.stdout.write('\n');
96
102
  if (code === 0) {
97
103
  resolve();
98
104
  }
@@ -44,6 +44,20 @@ const telemetry_service_1 = require("../services/telemetry.service");
44
44
  const discovery_service_1 = require("../services/discovery.service");
45
45
  const installer_service_1 = require("../services/installer.service");
46
46
  const generator_service_1 = require("../services/generator.service");
47
+ function fetchSiblingContext(specPath) {
48
+ // Drop the .spec or .test extensions to find the actual Component / Service logic
49
+ const baseSiblingPath = specPath.replace(/\.(spec|test)\.(ts|js|jsx|tsx)$/, '.$2');
50
+ if (baseSiblingPath !== specPath && fs.existsSync(baseSiblingPath)) {
51
+ try {
52
+ console.log(`[Lisa.ai Context Engine] 🧠 Located sibling logic structure at ${baseSiblingPath}`);
53
+ return fs.readFileSync(baseSiblingPath, 'utf-8');
54
+ }
55
+ catch (e) {
56
+ return undefined;
57
+ }
58
+ }
59
+ return undefined;
60
+ }
47
61
  function isolateTestCommand(globalCommand, targetFile) {
48
62
  const cmd = globalCommand.toLowerCase();
49
63
  const parsed = path.parse(targetFile);
@@ -123,27 +137,14 @@ async function healCommand(command, modelProvider, attempt = 1, healedFilePath =
123
137
  const text = data.toString();
124
138
  stdoutData += text;
125
139
  // [Lisa.ai Stream Interceptor]
126
- // The user explicitly requested to see the progress bar, but not the gigantic error dumps.
127
- // We filter the stdout to only print lines containing "Executed" (Karma/Jasmine standard).
128
- if (printProgress) {
129
- const lines = text.split('\n');
130
- for (const line of lines) {
131
- const match = line.match(/Executed\s+(\d+)\s+of\s+(\d+)/);
132
- if (match) {
133
- const failedMatch = line.match(/(\d+)\s+FAILED/);
134
- const failedCount = failedMatch ? failedMatch[1] : 0;
135
- const progressStr = `\r⏳ [Lisa.ai Testing] Executed ${match[1]} of ${match[2]} (${failedCount} FAILED) `;
136
- process.stdout.write(progressStr);
137
- }
138
- }
139
- }
140
+ // The user explicitly requested to see a single clean loading line instead of
141
+ // thousands of milliseconds of Redundant 'Executed' loops.
142
+ // Output is fully swallowed, and instead we log a spinner on boot.
140
143
  });
141
144
  child.stderr?.on('data', (data) => {
142
145
  stderrData += data.toString();
143
146
  });
144
147
  child.on('close', (code) => {
145
- if (printProgress)
146
- process.stdout.write('\n');
147
148
  if (code === 0) {
148
149
  resolve({ stdout: stdoutData, stderr: stderrData });
149
150
  }
@@ -154,7 +155,9 @@ async function healCommand(command, modelProvider, attempt = 1, healedFilePath =
154
155
  });
155
156
  };
156
157
  try {
157
- await runSilentlyAndIntercept(command, true); // True = stream progress globally
158
+ if (true)
159
+ console.log(`⏳ [Lisa.ai Testing] Booting Headless Test Suite in background...`);
160
+ await runSilentlyAndIntercept(command, false); // False = output is suppressed natively
158
161
  console.log(`\n✅ [Lisa.ai Success] Global Command executed successfully.`);
159
162
  if (healedFilePath) {
160
163
  await (0, git_service_1.createPullRequestForHeal)(healedFilePath);
@@ -186,22 +189,22 @@ async function healCommand(command, modelProvider, attempt = 1, healedFilePath =
186
189
  let previousContext = undefined;
187
190
  let generatedDetails = '';
188
191
  const isolatedCommand = isolateTestCommand(command, filePath);
192
+ const siblingContext = fetchSiblingContext(absolutePath);
193
+ await (0, telemetry_service_1.reportTelemetry)({
194
+ projectId,
195
+ type: 'heal',
196
+ filePath: filePath,
197
+ modelUsed: modelProvider,
198
+ status: 'running',
199
+ details: 'Agent is currently analyzing and applying patches...'
200
+ });
189
201
  while (localAttempt <= maxRetries && !isFixed) {
190
202
  console.log(`\n[Lisa.ai Micro-Heal] Isolating ${filePath} (Attempt ${localAttempt}/${maxRetries})`);
191
203
  const fileContent = fs.readFileSync(absolutePath, 'utf-8');
192
- const fixedCode = await (0, llm_service_1.askLisaForFix)(filePath, fileContent, currentErrorLog, modelProvider, apiKey, previousContext);
204
+ const fixedCode = await (0, llm_service_1.askLisaForFix)(filePath, fileContent, currentErrorLog, modelProvider, apiKey, previousContext, siblingContext);
193
205
  fs.writeFileSync(absolutePath, fixedCode, 'utf-8');
194
206
  console.log(`[Lisa.ai Micro-Heal] Applied isolated patch to ${filePath}`);
195
207
  generatedDetails = `### Auto-Heal Analysis Triggered\n**Caught Error:**\n\`\`\`bash\n${currentErrorLog}\n\`\`\`\n\n**Applied Fix (${modelProvider}):**\n\`\`\`typescript\n${fixedCode}\n\`\`\``;
196
- // Dispatch telemetry instantly so the dashboard updates exactly when the file is patched natively
197
- await (0, telemetry_service_1.reportTelemetry)({
198
- projectId,
199
- type: 'heal',
200
- filePath: filePath,
201
- modelUsed: modelProvider,
202
- status: 'success',
203
- details: generatedDetails
204
- });
205
208
  try {
206
209
  // Verify the isolated file silently without exploding the Global terminal with 400 other spec errors
207
210
  console.log(`[Lisa.ai Micro-Heal] Verifying fix with isolated command: ${isolatedCommand}`);
@@ -219,8 +222,26 @@ async function healCommand(command, modelProvider, attempt = 1, healedFilePath =
219
222
  localAttempt++;
220
223
  }
221
224
  }
222
- if (!isFixed) {
225
+ if (isFixed) {
226
+ await (0, telemetry_service_1.reportTelemetry)({
227
+ projectId,
228
+ type: 'heal',
229
+ filePath: filePath,
230
+ modelUsed: modelProvider,
231
+ status: 'success',
232
+ details: generatedDetails
233
+ });
234
+ }
235
+ else {
223
236
  console.warn(`\n[Lisa.ai Auto-Heal] ⚠️ Agent failed to locally heal ${filePath} after ${maxRetries} isolated attempts. Marking as Skipped to avoid infinite Loop.`);
237
+ await (0, telemetry_service_1.reportTelemetry)({
238
+ projectId,
239
+ type: 'heal',
240
+ filePath: filePath,
241
+ modelUsed: modelProvider,
242
+ status: 'error',
243
+ details: `Agent exhausted all ${maxRetries} attempts without success.\n\n` + generatedDetails
244
+ });
224
245
  skipLedger.push(filePath);
225
246
  // Advanced Path Neutralization for generic JS tests across the V2 Ecosystem Architecture
226
247
  if (filePath.match(/\.(spec|test)\.(ts|js|tsx|jsx|vue)$/)) {
@@ -43,8 +43,6 @@ const anthropic_1 = require("@ai-sdk/anthropic");
43
43
  const google_1 = require("@ai-sdk/google");
44
44
  const path = __importStar(require("path"));
45
45
  const dotenv = __importStar(require("dotenv"));
46
- // Force dotenv to load the `.env` file from the agent's root directory
47
- // regardless of which folder the user is currently running the CLI inside.
48
46
  dotenv.config({ path: path.resolve(__dirname, '../../.env') });
49
47
  function getProvider(provider, apiKey) {
50
48
  if (provider === 'claude') {
@@ -64,32 +62,44 @@ function getProvider(provider, apiKey) {
64
62
  throw new Error('No Google API key provided by local ENV or Control Plane');
65
63
  return (0, google_1.createGoogleGenerativeAI)({ apiKey: key })('gemini-1.5-pro-latest');
66
64
  }
67
- async function askLisaForFix(filePath, fileContent, errorLog, modelProvider, apiKey, previousFixAttempt) {
65
+ async function askLisaForFix(filePath, fileContent, errorLog, modelProvider, apiKey, previousFixAttempt, siblingContext) {
68
66
  console.log(`[Lisa.ai Auto-Heal] Requesting fix from ${modelProvider} for ${filePath}...`);
69
67
  const model = getProvider(modelProvider, apiKey);
70
- let prompt = `You are Lisa.ai, an autonomous CI/CD expert platform.
71
- A build/compilation error occurred. Your task is to fix the provided file so that the error resolves.
72
-
73
- --- Error Log ---
74
- ${errorLog}
75
-
76
- --- Target File Content (${filePath}) ---
77
- ${fileContent}
78
-
79
- --- Constraints ---
80
- 1. Do not delete business logic unless absolutely necessary to resolve a missing dependency.
81
- 2. Do not suppress TypeScript errors with @ts-ignore or any type assertions unless absolutely unavoidable.
82
- 3. Fix the underlying type or logic issue.
83
- 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.
84
- 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.
85
- 6. Return the code wrapped in a markdown code block (\`\`\`typescript ... \`\`\`). Do not include any explanation or intro text.`;
68
+ let prompt = "You are Lisa.ai, an autonomous CI/CD expert platform.\n" +
69
+ "A build/compilation error occurred. Your task is to fix the provided file so that the error resolves.\n\n" +
70
+ "--- Error Log ---\n" + errorLog + "\n\n" +
71
+ "--- Target File Content (" + filePath + ") ---\n" + fileContent + "\n\n" +
72
+ "--- Constraints ---\n" +
73
+ "1. Do not delete business logic unless absolutely necessary to resolve a missing dependency.\n" +
74
+ "2. Do not suppress TypeScript errors with @ts-ignore or any type assertions unless absolutely unavoidable.\n" +
75
+ "3. Fix the underlying type or logic issue.\n" +
76
+ "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" +
77
+ "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" +
78
+ "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" +
79
+ "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" +
80
+ "8. Return the code wrapped in a markdown code block (```typescript ... ```). Do not include any explanation or intro text.";
81
+ if (siblingContext) {
82
+ const isStandalone = /standalone\s*:\s*true/.test(siblingContext);
83
+ if (isStandalone) {
84
+ const classMatch = siblingContext.match(/export class (\w+)/);
85
+ const className = classMatch ? classMatch[1] : 'the component';
86
+ prompt += "\n\n--- CRITICAL ARCHITECTURAL REQUIREMENT ---\n" +
87
+ "The component " + className + " is marked as STANDALONE (standalone: true).\n" +
88
+ "You MUST add " + className + " to the 'imports' array within 'TestBed.configureTestingModule'.\n" +
89
+ "DO NOT add it to the 'declarations' array. If you omit " + className + " from 'imports', the test will fail.";
90
+ }
91
+ prompt += "\n\n--- Sibling Component / Service Context ---\n" +
92
+ "You are fixing a '.spec' test file. Here is the actual implementation code for the component you are testing.\n" +
93
+ "Use this to identify EXACTLY which imports, Services, and Variables need to be mocked inside your 'TestBed'.\n" +
94
+ siblingContext;
95
+ }
86
96
  if (previousFixAttempt) {
87
97
  console.log(`[Lisa.ai Auto-Heal] Warning! Agent is looping on ${filePath}. Injecting previous failed context...`);
88
- prompt += `\n\n--- CRITICAL WARNING ---\nYou previously attempted to fix this file but the compiler REJECTED your fix!
89
- Here is the previous analysis and failed fix you attempted:
90
- ${previousFixAttempt}
91
-
92
- DO NOT repeat the identical code changes. Try a completely different programming approach, fix syntax typos, or check for missing imports.`;
98
+ prompt += "\n\n--- CRITICAL WARNING ---\n" +
99
+ "You previously attempted to fix this file but the compiler REJECTED your fix!\n" +
100
+ "Here is the previous analysis and failed fix you attempted:\n" +
101
+ previousFixAttempt + "\n\n" +
102
+ "DO NOT repeat the identical code changes. Try a completely different programming approach, fix syntax typos, or check for missing imports.";
93
103
  }
94
104
  const { text } = await (0, ai_1.generateText)({
95
105
  model,
@@ -101,17 +111,14 @@ DO NOT repeat the identical code changes. Try a completely different programming
101
111
  async function generateTestForFile(sourceFilePath, sourceFileContent, modelProvider, apiKey) {
102
112
  console.log(`[Lisa.ai Coverage] Requesting test generation from ${modelProvider} for ${sourceFilePath}...`);
103
113
  const model = getProvider(modelProvider, apiKey);
104
- const prompt = `You are Lisa.ai, an autonomous CI/CD expert platform.
105
- A source file lacks 100% test coverage. Your task is to generate a comprehensive testing suite covering all branches, lines, and functions.
106
-
107
- --- Target File Content (${sourceFilePath}) ---
108
- ${sourceFileContent}
109
-
110
- --- Constraints ---
111
- 1. Return the generated test code wrapped in a markdown code block (\`\`\`typescript ... \`\`\`).
112
- 2. Do not include any explanation or intro text.
113
- 3. Include all necessary imports assuming a standard testing framework (Jest/Karma/Vitest) is available.
114
- 4. Aim for 100% logic coverage.`;
114
+ const prompt = "You are Lisa.ai, an autonomous CI/CD expert platform.\n" +
115
+ "A source file lacks 100% test coverage. Your task is to generate a comprehensive testing suite covering all branches, lines, and functions.\n\n" +
116
+ "--- Target File Content (" + sourceFilePath + ") ---\n" + sourceFileContent + "\n\n" +
117
+ "--- Constraints ---\n" +
118
+ "1. Return the generated test code wrapped in a markdown code block (```typescript ... ```).\n" +
119
+ "2. Do not include any explanation or intro text.\n" +
120
+ "3. Include all necessary imports assuming a standard testing framework (Jest/Karma/Vitest) is available.\n" +
121
+ "4. Aim for 100% logic coverage.";
115
122
  const { text } = await (0, ai_1.generateText)({
116
123
  model,
117
124
  prompt,
@@ -122,20 +129,15 @@ ${sourceFileContent}
122
129
  async function updateTestForFile(sourceFilePath, sourceFileContent, testFilePath, existingTestContent, modelProvider, apiKey) {
123
130
  console.log(`[Lisa.ai Coverage] Requesting test update from ${modelProvider} for ${sourceFilePath}...`);
124
131
  const model = getProvider(modelProvider, apiKey);
125
- const prompt = `You are Lisa.ai, an autonomous CI/CD expert platform.
126
- A source file lacks 100% test coverage. You must update its existing test suite to achieve full coverage.
127
-
128
- --- Target File Content (${sourceFilePath}) ---
129
- ${sourceFileContent}
130
-
131
- --- Existing Test Suite (${testFilePath}) ---
132
- ${existingTestContent}
133
-
134
- --- Constraints ---
135
- 1. Return the updated complete test code wrapped in a markdown code block (\`\`\`typescript ... \`\`\`).
136
- 2. Do not include any explanation or intro text.
137
- 3. Append missing tests to the existing suite. Do not delete existing passing tests unless they are fundamentally broken.
138
- 4. Aim for 100% logic coverage across branches, lines, and functions.`;
132
+ const prompt = "You are Lisa.ai, an autonomous CI/CD expert platform.\n" +
133
+ "A source file lacks 100% test coverage. You must update its existing test suite to achieve full coverage.\n\n" +
134
+ "--- Target File Content (" + sourceFilePath + ") ---\n" + sourceFileContent + "\n\n" +
135
+ "--- Existing Test Suite (" + testFilePath + ") ---\n" + existingTestContent + "\n\n" +
136
+ "--- Constraints ---\n" +
137
+ "1. Return the updated complete test code wrapped in a markdown code block (```typescript ... ```).\n" +
138
+ "2. Do not include any explanation or intro text.\n" +
139
+ "3. Append missing tests to the existing suite. Do not delete existing passing tests unless they are fundamentally broken.\n" +
140
+ "4. Aim for 100% logic coverage across branches, lines, and functions.";
139
141
  const { text } = await (0, ai_1.generateText)({
140
142
  model,
141
143
  prompt,
@@ -43,24 +43,25 @@ function extractFilePath(errorLog, skipFiles = [], searchDir = process.cwd()) {
43
43
  const normalizedSkips = skipFiles.map(p => path.resolve(p));
44
44
  // 1. First Pass: Try to match typical JS/TS/Vue error patterns with line numbers
45
45
  // e.g., "src/components/Button.tsx:12:3 - error"
46
- const exactErrorRegex = /([a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue))(?:\s*[:(])/g;
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;
47
48
  let match;
48
49
  while ((match = exactErrorRegex.exec(cleanLog)) !== null) {
49
50
  const foundPath = match[1];
50
51
  if (foundPath) {
51
- const absoluteFoundPath = path.resolve(searchDir, foundPath);
52
+ const absoluteFoundPath = path.isAbsolute(foundPath) ? foundPath : path.resolve(searchDir, foundPath);
52
53
  if (!normalizedSkips.includes(absoluteFoundPath) && fs.existsSync(absoluteFoundPath)) {
53
54
  return foundPath;
54
55
  }
55
56
  }
56
57
  }
57
58
  // 2. Second Pass (Fallback): Find anything that looks like a source file
58
- const fallbackRegex = /([a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue))/g;
59
+ const fallbackRegex = /([a-zA-Z]:[a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue)|[a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue))/g;
59
60
  let fallbackMatch;
60
61
  while ((fallbackMatch = fallbackRegex.exec(cleanLog)) !== null) {
61
62
  const foundPath = fallbackMatch[1];
62
63
  if (foundPath) {
63
- const absoluteFoundPath = path.resolve(searchDir, foundPath);
64
+ const absoluteFoundPath = path.isAbsolute(foundPath) ? foundPath : path.resolve(searchDir, foundPath);
64
65
  if (!normalizedSkips.includes(absoluteFoundPath) && fs.existsSync(absoluteFoundPath)) {
65
66
  return foundPath;
66
67
  }
@@ -72,7 +73,7 @@ function extractFilePath(errorLog, skipFiles = [], searchDir = process.cwd()) {
72
73
  let symbolMatch;
73
74
  const searchedSymbols = new Set();
74
75
  // Filter out common JS/Browser built-ins and framework generics that are not user files
75
- const ignoreList = ['Error', 'TypeError', 'SyntaxError', 'NullInjectorError', 'Object', 'Boolean', 'String', 'Number', 'Array', 'Chrome', 'Windows', 'Linux', 'Macintosh', 'UserContext', 'TestBed', 'Module'];
76
+ const ignoreList = ['Error', 'TypeError', 'SyntaxError', 'NullInjectorError', 'Object', 'Boolean', 'String', 'Number', 'Array', 'Chrome', 'Windows', 'Linux', 'Macintosh', 'UserContext', 'TestBed', 'Module', 'Unexpected', 'Expected', 'ChromeHeadless', 'Users', 'AppData', 'Local', 'Temp', 'Process', 'Component'];
76
77
  while ((symbolMatch = symbolRegex.exec(cleanLog)) !== null) {
77
78
  const symbolName = symbolMatch[1];
78
79
  if (symbolName && !ignoreList.includes(symbolName) && !searchedSymbols.has(symbolName)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lisa.ai/agent",
3
- "version": "2.0.4",
3
+ "version": "2.1.1",
4
4
  "description": "Lisa.ai Autonomous CI/CD Worker Agent",
5
5
  "main": "dist/index.js",
6
6
  "bin": {