@lisa.ai/agent 2.1.4 → 2.2.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.
@@ -44,17 +44,34 @@ 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
+ const parser_1 = require("../utils/parser");
47
48
  async function coverageCommand(command, modelProvider, attempt = 1, maxRetries = 3, projectId = 'local', apiKey) {
48
- // [Lisa.ai V2 Zero-Config Engine]
49
+ // Bug 8 fix: Always run scanRepository() so we have a framework fingerprint regardless of
50
+ // whether the user passed --command explicitly. scanRepository() is cheap (reads package.json)
51
+ // and the framework string is critical for generating syntactically correct tests (Bug 3).
52
+ const fingerprint = discovery_service_1.AutoDiscoveryService.scanRepository();
53
+ const detectedFramework = fingerprint.testingFramework !== 'none' ? fingerprint.testingFramework : undefined;
54
+ // [Lisa.ai V2 Zero-Config Engine]
49
55
  if (!command) {
50
56
  console.log(`\n[Lisa.ai Auto-Discovery] No explicit --command provided. Initiating Autonomous Framework Fingerprinting...`);
51
- let fingerprint = discovery_service_1.AutoDiscoveryService.scanRepository();
52
- if (fingerprint.testingFramework === 'none') {
53
- fingerprint = await installer_service_1.AutoInstallerService.installMissingFramework(fingerprint);
54
- await generator_service_1.AutoGeneratorService.provisionConfigurationFiles(fingerprint, modelProvider, apiKey);
57
+ let mutableFingerprint = fingerprint;
58
+ if (mutableFingerprint.testingFramework === 'none') {
59
+ mutableFingerprint = await installer_service_1.AutoInstallerService.installMissingFramework(mutableFingerprint);
60
+ await generator_service_1.AutoGeneratorService.provisionConfigurationFiles(mutableFingerprint, modelProvider, apiKey);
55
61
  }
56
- if (fingerprint.suggestedTestCommand) {
57
- command = fingerprint.suggestedTestCommand;
62
+ if (mutableFingerprint.suggestedTestCommand) {
63
+ command = mutableFingerprint.suggestedTestCommand;
64
+ // Ensure coverage reporting is enabled. Without --coverage the test runner never
65
+ // writes coverage-summary.json, so the agent stays permanently stuck in cold-start
66
+ // discovery mode no matter how many tests it generates.
67
+ // Karma instruments coverage via karma.conf.js — don't touch it.
68
+ const fw = mutableFingerprint.testingFramework;
69
+ if ((fw === 'jest' || fw === 'vitest') && !command.includes('--coverage')) {
70
+ command = command.includes('npm run')
71
+ ? `${command} -- --coverage`
72
+ : `${command} --coverage`;
73
+ console.log(`[Lisa.ai Coverage] Coverage flag appended: ${command}`);
74
+ }
58
75
  console.log(`[Lisa.ai Auto-Discovery] Bootstrapping execution with natively discovered command: ${command}`);
59
76
  }
60
77
  else {
@@ -63,7 +80,6 @@ async function coverageCommand(command, modelProvider, attempt = 1, maxRetries =
63
80
  }
64
81
  }
65
82
  console.log(`\n[Lisa.ai Coverage] ${command} (Attempt ${attempt}/${maxRetries}) Using Model: ${modelProvider}`);
66
- let executionPassed = true;
67
83
  await (0, telemetry_service_1.reportTelemetry)({
68
84
  projectId,
69
85
  type: 'coverage',
@@ -74,7 +90,7 @@ async function coverageCommand(command, modelProvider, attempt = 1, maxRetries =
74
90
  });
75
91
  const runSilentlyAndIntercept = (cmd, printProgress = false) => {
76
92
  return new Promise((resolve, reject) => {
77
- // Node 21+ Deprecation Fix: When shell is true, pass the entire raw command string
93
+ // Node 21+ Deprecation Fix: When shell is true, pass the entire raw command string
78
94
  // rather than explicitly escaping array arguments to bypass DEP0190.
79
95
  const child = (0, child_process_1.spawn)(cmd, { shell: true, stdio: ['ignore', 'pipe', 'pipe'] });
80
96
  let stdoutData = '';
@@ -116,90 +132,116 @@ async function coverageCommand(command, modelProvider, attempt = 1, maxRetries =
116
132
  console.log(`\n✅ [Lisa.ai Coverage] Tests passed successfully on attempt ${attempt}.`);
117
133
  }
118
134
  catch (error) {
119
- console.log(`\n [Lisa.ai Coverage] Tests failed. Delegating to Auto-Heal...`);
120
- // If tests themselves fail to compile or run, we fallback exactly to auto-heal
121
- await (0, heal_1.healCommand)(command, modelProvider, 1, null, maxRetries, projectId, undefined, apiKey);
122
- // Once the auto-heal engine successfully returns, compilation errors are fixed!
123
- // We gracefully restart the coverage engine to evaluate the newly working tests.
124
- console.log(`\n🔄 [Lisa.ai Coverage] Auto-Heal successful. Restarting coverage analysis...`);
125
- await coverageCommand(command, modelProvider, attempt + 1, maxRetries, projectId, apiKey);
126
- return;
135
+ const errorLog = (error.stderr || '') + '\n' + (error.stdout || '') + '\n' + (error.message || '');
136
+ // Check whether the error output contains an actual broken source file.
137
+ // If it does real compilation/runtime failure delegate to healCommand.
138
+ // If it does NOT (e.g. Jest's "No tests found, exiting with code 1" on a cold-start
139
+ // project, or a jest config error with no file reference) delegating to healCommand
140
+ // would crash with "Could not parse a valid failing file path". Instead, fall through
141
+ // to the cold-start discovery block below which will generate the first test files.
142
+ const hasHealableFile = (0, parser_1.extractFilePath)(errorLog, [], process.cwd()) !== null;
143
+ if (hasHealableFile) {
144
+ console.log(`\n❌ [Lisa.ai Coverage] Tests failed. Delegating to Auto-Heal...`);
145
+ await (0, heal_1.healCommand)(command, modelProvider, 1, null, maxRetries, projectId, undefined, apiKey);
146
+ console.log(`\n🔄 [Lisa.ai Coverage] Auto-Heal successful. Restarting coverage analysis...`);
147
+ await coverageCommand(command, modelProvider, attempt + 1, maxRetries, projectId, apiKey);
148
+ return;
149
+ }
150
+ // No file path found in the error — most likely cold-start (no test files yet).
151
+ // Fall through to the coverage evaluation block which will trigger cold-start discovery.
152
+ console.log(`\n[Lisa.ai Coverage] No failing spec file detected in error output. Tests may not exist yet — initiating Cold-Start Discovery...`);
127
153
  }
128
- // 2. If it passed without compile/runtime errors, evaluate coverage
129
- if (executionPassed) {
130
- try {
131
- // Find JSON summary expecting standard output paths
132
- const coveragePath = path.resolve(process.cwd(), 'coverage/coverage-summary.json');
133
- let uncoveredFiles = [];
134
- if (!fs.existsSync(coveragePath)) {
135
- console.log(`\n[Lisa.ai Coverage] No coverage-summary.json found. Initiating Cold-Start Project Crawler...`);
136
- // [Lisa.ai Phase 8] Cold-Start Discovery
137
- uncoveredFiles = discovery_service_1.AutoDiscoveryService.findUntestedFiles(process.cwd());
138
- if (uncoveredFiles.length === 0) {
139
- console.log(`✅ [Lisa.ai Coverage] Zero-Test scan complete. No untested source files discovered.`);
140
- return;
141
- }
142
- console.log(`[Lisa.ai Coverage] Discovered ${uncoveredFiles.length} untested file(s). Seeding first test suite...`);
143
- }
144
- else {
145
- console.log(`[Lisa.ai Coverage] Evaluating summary...`);
146
- uncoveredFiles = (0, coverage_parser_1.parseCoverageSummary)('coverage/coverage-summary.json');
147
- }
154
+ // 2. Tests passed evaluate coverage.
155
+ // Bug 5 fix: removed dead `executionPassed` variable. It was set to `true` on line 42
156
+ // and never changed, making `if (executionPassed)` an always-true guard that added
157
+ // misleading nesting. The try block above returns/recursed-and-returned on failure,
158
+ // so reaching here already implies execution passed.
159
+ try {
160
+ // Find JSON summary expecting standard output paths
161
+ const coveragePath = path.resolve(process.cwd(), 'coverage/coverage-summary.json');
162
+ let uncoveredFiles = [];
163
+ if (!fs.existsSync(coveragePath)) {
164
+ console.log(`\n[Lisa.ai Coverage] No coverage-summary.json found. Initiating Cold-Start Project Crawler...`);
165
+ // [Lisa.ai Phase 8] Cold-Start Discovery
166
+ uncoveredFiles = discovery_service_1.AutoDiscoveryService.findUntestedFiles(process.cwd());
148
167
  if (uncoveredFiles.length === 0) {
149
- console.log(`✅ [Lisa.ai Coverage] 100% Logic Coverage Verified! No un-covered files remaining.`);
150
- return; // We exit the loop
151
- }
152
- // [Lisa.ai V2 Infinite Loop]
153
- // We consciously remove the global retry cutoff. Lisa natively runs infinitely in the background
154
- // resolving testing issues continuously until the pipeline hits 100% green code organically.
155
- console.log(`[Lisa.ai Coverage] Found ${uncoveredFiles.length} file(s) below 100% threshold:`, uncoveredFiles);
156
- // 3. Address the first uncovered file
157
- const targetFilePath = uncoveredFiles[0];
158
- const absoluteTarget = path.resolve(process.cwd(), targetFilePath);
159
- const parsedPath = path.parse(absoluteTarget);
160
- // Dynamically detect existing Testing specs irrespective of framework
161
- const possibleSpecs = [
162
- path.join(parsedPath.dir, `${parsedPath.name}.spec${parsedPath.ext}`),
163
- path.join(parsedPath.dir, `${parsedPath.name}.test${parsedPath.ext}`),
164
- path.join(parsedPath.dir, `${parsedPath.name}.spec.js`),
165
- path.join(parsedPath.dir, `${parsedPath.name}.test.js`),
166
- path.join(parsedPath.dir, `${parsedPath.name}.spec.ts`),
167
- path.join(parsedPath.dir, `${parsedPath.name}.test.ts`)
168
- ];
169
- let existingSpecContent = null;
170
- let targetSpecPath = path.join(parsedPath.dir, `${parsedPath.name}.spec${parsedPath.ext}`); // Default
171
- for (const p of possibleSpecs) {
172
- if (fs.existsSync(p)) {
173
- targetSpecPath = p;
174
- existingSpecContent = fs.readFileSync(p, 'utf-8');
175
- break;
176
- }
177
- }
178
- let testCode = '';
179
- const fileContent = fs.readFileSync(absoluteTarget, 'utf-8');
180
- if (existingSpecContent) {
181
- console.log(`[Lisa.ai Coverage] Existing test suite discovered for ${targetFilePath}. Requesting single-pass logic coverage append...`);
182
- testCode = await (0, llm_service_1.updateTestForFile)(targetFilePath, fileContent, path.relative(process.cwd(), targetSpecPath), existingSpecContent, modelProvider, apiKey);
168
+ console.log(`✅ [Lisa.ai Coverage] Zero-Test scan complete. No untested source files discovered.`);
169
+ return;
183
170
  }
184
- else {
185
- console.log(`[Lisa.ai Coverage] Requesting newly generated test suite for ${targetFilePath}...`);
186
- testCode = await (0, llm_service_1.generateTestForFile)(targetFilePath, fileContent, modelProvider, apiKey);
171
+ console.log(`[Lisa.ai Coverage] Discovered ${uncoveredFiles.length} untested file(s). Seeding first test suite...`);
172
+ }
173
+ else {
174
+ console.log(`[Lisa.ai Coverage] Evaluating summary...`);
175
+ uncoveredFiles = (0, coverage_parser_1.parseCoverageSummary)('coverage/coverage-summary.json');
176
+ }
177
+ if (uncoveredFiles.length === 0) {
178
+ console.log(`✅ [Lisa.ai Coverage] 100% Logic Coverage Verified! No un-covered files remaining.`);
179
+ return; // We exit the loop
180
+ }
181
+ // [Lisa.ai V2 Infinite Loop]
182
+ // We consciously remove the global retry cutoff. Lisa natively runs infinitely in the background
183
+ // resolving testing issues continuously until the pipeline hits 100% green code organically.
184
+ console.log(`[Lisa.ai Coverage] Found ${uncoveredFiles.length} file(s) below 100% threshold:`, uncoveredFiles);
185
+ // 3. Address the first uncovered file
186
+ const targetFilePath = uncoveredFiles[0];
187
+ const absoluteTarget = path.resolve(process.cwd(), targetFilePath);
188
+ const parsedPath = path.parse(absoluteTarget);
189
+ // Dynamically detect existing Testing specs irrespective of framework
190
+ const possibleSpecs = [
191
+ path.join(parsedPath.dir, `${parsedPath.name}.spec${parsedPath.ext}`),
192
+ path.join(parsedPath.dir, `${parsedPath.name}.test${parsedPath.ext}`),
193
+ path.join(parsedPath.dir, `${parsedPath.name}.spec.js`),
194
+ path.join(parsedPath.dir, `${parsedPath.name}.test.js`),
195
+ path.join(parsedPath.dir, `${parsedPath.name}.spec.ts`),
196
+ path.join(parsedPath.dir, `${parsedPath.name}.test.ts`)
197
+ ];
198
+ let existingSpecContent = null;
199
+ let targetSpecPath = path.join(parsedPath.dir, `${parsedPath.name}.spec${parsedPath.ext}`); // Default
200
+ for (const p of possibleSpecs) {
201
+ if (fs.existsSync(p)) {
202
+ targetSpecPath = p;
203
+ existingSpecContent = fs.readFileSync(p, 'utf-8');
204
+ break;
187
205
  }
188
- fs.writeFileSync(targetSpecPath, testCode, 'utf-8');
189
- console.log(`[Lisa.ai Coverage] Wrote Spec File to ${targetSpecPath}`);
190
- await (0, telemetry_service_1.reportTelemetry)({
191
- projectId,
192
- type: 'coverage',
193
- filePath: targetFilePath,
194
- modelUsed: modelProvider,
195
- status: 'success',
196
- details: `### Logic Coverage Action Detected\n**Auto-Generated Specification Append (${modelProvider}):**\n\`\`\`typescript\n${testCode}\n\`\`\``
197
- });
198
- // 4. Recursive iteration to verify newly written test and hunt for next gap
199
- await coverageCommand(command, modelProvider, attempt + 1, maxRetries, projectId, apiKey);
200
206
  }
201
- catch (e) {
202
- console.error(`[Lisa.ai Coverage loop failure]:`, e.message);
207
+ let testCode = '';
208
+ const fileContent = fs.readFileSync(absoluteTarget, 'utf-8');
209
+ if (existingSpecContent) {
210
+ console.log(`[Lisa.ai Coverage] Existing test suite discovered for ${targetFilePath}. Requesting single-pass logic coverage append...`);
211
+ // Bug 8 fix: pass detectedFramework so the LLM generates correct syntax for the project's framework.
212
+ testCode = await (0, llm_service_1.updateTestForFile)(targetFilePath, fileContent, path.relative(process.cwd(), targetSpecPath), existingSpecContent, modelProvider, apiKey, detectedFramework);
203
213
  }
214
+ else {
215
+ console.log(`[Lisa.ai Coverage] Requesting newly generated test suite for ${targetFilePath}...`);
216
+ // Bug 8 fix: pass detectedFramework so the LLM generates correct syntax for the project's framework.
217
+ testCode = await (0, llm_service_1.generateTestForFile)(targetFilePath, fileContent, modelProvider, apiKey, detectedFramework);
218
+ }
219
+ fs.writeFileSync(targetSpecPath, testCode, 'utf-8');
220
+ console.log(`[Lisa.ai Coverage] Wrote Spec File to ${targetSpecPath}`);
221
+ await (0, telemetry_service_1.reportTelemetry)({
222
+ projectId,
223
+ type: 'coverage',
224
+ filePath: targetFilePath,
225
+ modelUsed: modelProvider,
226
+ status: 'success',
227
+ details: `### Logic Coverage Action Detected\n**Auto-Generated Specification Append (${modelProvider}):**\n\`\`\`typescript\n${testCode}\n\`\`\``
228
+ });
229
+ // 4. Recursive iteration to verify newly written test and hunt for next gap
230
+ await coverageCommand(command, modelProvider, attempt + 1, maxRetries, projectId, apiKey);
231
+ }
232
+ catch (e) {
233
+ console.error(`[Lisa.ai Coverage loop failure]:`, e.message);
234
+ // Telemetry Bug 2 fix: send an error event so the control plane dashboard doesn't
235
+ // show a run that started (status: 'running') but never resolved. Previously any
236
+ // exception thrown inside the coverage loop — LLM timeout, unreadable file, bad JSON —
237
+ // would be caught here and silently disappear from the dashboard.
238
+ await (0, telemetry_service_1.reportTelemetry)({
239
+ projectId,
240
+ type: 'coverage',
241
+ filePath: 'coverage-loop',
242
+ modelUsed: modelProvider,
243
+ status: 'error',
244
+ details: `Coverage loop encountered an unrecoverable error: ${e.message}`
245
+ });
204
246
  }
205
247
  }
@@ -44,6 +44,10 @@ 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
+ // Bug 6 fix: threshold for the failTracker — how many times a file is allowed to exhaust
48
+ // all local retries across the entire global run before being permanently abandoned.
49
+ // This is the "3-strike skip" described in the context architecture doc.
50
+ const GLOBAL_FAIL_THRESHOLD = 3;
47
51
  function fetchSiblingContext(specPath) {
48
52
  // Drop the .spec or .test extensions to find the actual Component / Service logic
49
53
  const baseSiblingPath = specPath.replace(/\.(spec|test)\.(ts|js|jsx|tsx)$/, '.$2');
@@ -128,7 +132,7 @@ async function healCommand(command, modelProvider, attempt = 1, healedFilePath =
128
132
  console.log(`\n[Lisa.ai Executing] ${command} (Global Engine) Using Model: ${modelProvider}`);
129
133
  const runSilentlyAndIntercept = (cmd, printProgress = false) => {
130
134
  return new Promise((resolve, reject) => {
131
- // Node 21+ Deprecation Fix: When shell is true, pass the entire raw command string
135
+ // Node 21+ Deprecation Fix: When shell is true, pass the entire raw command string
132
136
  // rather than explicitly escaping array arguments to bypass DEP0190.
133
137
  const child = (0, child_process_1.spawn)(cmd, { shell: true, stdio: ['ignore', 'pipe', 'pipe'] });
134
138
  let stdoutData = '';
@@ -137,7 +141,7 @@ async function healCommand(command, modelProvider, attempt = 1, healedFilePath =
137
141
  const text = data.toString();
138
142
  stdoutData += text;
139
143
  // [Lisa.ai Stream Interceptor]
140
- // The user explicitly requested to see a single clean loading line instead of
144
+ // The user explicitly requested to see a single clean loading line instead of
141
145
  // thousands of milliseconds of Redundant 'Executed' loops.
142
146
  // Output is fully swallowed, and instead we log a spinner on boot.
143
147
  });
@@ -155,8 +159,7 @@ async function healCommand(command, modelProvider, attempt = 1, healedFilePath =
155
159
  });
156
160
  };
157
161
  try {
158
- if (true)
159
- console.log(`⏳ [Lisa.ai Testing] Booting Headless Test Suite in background...`);
162
+ console.log(`⏳ [Lisa.ai Testing] Booting Headless Test Suite in background...`);
160
163
  await runSilentlyAndIntercept(command, false); // False = output is suppressed natively
161
164
  console.log(`\n✅ [Lisa.ai Success] Global Command executed successfully.`);
162
165
  if (healedFilePath) {
@@ -179,7 +182,18 @@ async function healCommand(command, modelProvider, attempt = 1, healedFilePath =
179
182
  console.error(`\n🚨 [Lisa.ai Error] Extracted file path does not exist: ${absolutePath}`);
180
183
  process.exit(1);
181
184
  }
182
- console.log(`[Lisa.ai Auto-Heal] Identified bleeding file: ${filePath}`);
185
+ // Bug 6 fix: implement the "3-strike global skip" described in the architecture doc.
186
+ // If this file has already exhausted all local retries GLOBAL_FAIL_THRESHOLD times
187
+ // across the entire run, it is a persistently broken file. Skip it immediately
188
+ // instead of wasting more LLM calls on it.
189
+ const globalFailCount = failTracker[filePath] || 0;
190
+ if (globalFailCount >= GLOBAL_FAIL_THRESHOLD) {
191
+ console.warn(`\n[Lisa.ai Auto-Heal] ⚠️ ${filePath} has failed globally ${globalFailCount} time(s) — exceeds threshold of ${GLOBAL_FAIL_THRESHOLD}. Permanently skipping.`);
192
+ skipLedger.push(filePath);
193
+ await healCommand(command, modelProvider, 1, null, maxRetries, projectId, undefined, apiKey, skipLedger, 0, failTracker);
194
+ return;
195
+ }
196
+ console.log(`[Lisa.ai Auto-Heal] Identified bleeding file: ${filePath} (global fail count: ${globalFailCount}/${GLOBAL_FAIL_THRESHOLD})`);
183
197
  // [Lisa.ai V2 Micro-Targeted Healing Loop]
184
198
  // Instead of restarting the global CI engine (e.g. `npm run test`) and waiting for all 400 specs
185
199
  // to run just to fetch the error for this one file, we trap it locally with its native CLI frame isolation arguments!
@@ -242,6 +256,10 @@ async function healCommand(command, modelProvider, attempt = 1, healedFilePath =
242
256
  status: 'error',
243
257
  details: `Agent exhausted all ${maxRetries} attempts without success.\n\n` + generatedDetails
244
258
  });
259
+ // Bug 6 fix: increment the global fail counter for this file so the 3-strike
260
+ // check at the top of the next iteration can catch it and skip it early.
261
+ failTracker[filePath] = (failTracker[filePath] || 0) + 1;
262
+ console.log(`[Lisa.ai Auto-Heal] Global fail counter for ${filePath}: ${failTracker[filePath]}/${GLOBAL_FAIL_THRESHOLD}`);
245
263
  skipLedger.push(filePath);
246
264
  // Advanced Path Neutralization for generic JS tests across the V2 Ecosystem Architecture
247
265
  if (filePath.match(/\.(spec|test)\.(ts|js|tsx|jsx|vue)$/)) {
package/dist/index.js CHANGED
@@ -5,8 +5,8 @@ const commander_1 = require("commander");
5
5
  const config_service_1 = require("./services/config.service");
6
6
  const heal_1 = require("./commands/heal");
7
7
  const coverage_1 = require("./commands/coverage");
8
+ const pkg = require('../package.json');
8
9
  function printBanner() {
9
- const pkg = require('../package.json');
10
10
  console.log(`\n======================================================`);
11
11
  console.log(`✨ Lisa.ai Agent v${pkg.version} Running... `);
12
12
  console.log(` Where pure magic happens! 🪄`);
@@ -16,7 +16,9 @@ const program = new commander_1.Command();
16
16
  program
17
17
  .name('lisa-agent')
18
18
  .description('Lisa.ai - Autonomous CI/CD Platform Worker Agent')
19
- .version('1.0.0');
19
+ // CP Bug 2 fix: was hardcoded as '1.0.0' while package.json (and the banner) show 2.1.x.
20
+ // `lisa-agent --version` now reports the real package version.
21
+ .version(pkg.version);
20
22
  program
21
23
  .command('heal')
22
24
  .description('Run a command and autonomously heal errors')
@@ -29,21 +31,34 @@ program
29
31
  let model = options.model;
30
32
  let apiKey = undefined;
31
33
  if (options.projectId) {
32
- const config = await (0, config_service_1.fetchRemoteConfig)(options.projectId);
33
- if (!config) {
34
- console.error(`\n🚨 [Lisa.ai Agent Error] Project '${options.projectId}' does not exist on the Control Plane. Please create it in the Dashboard first or run locally without a project ID.`);
35
- process.exit(1);
34
+ // CP Bug 4 fix: fetchRemoteConfig now returns a discriminated union so we can
35
+ // tell the difference between "project not found" ( hard exit, user must create
36
+ // the project in the Dashboard) and "control plane unreachable" (→ warn and fall
37
+ // back to local CLI defaults so the agent can still do its job offline).
38
+ const result = await (0, config_service_1.fetchRemoteConfig)(options.projectId);
39
+ if (!result.ok) {
40
+ if (result.reason === 'not_found') {
41
+ console.error(`\n🚨 [Lisa.ai Agent Error] Project '${options.projectId}' does not exist on the Control Plane. Please create it in the Dashboard first or run locally without a project ID.`);
42
+ process.exit(1);
43
+ }
44
+ else {
45
+ // 'unreachable' — control plane is temporarily down; continue with local defaults
46
+ console.warn(`\n⚠️ [Lisa.ai Warning] Control Plane is unreachable. Continuing with local CLI defaults (model=${model}, maxRetries=${maxRetries}).`);
47
+ }
36
48
  }
37
- console.log(`[Lisa.ai Agent] Dynamic Config Loaded: Provider=${config.modelProvider}, MaxRetries=${config.maxRetries}`);
38
- if (config.maxRetries < 5) {
39
- console.warn(`\n⚠️ [Lisa.ai Warning] Your Dashboard Analytics Config has maxRetries set to ${config.maxRetries}. Consider increasing this to 5+ in the WEB UI for complex Angular Healing!`);
40
- }
41
- model = config.modelProvider;
42
- maxRetries = config.maxRetries;
43
- apiKey = config.apiKey;
44
- if (config.autoHealEnabled === false) {
45
- console.log(`[Lisa.ai Agent] Auto-heal is disabled by Control Plane.`);
46
- process.exit(1);
49
+ else {
50
+ const config = result.config;
51
+ console.log(`[Lisa.ai Agent] Dynamic Config Loaded: Provider=${config.modelProvider}, MaxRetries=${config.maxRetries}`);
52
+ if (config.maxRetries < 5) {
53
+ console.warn(`\n⚠️ [Lisa.ai Warning] Your Dashboard Analytics Config has maxRetries set to ${config.maxRetries}. Consider increasing this to 5+ in the WEB UI for complex Angular Healing!`);
54
+ }
55
+ model = config.modelProvider;
56
+ maxRetries = config.maxRetries;
57
+ apiKey = config.apiKey;
58
+ if (config.autoHealEnabled === false) {
59
+ console.log(`[Lisa.ai Agent] Auto-heal is disabled by Control Plane.`);
60
+ process.exit(1);
61
+ }
47
62
  }
48
63
  }
49
64
  await (0, heal_1.healCommand)(options.command, model, 1, null, maxRetries, options.projectId || 'local', undefined, apiKey);
@@ -60,18 +75,35 @@ program
60
75
  let model = options.model;
61
76
  let apiKey = undefined;
62
77
  if (options.projectId) {
63
- const config = await (0, config_service_1.fetchRemoteConfig)(options.projectId);
64
- if (!config) {
65
- console.error(`\n🚨 [Lisa.ai Agent Error] Project '${options.projectId}' does not exist on the Control Plane. Please create it in the Dashboard first or run locally without a project ID.`);
66
- process.exit(1);
78
+ // CP Bug 4 fix: same discriminated-union handling as the heal command above.
79
+ const result = await (0, config_service_1.fetchRemoteConfig)(options.projectId);
80
+ if (!result.ok) {
81
+ if (result.reason === 'not_found') {
82
+ console.error(`\n🚨 [Lisa.ai Agent Error] Project '${options.projectId}' does not exist on the Control Plane. Please create it in the Dashboard first or run locally without a project ID.`);
83
+ process.exit(1);
84
+ }
85
+ else {
86
+ console.warn(`\n⚠️ [Lisa.ai Warning] Control Plane is unreachable. Continuing with local CLI defaults (model=${model}, maxRetries=${maxRetries}).`);
87
+ }
67
88
  }
68
- console.log(`[Lisa.ai Agent] Dynamic Config Loaded: Provider=${config.modelProvider}, MaxRetries=${config.maxRetries}`);
69
- if (config.maxRetries < 5) {
70
- console.warn(`\n⚠️ [Lisa.ai Warning] Your Dashboard Analytics Config has maxRetries set to ${config.maxRetries}. Consider increasing this to 5+ in the WEB UI for complex Angular Healing!`);
89
+ else {
90
+ const config = result.config;
91
+ console.log(`[Lisa.ai Agent] Dynamic Config Loaded: Provider=${config.modelProvider}, MaxRetries=${config.maxRetries}`);
92
+ if (config.maxRetries < 5) {
93
+ console.warn(`\n⚠️ [Lisa.ai Warning] Your Dashboard Analytics Config has maxRetries set to ${config.maxRetries}. Consider increasing this to 5+ in the WEB UI for complex Angular Healing!`);
94
+ }
95
+ model = config.modelProvider;
96
+ maxRetries = config.maxRetries;
97
+ apiKey = config.apiKey;
98
+ // CP Bug 1 fix: the coverage command was missing the autoHealEnabled check.
99
+ // Without this, a user who disables auto-heal on the control plane dashboard
100
+ // would still have their tests auto-healed internally via coverageCommand's
101
+ // fallback to healCommand when tests fail to compile.
102
+ if (config.autoHealEnabled === false) {
103
+ console.log(`[Lisa.ai Agent] Auto-heal is disabled by Control Plane.`);
104
+ process.exit(1);
105
+ }
71
106
  }
72
- model = config.modelProvider;
73
- maxRetries = config.maxRetries;
74
- apiKey = config.apiKey;
75
107
  }
76
108
  await (0, coverage_1.coverageCommand)(options.command, model, 1, maxRetries, options.projectId || 'local', apiKey);
77
109
  });
@@ -1,22 +1,69 @@
1
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
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.fetchRemoteConfig = fetchRemoteConfig;
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
+ // regardless of module import order. Previously this relied on llm.service.ts being
41
+ // imported first (a fragile side-effect dependency).
42
+ dotenv.config({ path: path.resolve(__dirname, '../../.env'), quiet: true });
4
43
  async function fetchRemoteConfig(projectId) {
5
44
  const MASTER_CONTROL_URL = process.env.LISA_CONTROL_PLANE_URL || 'http://localhost:3000';
6
45
  try {
7
46
  const url = `${MASTER_CONTROL_URL}/api/config/${projectId}`;
8
47
  console.log(`[Lisa.ai Agent] Fetching dynamic configuration from ${url}...`);
9
- // Using native Node Fetch API available in Node 18+
10
48
  const response = await fetch(url);
11
49
  if (!response.ok) {
12
- console.warn(`[Lisa.ai Agent Warning] Control Plane returned ${response.status}. Falling back to default settings.`);
13
- return null;
50
+ // 404 means the project was never created on the control plane.
51
+ // Other 4xx/5xx errors are treated as unreachable so the agent can fallback.
52
+ const reason = response.status === 404 ? 'not_found' : 'unreachable';
53
+ if (reason === 'not_found') {
54
+ console.warn(`[Lisa.ai Agent Warning] Control Plane returned 404. Project '${projectId}' not found.`);
55
+ }
56
+ else {
57
+ console.warn(`[Lisa.ai Agent Warning] Control Plane returned ${response.status}. Falling back to local defaults.`);
58
+ }
59
+ return { ok: false, reason };
14
60
  }
15
61
  const config = await response.json();
16
- return config;
62
+ return { ok: true, config };
17
63
  }
18
64
  catch (error) {
65
+ // Network-level failure — the control plane is unreachable, not "project not found".
19
66
  console.warn(`[Lisa.ai Agent Warning] Failed to reach Control Plane (${MASTER_CONTROL_URL}). Using local fallback configuration.`);
20
- return null;
67
+ return { ok: false, reason: 'unreachable' };
21
68
  }
22
69
  }
@@ -83,8 +83,19 @@ class AutoDiscoveryService {
83
83
  result.suggestedTestCommand = 'npm run test';
84
84
  }
85
85
  else {
86
- // They have the library but no script; we execute binary directly
87
- result.suggestedTestCommand = result.testingFramework === 'karma' ? 'npx karma start' : `npx ${result.testingFramework}`;
86
+ // They have the library but no script; execute the binary directly.
87
+ // Include --coverage for jest/vitest so coverage-summary.json is produced.
88
+ // Without it the coverage command has no report to evaluate and gets permanently
89
+ // stuck in cold-start discovery mode.
90
+ if (result.testingFramework === 'karma') {
91
+ result.suggestedTestCommand = 'npx karma start';
92
+ }
93
+ else if (result.testingFramework === 'jest') {
94
+ result.suggestedTestCommand = 'npx jest --coverage';
95
+ }
96
+ else if (result.testingFramework === 'vitest') {
97
+ result.suggestedTestCommand = 'npx vitest run --coverage';
98
+ }
88
99
  }
89
100
  }
90
101
  return result;
@@ -13,6 +13,16 @@ async function createPullRequestForHeal(filePath) {
13
13
  const timestamp = new Date().getTime();
14
14
  const branchName = `lisa-fix/build-error-${timestamp}`;
15
15
  const commitMsg = `fix: automated auto-heal by Lisa.ai for ${filePath}`;
16
+ // Bug 7 fix: capture the current branch BEFORE making any changes so we can reliably
17
+ // restore it in the finally block. Hardcoding 'main' would leave the repo stuck on a
18
+ // lisa-fix/ branch for any project that uses 'master' or a custom default branch.
19
+ let originalBranch = 'main'; // safe fallback
20
+ try {
21
+ originalBranch = (await git.revparse(['--abbrev-ref', 'HEAD'])).trim();
22
+ }
23
+ catch (e) {
24
+ // If we can't determine the branch (e.g. detached HEAD), keep the fallback
25
+ }
16
26
  try {
17
27
  // 1. Git Config Setup
18
28
  await git.addConfig('user.name', 'Lisa.ai');
@@ -43,7 +53,9 @@ This PR was automatically generated by Lisa.ai to resolve a failing compilation/
43
53
 
44
54
  Please review the changes.`,
45
55
  head: branchName,
46
- base: 'main', // Hardcoding main, could be dynamic
56
+ // Bug 7 fix: use the branch that was active before we created the heal branch
57
+ // rather than the hardcoded 'main' string.
58
+ base: originalBranch,
47
59
  });
48
60
  console.log(`✅ [Lisa.ai PR Engine] Pull Request created successfully: ${pr.data.html_url}`);
49
61
  }
@@ -55,9 +67,9 @@ Please review the changes.`,
55
67
  console.error(`\n🚨 [Lisa.ai PR Engine Error] Failed to create Pull Request:`, error.message);
56
68
  }
57
69
  finally {
58
- // Switch back to original branch so we don't leave the user in a detached state locally
70
+ // Bug 7 fix: restore to the originally detected branch, not a hardcoded 'main'.
59
71
  try {
60
- await git.checkout('main'); // assumes main is the default, ideally parse current branch beforehand
72
+ await git.checkout(originalBranch);
61
73
  }
62
74
  catch (e) {
63
75
  // Ignore checkout cleanup error
@@ -43,19 +43,22 @@ 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
- dotenv.config({ path: path.resolve(__dirname, '../../.env') });
46
+ dotenv.config({ path: path.resolve(__dirname, '../../.env'), quiet: true });
47
47
  function getProvider(provider, apiKey) {
48
48
  if (provider === 'claude') {
49
49
  const key = apiKey || process.env.ANTHROPIC_API_KEY;
50
50
  if (!key)
51
51
  throw new Error('No Anthropic API key provided by local ENV or Control Plane');
52
- return (0, anthropic_1.createAnthropic)({ apiKey: key })('claude-3-haiku-20240307');
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');
53
55
  }
54
56
  if (provider === 'openai') {
55
57
  const key = apiKey || process.env.OPENAI_API_KEY;
56
58
  if (!key)
57
59
  throw new Error('No OpenAI API key provided by local ENV or Control Plane');
58
- return (0, openai_1.createOpenAI)({ apiKey: key })('gpt-3.5-turbo');
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');
59
62
  }
60
63
  const key = apiKey || process.env.GOOGLE_GENERATIVE_AI_API_KEY;
61
64
  if (!key)
@@ -105,19 +108,28 @@ async function askLisaForFix(filePath, fileContent, errorLog, modelProvider, api
105
108
  model,
106
109
  prompt,
107
110
  });
108
- const match = text.match(/```(?:typescript|ts)?\n([\s\S]*?)```/);
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]*?)```/);
109
115
  return match ? match[1].trim() : text.trim();
110
116
  }
111
- async function generateTestForFile(sourceFilePath, sourceFileContent, modelProvider, apiKey) {
117
+ async function generateTestForFile(sourceFilePath, sourceFileContent, modelProvider, apiKey, framework) {
112
118
  console.log(`[Lisa.ai Coverage] Requesting test generation from ${modelProvider} for ${sourceFilePath}...`);
113
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";
114
126
  const prompt = "You are Lisa.ai, an autonomous CI/CD expert platform.\n" +
115
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" +
116
128
  "--- Target File Content (" + sourceFilePath + ") ---\n" + sourceFileContent + "\n\n" +
117
129
  "--- Constraints ---\n" +
118
130
  "1. Return the generated test code wrapped in a markdown code block (```typescript ... ```).\n" +
119
131
  "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" +
132
+ frameworkInstruction +
121
133
  "4. Aim for 100% logic coverage.";
122
134
  const { text } = await (0, ai_1.generateText)({
123
135
  model,
@@ -126,9 +138,14 @@ async function generateTestForFile(sourceFilePath, sourceFileContent, modelProvi
126
138
  const match = text.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);
127
139
  return match ? match[1].trim() : text.trim();
128
140
  }
129
- async function updateTestForFile(sourceFilePath, sourceFileContent, testFilePath, existingTestContent, modelProvider, apiKey) {
141
+ async function updateTestForFile(sourceFilePath, sourceFileContent, testFilePath, existingTestContent, modelProvider, apiKey, framework) {
130
142
  console.log(`[Lisa.ai Coverage] Requesting test update from ${modelProvider} for ${sourceFilePath}...`);
131
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";
132
149
  const prompt = "You are Lisa.ai, an autonomous CI/CD expert platform.\n" +
133
150
  "A source file lacks 100% test coverage. You must update its existing test suite to achieve full coverage.\n\n" +
134
151
  "--- Target File Content (" + sourceFilePath + ") ---\n" + sourceFileContent + "\n\n" +
@@ -136,7 +153,7 @@ async function updateTestForFile(sourceFilePath, sourceFileContent, testFilePath
136
153
  "--- Constraints ---\n" +
137
154
  "1. Return the updated complete test code wrapped in a markdown code block (```typescript ... ```).\n" +
138
155
  "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" +
156
+ frameworkInstruction +
140
157
  "4. Aim for 100% logic coverage across branches, lines, and functions.";
141
158
  const { text } = await (0, ai_1.generateText)({
142
159
  model,
@@ -1,21 +1,60 @@
1
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
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
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 });
4
41
  async function reportTelemetry(event) {
5
42
  const MASTER_CONTROL_URL = process.env.LISA_CONTROL_PLANE_URL || 'http://localhost:3000';
6
- try {
7
- const url = `${MASTER_CONTROL_URL}/api/telemetry`;
8
- // Fire and forget, don't block the compilation thread heavily
9
- await fetch(url, {
10
- method: 'POST',
11
- headers: {
12
- 'Content-Type': 'application/json'
13
- },
14
- body: JSON.stringify(event)
15
- });
16
- }
17
- catch (error) {
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) => {
18
57
  // Silently fail if control plane is down so agent can still work detached
19
58
  console.debug(`[Lisa.ai Agent Debug] Failed to report telemetry: ${error.message}`);
20
- }
59
+ });
21
60
  }
@@ -49,6 +49,11 @@ function extractFilePath(errorLog, skipFiles = [], searchDir = process.cwd()) {
49
49
  while ((match = exactErrorRegex.exec(cleanLog)) !== null) {
50
50
  const foundPath = match[1];
51
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;
52
57
  const absoluteFoundPath = path.isAbsolute(foundPath) ? foundPath : path.resolve(searchDir, foundPath);
53
58
  if (!normalizedSkips.includes(absoluteFoundPath) && fs.existsSync(absoluteFoundPath)) {
54
59
  return foundPath;
@@ -72,6 +77,9 @@ function extractFilePath(errorLog, skipFiles = [], searchDir = process.cwd()) {
72
77
  while ((fallbackMatch = fallbackRegex.exec(cleanLog)) !== null) {
73
78
  const foundPath = fallbackMatch[1];
74
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;
75
83
  const absoluteFoundPath = path.isAbsolute(foundPath) ? foundPath : path.resolve(searchDir, foundPath);
76
84
  if (!normalizedSkips.includes(absoluteFoundPath) && fs.existsSync(absoluteFoundPath)) {
77
85
  return foundPath;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lisa.ai/agent",
3
- "version": "2.1.4",
3
+ "version": "2.2.1",
4
4
  "description": "Lisa.ai Autonomous CI/CD Worker Agent",
5
5
  "main": "dist/index.js",
6
6
  "bin": {