@lisa.ai/agent 2.2.2 → 2.3.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.
- package/dist/index.js +141 -109
- package/package.json +6 -4
- package/dist/commands/coverage.js +0 -258
- package/dist/commands/heal.js +0 -277
- package/dist/services/config.service.js +0 -69
- package/dist/services/discovery.service.js +0 -180
- package/dist/services/generator.service.js +0 -84
- package/dist/services/git.service.js +0 -78
- package/dist/services/installer.service.js +0 -60
- package/dist/services/llm.service.js +0 -197
- package/dist/services/telemetry.service.js +0 -60
- package/dist/utils/coverage.parser.js +0 -59
- package/dist/utils/parser.js +0 -174
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.coverageCommand = coverageCommand;
|
|
37
|
-
const child_process_1 = require("child_process");
|
|
38
|
-
const fs = __importStar(require("fs"));
|
|
39
|
-
const path = __importStar(require("path"));
|
|
40
|
-
const coverage_parser_1 = require("../utils/coverage.parser");
|
|
41
|
-
const llm_service_1 = require("../services/llm.service");
|
|
42
|
-
const heal_1 = require("./heal");
|
|
43
|
-
const telemetry_service_1 = require("../services/telemetry.service");
|
|
44
|
-
const discovery_service_1 = require("../services/discovery.service");
|
|
45
|
-
const installer_service_1 = require("../services/installer.service");
|
|
46
|
-
const generator_service_1 = require("../services/generator.service");
|
|
47
|
-
const parser_1 = require("../utils/parser");
|
|
48
|
-
async function coverageCommand(command, modelProvider, attempt = 1, maxRetries = 3, projectId = 'local', apiKey) {
|
|
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]
|
|
55
|
-
if (!command) {
|
|
56
|
-
console.log(`\n[Lisa.ai Auto-Discovery] No explicit --command provided. Initiating Autonomous Framework Fingerprinting...`);
|
|
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);
|
|
61
|
-
}
|
|
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
|
-
}
|
|
75
|
-
console.log(`[Lisa.ai Auto-Discovery] Bootstrapping execution with natively discovered command: ${command}`);
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
console.error(`\n🚨 [Lisa.ai Fatal Error] Agent could not dynamically extrapolate a testing command for a Generic Node Environment. Please pass --command explicitly.`);
|
|
79
|
-
process.exit(1);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
console.log(`\n[Lisa.ai Coverage] ${command} (Attempt ${attempt}/${maxRetries}) Using Model: ${modelProvider}`);
|
|
83
|
-
await (0, telemetry_service_1.reportTelemetry)({
|
|
84
|
-
projectId,
|
|
85
|
-
type: 'coverage',
|
|
86
|
-
filePath: 'global-test-suite',
|
|
87
|
-
modelUsed: modelProvider,
|
|
88
|
-
status: 'running',
|
|
89
|
-
details: 'Agent is currently executing testing suite and validating coverage drops...'
|
|
90
|
-
});
|
|
91
|
-
const runSilentlyAndIntercept = (cmd, printProgress = false) => {
|
|
92
|
-
return new Promise((resolve, reject) => {
|
|
93
|
-
// Node 21+ Deprecation Fix: When shell is true, pass the entire raw command string
|
|
94
|
-
// rather than explicitly escaping array arguments to bypass DEP0190.
|
|
95
|
-
const child = (0, child_process_1.spawn)(cmd, { shell: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
96
|
-
let stdoutData = '';
|
|
97
|
-
let stderrData = '';
|
|
98
|
-
child.stdout?.on('data', (data) => {
|
|
99
|
-
const text = data.toString();
|
|
100
|
-
stdoutData += text;
|
|
101
|
-
if (printProgress) {
|
|
102
|
-
const lines = text.split('\n');
|
|
103
|
-
for (const line of lines) {
|
|
104
|
-
const match = line.match(/Executed\s+(\d+)\s+of\s+(\d+)/);
|
|
105
|
-
if (match) {
|
|
106
|
-
const failedMatch = line.match(/(\d+)\s+FAILED/);
|
|
107
|
-
const failedCount = failedMatch ? failedMatch[1] : 0;
|
|
108
|
-
const progressStr = `\r⏳ [Lisa.ai Testing] Executed ${match[1]} of ${match[2]} (${failedCount} FAILED) `;
|
|
109
|
-
process.stdout.write(progressStr);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
child.stderr?.on('data', (data) => {
|
|
115
|
-
stderrData += data.toString();
|
|
116
|
-
});
|
|
117
|
-
child.on('close', (code) => {
|
|
118
|
-
if (code === 0) {
|
|
119
|
-
resolve();
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
reject({ message: `Test process exited with code ${code}`, stdout: stdoutData, stderr: stderrData });
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
};
|
|
127
|
-
try {
|
|
128
|
-
// 1. Run the test command which should ideally produce coverage-summary.json
|
|
129
|
-
// Use the native interceptor to stream the live Karma/Jest progress bar without error spam
|
|
130
|
-
console.log(`[Lisa.ai Coverage] Booting testing framework in the background. This may take a moment...`);
|
|
131
|
-
await runSilentlyAndIntercept(command, true);
|
|
132
|
-
console.log(`\n✅ [Lisa.ai Coverage] Tests passed successfully on attempt ${attempt}.`);
|
|
133
|
-
}
|
|
134
|
-
catch (error) {
|
|
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...`);
|
|
153
|
-
}
|
|
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());
|
|
167
|
-
if (uncoveredFiles.length === 0) {
|
|
168
|
-
console.log(`✅ [Lisa.ai Coverage] Zero-Test scan complete. No untested source files discovered.`);
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
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;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
let testCode = '';
|
|
208
|
-
const fileContent = fs.readFileSync(absoluteTarget, 'utf-8');
|
|
209
|
-
// Bug 9 fix: wrap the LLM call in its own try-catch so that a context-overflow
|
|
210
|
-
// or garbage response for ONE file doesn't crash the whole coverage loop.
|
|
211
|
-
// On failure we write a minimal placeholder spec so:
|
|
212
|
-
// (a) Jest doesn't error on a missing/corrupt file
|
|
213
|
-
// (b) The coverage report records this file so it won't be re-attempted
|
|
214
|
-
// (c) The loop continues straight to the next uncovered file.
|
|
215
|
-
try {
|
|
216
|
-
if (existingSpecContent) {
|
|
217
|
-
console.log(`[Lisa.ai Coverage] Existing test suite discovered for ${targetFilePath}. Requesting single-pass logic coverage append...`);
|
|
218
|
-
testCode = await (0, llm_service_1.updateTestForFile)(targetFilePath, fileContent, path.relative(process.cwd(), targetSpecPath), existingSpecContent, modelProvider, apiKey, detectedFramework);
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
console.log(`[Lisa.ai Coverage] Requesting newly generated test suite for ${targetFilePath}...`);
|
|
222
|
-
testCode = await (0, llm_service_1.generateTestForFile)(targetFilePath, fileContent, modelProvider, apiKey, detectedFramework);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
catch (llmError) {
|
|
226
|
-
console.warn(`[Lisa.ai Coverage] Skipping ${targetFilePath} — LLM call failed: ${llmError.message}`);
|
|
227
|
-
// Placeholder keeps Jest happy and marks the file as "attempted"
|
|
228
|
-
testCode = `// Lisa.ai: Auto-skipped — ${llmError.message}\ntest('placeholder', () => { expect(true).toBe(true); });\n`;
|
|
229
|
-
}
|
|
230
|
-
fs.writeFileSync(targetSpecPath, testCode, 'utf-8');
|
|
231
|
-
console.log(`[Lisa.ai Coverage] Wrote Spec File to ${targetSpecPath}`);
|
|
232
|
-
await (0, telemetry_service_1.reportTelemetry)({
|
|
233
|
-
projectId,
|
|
234
|
-
type: 'coverage',
|
|
235
|
-
filePath: targetFilePath,
|
|
236
|
-
modelUsed: modelProvider,
|
|
237
|
-
status: 'success',
|
|
238
|
-
details: `### Logic Coverage Action Detected\n**Auto-Generated Specification Append (${modelProvider}):**\n\`\`\`typescript\n${testCode}\n\`\`\``
|
|
239
|
-
});
|
|
240
|
-
// 4. Recursive iteration to verify newly written test and hunt for next gap
|
|
241
|
-
await coverageCommand(command, modelProvider, attempt + 1, maxRetries, projectId, apiKey);
|
|
242
|
-
}
|
|
243
|
-
catch (e) {
|
|
244
|
-
console.error(`[Lisa.ai Coverage loop failure]:`, e.message);
|
|
245
|
-
// Telemetry Bug 2 fix: send an error event so the control plane dashboard doesn't
|
|
246
|
-
// show a run that started (status: 'running') but never resolved. Previously any
|
|
247
|
-
// exception thrown inside the coverage loop — LLM timeout, unreadable file, bad JSON —
|
|
248
|
-
// would be caught here and silently disappear from the dashboard.
|
|
249
|
-
await (0, telemetry_service_1.reportTelemetry)({
|
|
250
|
-
projectId,
|
|
251
|
-
type: 'coverage',
|
|
252
|
-
filePath: 'coverage-loop',
|
|
253
|
-
modelUsed: modelProvider,
|
|
254
|
-
status: 'error',
|
|
255
|
-
details: `Coverage loop encountered an unrecoverable error: ${e.message}`
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
}
|
package/dist/commands/heal.js
DELETED
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.healCommand = healCommand;
|
|
37
|
-
const child_process_1 = require("child_process");
|
|
38
|
-
const fs = __importStar(require("fs"));
|
|
39
|
-
const path = __importStar(require("path"));
|
|
40
|
-
const parser_1 = require("../utils/parser");
|
|
41
|
-
const llm_service_1 = require("../services/llm.service");
|
|
42
|
-
const git_service_1 = require("../services/git.service");
|
|
43
|
-
const telemetry_service_1 = require("../services/telemetry.service");
|
|
44
|
-
const discovery_service_1 = require("../services/discovery.service");
|
|
45
|
-
const installer_service_1 = require("../services/installer.service");
|
|
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;
|
|
51
|
-
function fetchSiblingContext(specPath) {
|
|
52
|
-
// Drop the .spec or .test extensions to find the actual Component / Service logic
|
|
53
|
-
const baseSiblingPath = specPath.replace(/\.(spec|test)\.(ts|js|jsx|tsx)$/, '.$2');
|
|
54
|
-
if (baseSiblingPath !== specPath && fs.existsSync(baseSiblingPath)) {
|
|
55
|
-
try {
|
|
56
|
-
console.log(`[Lisa.ai Context Engine] 🧠 Located sibling logic structure at ${baseSiblingPath}`);
|
|
57
|
-
return fs.readFileSync(baseSiblingPath, 'utf-8');
|
|
58
|
-
}
|
|
59
|
-
catch (e) {
|
|
60
|
-
return undefined;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return undefined;
|
|
64
|
-
}
|
|
65
|
-
function isolateTestCommand(globalCommand, targetFile) {
|
|
66
|
-
const cmd = globalCommand.toLowerCase();
|
|
67
|
-
const parsed = path.parse(targetFile);
|
|
68
|
-
// 1. Explicit Frameworks
|
|
69
|
-
if (cmd.includes('ng test') || cmd.includes('karma')) {
|
|
70
|
-
return `${globalCommand} --include **/${parsed.base}`;
|
|
71
|
-
}
|
|
72
|
-
if (cmd.includes('jest') || cmd.includes('vitest') || cmd.includes('playwright')) {
|
|
73
|
-
return `${globalCommand} ${targetFile}`;
|
|
74
|
-
}
|
|
75
|
-
if (cmd.includes('cypress')) {
|
|
76
|
-
return `${globalCommand} --spec ${targetFile}`;
|
|
77
|
-
}
|
|
78
|
-
// 2. Generic Package Manager Scripts (npm run test, yarn test)
|
|
79
|
-
if (cmd.includes('npm') || cmd.includes('yarn') || cmd.includes('pnpm')) {
|
|
80
|
-
try {
|
|
81
|
-
const packageJsonPath = path.resolve(process.cwd(), 'package.json');
|
|
82
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
83
|
-
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
84
|
-
let scriptName = 'test';
|
|
85
|
-
if (cmd.includes('npm run ')) {
|
|
86
|
-
scriptName = cmd.split('npm run ')[1].split(' ')[0];
|
|
87
|
-
}
|
|
88
|
-
else if (cmd.includes('yarn ')) {
|
|
89
|
-
scriptName = cmd.split('yarn ')[1].split(' ')[0];
|
|
90
|
-
}
|
|
91
|
-
else if (cmd.includes('pnpm ')) {
|
|
92
|
-
scriptName = cmd.split('pnpm ')[1].split(' ')[0];
|
|
93
|
-
}
|
|
94
|
-
const testScript = pkg.scripts?.[scriptName]?.toLowerCase() || '';
|
|
95
|
-
const dashDash = cmd.includes('npm') ? ' --' : '';
|
|
96
|
-
// Deducing underlying framework from package manager translation
|
|
97
|
-
if (testScript.includes('ng test') || testScript.includes('karma')) {
|
|
98
|
-
return `${globalCommand}${dashDash} --include **/${parsed.base}`;
|
|
99
|
-
}
|
|
100
|
-
if (testScript.includes('jest') || testScript.includes('vitest') || testScript.includes('playwright')) {
|
|
101
|
-
return `${globalCommand}${dashDash} ${targetFile}`;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
catch (e) {
|
|
106
|
-
// Silently fallback if package.json is inaccessible
|
|
107
|
-
}
|
|
108
|
-
// 3. Ultimate Fallback: Assume it behaves like standard generic node environments
|
|
109
|
-
const dashDash = cmd.includes('npm') ? ' --' : '';
|
|
110
|
-
return `${globalCommand}${dashDash} ${targetFile}`;
|
|
111
|
-
}
|
|
112
|
-
return globalCommand;
|
|
113
|
-
}
|
|
114
|
-
async function healCommand(command, modelProvider, attempt = 1, healedFilePath = null, maxRetries = 3, projectId = 'local', lastFixDetails, apiKey, skipLedger = [], consecutiveFails = 0, failTracker = {}) {
|
|
115
|
-
// [Lisa.ai V2 Zero-Config Engine]
|
|
116
|
-
if (!command) {
|
|
117
|
-
console.log(`\n[Lisa.ai Auto-Discovery] No explicit --command provided. Initiating Autonomous Framework Fingerprinting...`);
|
|
118
|
-
let fingerprint = discovery_service_1.AutoDiscoveryService.scanRepository();
|
|
119
|
-
if (fingerprint.testingFramework === 'none') {
|
|
120
|
-
fingerprint = await installer_service_1.AutoInstallerService.installMissingFramework(fingerprint);
|
|
121
|
-
await generator_service_1.AutoGeneratorService.provisionConfigurationFiles(fingerprint, modelProvider, apiKey);
|
|
122
|
-
}
|
|
123
|
-
if (fingerprint.suggestedTestCommand) {
|
|
124
|
-
command = fingerprint.suggestedTestCommand;
|
|
125
|
-
console.log(`[Lisa.ai Auto-Discovery] Bootstrapping execution with natively discovered command: ${command}`);
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
console.error(`\n🚨 [Lisa.ai Fatal Error] Agent could not dynamically extrapolate a testing command for a Generic Node Environment. Please pass --command explicitly.`);
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
console.log(`\n[Lisa.ai Executing] ${command} (Global Engine) Using Model: ${modelProvider}`);
|
|
133
|
-
const runSilentlyAndIntercept = (cmd, printProgress = false) => {
|
|
134
|
-
return new Promise((resolve, reject) => {
|
|
135
|
-
// Node 21+ Deprecation Fix: When shell is true, pass the entire raw command string
|
|
136
|
-
// rather than explicitly escaping array arguments to bypass DEP0190.
|
|
137
|
-
const child = (0, child_process_1.spawn)(cmd, { shell: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
138
|
-
let stdoutData = '';
|
|
139
|
-
let stderrData = '';
|
|
140
|
-
child.stdout?.on('data', (data) => {
|
|
141
|
-
const text = data.toString();
|
|
142
|
-
stdoutData += text;
|
|
143
|
-
// [Lisa.ai Stream Interceptor]
|
|
144
|
-
// The user explicitly requested to see a single clean loading line instead of
|
|
145
|
-
// thousands of milliseconds of Redundant 'Executed' loops.
|
|
146
|
-
// Output is fully swallowed, and instead we log a spinner on boot.
|
|
147
|
-
});
|
|
148
|
-
child.stderr?.on('data', (data) => {
|
|
149
|
-
stderrData += data.toString();
|
|
150
|
-
});
|
|
151
|
-
child.on('close', (code) => {
|
|
152
|
-
if (code === 0) {
|
|
153
|
-
resolve({ stdout: stdoutData, stderr: stderrData });
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
reject({ message: `Process exited with code ${code}`, stdout: stdoutData, stderr: stderrData });
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
};
|
|
161
|
-
try {
|
|
162
|
-
console.log(`⏳ [Lisa.ai Testing] Booting Headless Test Suite in background...`);
|
|
163
|
-
await runSilentlyAndIntercept(command, false); // False = output is suppressed natively
|
|
164
|
-
console.log(`\n✅ [Lisa.ai Success] Global Command executed successfully.`);
|
|
165
|
-
if (healedFilePath) {
|
|
166
|
-
await (0, git_service_1.createPullRequestForHeal)(healedFilePath);
|
|
167
|
-
}
|
|
168
|
-
return; // Successfully finished without throwing
|
|
169
|
-
}
|
|
170
|
-
catch (error) {
|
|
171
|
-
console.log(`\n❌ [Lisa.ai Failure] Global Command failed.`);
|
|
172
|
-
const errorLog = (error.stderr || '') + '\n' + (error.stdout || '') + '\n' + (error.message || '');
|
|
173
|
-
// console.error(errorLog); // Suppressed to keep "Pure Magic" CLI clean. Error is parsed abstractly.
|
|
174
|
-
console.log(`\n[Lisa.ai Auto-Heal] Analyzing errors...`);
|
|
175
|
-
const filePath = (0, parser_1.extractFilePath)(errorLog, skipLedger, process.cwd());
|
|
176
|
-
if (!filePath) {
|
|
177
|
-
console.error(`\n🚨 [Lisa.ai Error] Could not parse a valid failing file path from the logs (or all were explicitly skipped).`);
|
|
178
|
-
process.exit(1);
|
|
179
|
-
}
|
|
180
|
-
const absolutePath = path.resolve(process.cwd(), filePath);
|
|
181
|
-
if (!fs.existsSync(absolutePath)) {
|
|
182
|
-
console.error(`\n🚨 [Lisa.ai Error] Extracted file path does not exist: ${absolutePath}`);
|
|
183
|
-
process.exit(1);
|
|
184
|
-
}
|
|
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})`);
|
|
197
|
-
// [Lisa.ai V2 Micro-Targeted Healing Loop]
|
|
198
|
-
// Instead of restarting the global CI engine (e.g. `npm run test`) and waiting for all 400 specs
|
|
199
|
-
// to run just to fetch the error for this one file, we trap it locally with its native CLI frame isolation arguments!
|
|
200
|
-
let localAttempt = 1;
|
|
201
|
-
let isFixed = false;
|
|
202
|
-
let currentErrorLog = errorLog;
|
|
203
|
-
let previousContext = undefined;
|
|
204
|
-
let generatedDetails = '';
|
|
205
|
-
const isolatedCommand = isolateTestCommand(command, filePath);
|
|
206
|
-
const siblingContext = fetchSiblingContext(absolutePath);
|
|
207
|
-
await (0, telemetry_service_1.reportTelemetry)({
|
|
208
|
-
projectId,
|
|
209
|
-
type: 'heal',
|
|
210
|
-
filePath: filePath,
|
|
211
|
-
modelUsed: modelProvider,
|
|
212
|
-
status: 'running',
|
|
213
|
-
details: 'Agent is currently analyzing and applying patches...'
|
|
214
|
-
});
|
|
215
|
-
while (localAttempt <= maxRetries && !isFixed) {
|
|
216
|
-
console.log(`\n[Lisa.ai Micro-Heal] Isolating ${filePath} (Attempt ${localAttempt}/${maxRetries})`);
|
|
217
|
-
const fileContent = fs.readFileSync(absolutePath, 'utf-8');
|
|
218
|
-
const fixedCode = await (0, llm_service_1.askLisaForFix)(filePath, fileContent, currentErrorLog, modelProvider, apiKey, previousContext, siblingContext);
|
|
219
|
-
fs.writeFileSync(absolutePath, fixedCode, 'utf-8');
|
|
220
|
-
console.log(`[Lisa.ai Micro-Heal] Applied isolated patch to ${filePath}`);
|
|
221
|
-
generatedDetails = `### Auto-Heal Analysis Triggered\n**Caught Error:**\n\`\`\`bash\n${currentErrorLog}\n\`\`\`\n\n**Applied Fix (${modelProvider}):**\n\`\`\`typescript\n${fixedCode}\n\`\`\``;
|
|
222
|
-
try {
|
|
223
|
-
// Verify the isolated file silently without exploding the Global terminal with 400 other spec errors
|
|
224
|
-
console.log(`[Lisa.ai Micro-Heal] Verifying fix with isolated command: ${isolatedCommand}`);
|
|
225
|
-
await runSilentlyAndIntercept(isolatedCommand, false); // False = completely invisible isolated healing
|
|
226
|
-
console.log(`✅ [Lisa.ai Micro-Heal] Isolated verification passed for ${filePath}!`);
|
|
227
|
-
isFixed = true;
|
|
228
|
-
}
|
|
229
|
-
catch (isolatedError) {
|
|
230
|
-
console.log(`❌ [Lisa.ai Micro-Heal] Isolated verification failed.`);
|
|
231
|
-
// CRITICAL FIX: Ensure `stdout` is aggressively extracted because Karma/Jest pump compiler failures into stdout, not stderr.
|
|
232
|
-
const fallbackStdout = isolatedError.stdout ? isolatedError.stdout.toString() : '';
|
|
233
|
-
const fallbackStderr = isolatedError.stderr ? isolatedError.stderr.toString() : '';
|
|
234
|
-
currentErrorLog = fallbackStderr + '\n' + fallbackStdout + '\n' + (isolatedError.message || '');
|
|
235
|
-
previousContext = `### Attempt ${localAttempt} Failed\n\`\`\`typescript\n${fixedCode}\n\`\`\`\n\n**New Error:**\n${currentErrorLog}`;
|
|
236
|
-
localAttempt++;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
if (isFixed) {
|
|
240
|
-
await (0, telemetry_service_1.reportTelemetry)({
|
|
241
|
-
projectId,
|
|
242
|
-
type: 'heal',
|
|
243
|
-
filePath: filePath,
|
|
244
|
-
modelUsed: modelProvider,
|
|
245
|
-
status: 'success',
|
|
246
|
-
details: generatedDetails
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
console.warn(`\n[Lisa.ai Auto-Heal] ⚠️ Agent failed to locally heal ${filePath} after ${maxRetries} isolated attempts. Marking as Skipped to avoid infinite Loop.`);
|
|
251
|
-
await (0, telemetry_service_1.reportTelemetry)({
|
|
252
|
-
projectId,
|
|
253
|
-
type: 'heal',
|
|
254
|
-
filePath: filePath,
|
|
255
|
-
modelUsed: modelProvider,
|
|
256
|
-
status: 'error',
|
|
257
|
-
details: `Agent exhausted all ${maxRetries} attempts without success.\n\n` + generatedDetails
|
|
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}`);
|
|
263
|
-
skipLedger.push(filePath);
|
|
264
|
-
// Advanced Path Neutralization for generic JS tests across the V2 Ecosystem Architecture
|
|
265
|
-
if (filePath.match(/\.(spec|test)\.(ts|js|tsx|jsx|vue)$/)) {
|
|
266
|
-
const brokenPath = absolutePath + '.broken';
|
|
267
|
-
try {
|
|
268
|
-
fs.renameSync(absolutePath, brokenPath);
|
|
269
|
-
console.log(`[Lisa.ai Auto-Heal] 🚨 Renamed unhealable spec to ${filePath}.broken to force the test runner to bypass it!`);
|
|
270
|
-
}
|
|
271
|
-
catch (e) { }
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
// Return to the Global Macro CI Engine to find the next error implicitly or gracefully green-light
|
|
275
|
-
await healCommand(command, modelProvider, 1, isFixed ? filePath : null, maxRetries, projectId, undefined, apiKey, skipLedger, 0, failTracker);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.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 });
|
|
43
|
-
async function fetchRemoteConfig(projectId) {
|
|
44
|
-
const MASTER_CONTROL_URL = process.env.LISA_CONTROL_PLANE_URL || 'http://localhost:3000';
|
|
45
|
-
try {
|
|
46
|
-
const url = `${MASTER_CONTROL_URL}/api/config/${projectId}`;
|
|
47
|
-
console.log(`[Lisa.ai Agent] Fetching dynamic configuration from ${url}...`);
|
|
48
|
-
const response = await fetch(url);
|
|
49
|
-
if (!response.ok) {
|
|
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 };
|
|
60
|
-
}
|
|
61
|
-
const config = await response.json();
|
|
62
|
-
return { ok: true, config };
|
|
63
|
-
}
|
|
64
|
-
catch (error) {
|
|
65
|
-
// Network-level failure — the control plane is unreachable, not "project not found".
|
|
66
|
-
console.warn(`[Lisa.ai Agent Warning] Failed to reach Control Plane (${MASTER_CONTROL_URL}). Using local fallback configuration.`);
|
|
67
|
-
return { ok: false, reason: 'unreachable' };
|
|
68
|
-
}
|
|
69
|
-
}
|