@lisa.ai/agent 2.2.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +134 -109
- package/package.json +6 -4
- package/dist/commands/coverage.js +0 -247
- package/dist/commands/heal.js +0 -277
- package/dist/services/config.service.js +0 -69
- package/dist/services/discovery.service.js +0 -152
- 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 -173
- package/dist/services/telemetry.service.js +0 -60
- package/dist/utils/coverage.parser.js +0 -59
- package/dist/utils/parser.js +0 -174
package/dist/index.js
CHANGED
|
@@ -1,110 +1,135 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
})
|
|
110
|
-
|
|
2
|
+
"use strict";var Fe=Object.create;var ie=Object.defineProperty;var Te=Object.getOwnPropertyDescriptor;var Re=Object.getOwnPropertyNames;var De=Object.getPrototypeOf,Me=Object.prototype.hasOwnProperty;var _e=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var Oe=(t,e,s,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Re(e))!Me.call(t,n)&&n!==s&&ie(t,n,{get:()=>e[n],enumerable:!(o=Te(e,n))||o.enumerable});return t};var L=(t,e,s)=>(s=t!=null?Fe(De(t)):{},Oe(e||!t||!t.__esModule?ie(s,"default",{value:t,enumerable:!0}):s,t));var Se=_e((At,He)=>{He.exports={name:"@lisa.ai/agent",version:"2.3.0",description:"Lisa.ai Autonomous CI/CD Worker Agent",main:"dist/index.js",bin:{"lisa-agent":"dist/index.js"},files:["dist/"],scripts:{build:"tsup",typecheck:"tsc --noEmit",start:"node dist/index.js",dev:"ts-node src/index.ts",prepublishOnly:"npm run typecheck && npm run build"},keywords:["lisa","ci","cd","agent"],author:"Lisa.ai",license:"ISC",dependencies:{"@ai-sdk/anthropic":"^3.0.46","@ai-sdk/google":"^3.0.30","@ai-sdk/openai":"^3.0.30","@octokit/rest":"^22.0.1",ai:"^6.0.94",commander:"^11.1.0",dotenv:"^17.3.1","simple-git":"^3.31.1"},devDependencies:{"@types/jest":"^30.0.0","@types/node":"^20.0.0","ts-node":"^10.9.1",tsup:"^8.5.1",typescript:"^5.0.0"}}});var Ee=require("commander");var re=L(require("dotenv"));re.config({quiet:!0});async function Y(t){let e=process.env.LISA_CONTROL_PLANE_URL||"http://localhost:3000";try{let s=`${e}/api/config/${t}`;console.log(`[Lisa.ai Agent] Fetching dynamic configuration from ${s}...`);let o=await fetch(s);if(!o.ok){let i=o.status===404?"not_found":"unreachable";return console.warn(i==="not_found"?`[Lisa.ai Agent Warning] Control Plane returned 404. Project '${t}' not found.`:`[Lisa.ai Agent Warning] Control Plane returned ${o.status}. Falling back to local defaults.`),{ok:!1,reason:i}}return{ok:!0,config:await o.json()}}catch{return console.warn(`[Lisa.ai Agent Warning] Failed to reach Control Plane (${e}). Using local fallback configuration.`),{ok:!1,reason:"unreachable"}}}var $e=require("child_process"),S=L(require("fs")),U=L(require("path"));var C=L(require("path")),b=L(require("fs"));function q(t,e=[],s=process.cwd()){let o=t.replace(/\x1B\[[0-9;]*[a-zA-Z]/g,""),n=e.map(u=>C.resolve(u)),i=/([a-zA-Z]:[a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue)|[a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue))(?:\s*[:(])/g,r;for(;(r=i.exec(o))!==null;){let u=r[1];if(u){if(/[/\\](node_modules|dist|build)[/\\]/.test(u))continue;let p=C.isAbsolute(u)?u:C.resolve(s,u);if(!n.includes(p)&&b.existsSync(p))return u}}let g=/FAIL\s+([a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue))/gi,d;for(;(d=g.exec(o))!==null;){let u=d[1],p=C.isAbsolute(u)?u:C.resolve(s,u);if(!n.includes(p)&&b.existsSync(p))return u}let m=/([a-zA-Z]:[a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue)|[a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue))/g,c;for(;(c=m.exec(o))!==null;){let u=c[1];if(u){if(/[/\\](node_modules|dist|build)[/\\]/.test(u))continue;let p=C.isAbsolute(u)?u:C.resolve(s,u);if(!n.includes(p)&&b.existsSync(p))return u}}let f=/\b([A-Z][a-zA-Z0-9]{3,})\b/g,l,v=new Set,a=["Error","TypeError","SyntaxError","ReferenceError","RangeError","NullInjectorError","Object","Boolean","String","Number","Array","Chrome","Windows","Linux","Macintosh","UserContext","TestBed","Module","Unexpected","Expected","ChromeHeadless","Users","AppData","Local","Temp","Process","Component","Validation","Directory","Configuration","Documentation"];for(;(l=f.exec(o))!==null;){let u=l[1];if(u&&!a.includes(u)&&!v.has(u)){v.add(u),console.log(`[Lisa.ai Parser] Discovered abstract symbol failure: ${u}. Scanning project tree...`);let p=ae(u,s,n);if(p)return C.relative(s,p)}}return null}function ae(t,e,s){if(!b.existsSync(e))return null;let o=b.readdirSync(e);for(let n of o){let i=C.join(e,n);if(n==="node_modules"||n==="dist"||n==="build"||n===".git"||n===".angular")continue;let r;try{r=b.statSync(i)}catch{continue}if(r.isDirectory()){let g=ae(t,i,s);if(g)return g}else if(n.match(/\.(ts|tsx|js|jsx|vue)$/)){let g=b.readFileSync(i,"utf8");if(g.includes(`class ${t}`)||g.includes(`function ${t}`)||g.includes(`const ${t}`)||g.includes(`let ${t}`)||g.includes(`exports.${t}`)||g.includes(`module.exports.${t}`)){let d=i,m=C.extname(i),c=i.slice(0,-m.length);if(!n.includes(".spec.")&&!n.includes(".test.")){let f=[`${c}.spec${m}`,`${c}.test${m}`,`${c}.spec.js`,`${c}.test.js`];for(let l of f)if(b.existsSync(l)){d=l;break}}if(!s.includes(C.resolve(d)))return d}}}return null}var N=require("ai"),ce=require("@ai-sdk/openai"),le=require("@ai-sdk/anthropic"),ue=require("@ai-sdk/google"),de=L(require("dotenv"));de.config({quiet:!0});var ee=15e3;function ge(t,e){return t.length<=ee?t:(console.warn(`[Lisa.ai LLM] ${e} is ${t.length} chars \u2014 truncating to ${ee} to stay within context window.`),t.slice(0,ee)+`
|
|
3
|
+
|
|
4
|
+
// ... (truncated)`)}function pe(t,e){if(!/\b(describe|it\(|test\(|expect\(|beforeEach|afterEach|suite|assert\.|cy\.)\b/.test(t))throw new Error(`LLM returned a non-code response for ${e} (possible context overflow). Skipping to prevent file corruption.`)}function W(t,e){if(t==="claude"){let n=e||process.env.ANTHROPIC_API_KEY;if(!n)throw new Error("No Anthropic API key provided by local ENV or Control Plane");let i=process.env.LISA_CLAUDE_MODEL||"claude-haiku-4-5";return(0,le.createAnthropic)({apiKey:n})(i)}if(t==="openai"){let n=e||process.env.OPENAI_API_KEY;if(!n)throw new Error("No OpenAI API key provided by local ENV or Control Plane");let i=process.env.LISA_OPENAI_MODEL||"gpt-4o-mini";return(0,ce.createOpenAI)({apiKey:n})(i)}let s=e||process.env.GOOGLE_GENERATIVE_AI_API_KEY;if(!s)throw new Error("No Google API key provided by local ENV or Control Plane");let o=process.env.LISA_GOOGLE_MODEL||"gemini-2.0-flash";return(0,ue.createGoogleGenerativeAI)({apiKey:s})(o)}async function me(t,e,s,o,n,i,r){console.log(`[Lisa.ai Auto-Heal] Requesting fix from ${o} for ${t}...`);let g=W(o,n),d=`You are Lisa.ai, an autonomous CI/CD expert platform.
|
|
5
|
+
A build/compilation error occurred. Your task is to fix the provided file so that the error resolves.
|
|
6
|
+
|
|
7
|
+
--- Error Log ---
|
|
8
|
+
`+s+`
|
|
9
|
+
|
|
10
|
+
--- Target File Content (`+t+`) ---
|
|
11
|
+
`+e+`
|
|
12
|
+
|
|
13
|
+
--- Constraints ---
|
|
14
|
+
1. Do not delete business logic unless absolutely necessary to resolve a missing dependency.
|
|
15
|
+
2. Do not suppress TypeScript errors with @ts-ignore or any type assertions unless absolutely unavoidable.
|
|
16
|
+
3. Fix the underlying type or logic issue.
|
|
17
|
+
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.
|
|
18
|
+
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.
|
|
19
|
+
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'.
|
|
20
|
+
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'.
|
|
21
|
+
8. Return the code wrapped in a markdown code block (\`\`\`typescript ... \`\`\`). Do not include any explanation or intro text.`;if(r){if(/standalone\s*:\s*true/.test(r)){let l=r.match(/export class (\w+)/),v=l?l[1]:"the component";d+=`
|
|
22
|
+
|
|
23
|
+
--- CRITICAL ARCHITECTURAL REQUIREMENT ---
|
|
24
|
+
The component `+v+` is marked as STANDALONE (standalone: true).
|
|
25
|
+
You MUST add `+v+` to the 'imports' array within 'TestBed.configureTestingModule'.
|
|
26
|
+
DO NOT add it to the 'declarations' array. If you omit `+v+" from 'imports', the test will fail."}d+=`
|
|
27
|
+
|
|
28
|
+
--- Sibling Component / Service Context ---
|
|
29
|
+
You are fixing a '.spec' test file. Here is the actual implementation code for the component you are testing.
|
|
30
|
+
Use this to identify EXACTLY which imports, Services, and Variables need to be mocked inside your 'TestBed'.
|
|
31
|
+
`+r}i&&(console.log(`[Lisa.ai Auto-Heal] Warning! Agent is looping on ${t}. Injecting previous failed context...`),d+=`
|
|
32
|
+
|
|
33
|
+
--- CRITICAL WARNING ---
|
|
34
|
+
You previously attempted to fix this file but the compiler REJECTED your fix!
|
|
35
|
+
Here is the previous analysis and failed fix you attempted:
|
|
36
|
+
`+i+`
|
|
37
|
+
|
|
38
|
+
DO NOT repeat the identical code changes. Try a completely different programming approach, fix syntax typos, or check for missing imports.`);let{text:m}=await(0,N.generateText)({model:g,prompt:d}),c=m.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);return c?c[1].trim():m.trim()}async function fe(t,e,s,o,n){console.log(`[Lisa.ai Coverage] Requesting test generation from ${s} for ${t}...`);let i=W(s,o),r=n?`3. You MUST use the '${n}' testing framework exclusively. All imports, describe/it/test blocks, and mock utilities must follow '${n}' conventions.
|
|
39
|
+
`:`3. Include all necessary imports assuming a standard testing framework (Jest/Karma/Vitest) is available.
|
|
40
|
+
`,g=`You are Lisa.ai, an autonomous CI/CD expert platform.
|
|
41
|
+
A source file lacks 100% test coverage. Your task is to generate a comprehensive testing suite covering all branches, lines, and functions.
|
|
42
|
+
|
|
43
|
+
--- Target File Content (`+t+`) ---
|
|
44
|
+
`+ge(e,t)+"\n\n--- Constraints ---\n1. Return the generated test code wrapped in a markdown code block (```typescript ... ```).\n2. Do not include any explanation or intro text.\n"+r+"4. Aim for 100% logic coverage.",{text:d}=await(0,N.generateText)({model:i,prompt:g}),m=d.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/),c=m?m[1].trim():d.trim();return pe(c,t),c}async function he(t,e,s,o,n,i,r){console.log(`[Lisa.ai Coverage] Requesting test update from ${n} for ${t}...`);let g=W(n,i),d=r?`3. You MUST use the '${r}' testing framework exclusively. All new tests must follow '${r}' conventions and integrate cleanly with the existing suite.
|
|
45
|
+
`:`3. Append missing tests to the existing suite. Do not delete existing passing tests unless they are fundamentally broken.
|
|
46
|
+
`,m=`You are Lisa.ai, an autonomous CI/CD expert platform.
|
|
47
|
+
A source file lacks 100% test coverage. You must update its existing test suite to achieve full coverage.
|
|
48
|
+
|
|
49
|
+
--- Target File Content (`+t+`) ---
|
|
50
|
+
`+ge(e,t)+`
|
|
51
|
+
|
|
52
|
+
--- Existing Test Suite (`+s+`) ---
|
|
53
|
+
`+o+"\n\n--- Constraints ---\n1. Return the updated complete test code wrapped in a markdown code block (```typescript ... ```).\n2. Do not include any explanation or intro text.\n"+d+"4. Aim for 100% logic coverage across branches, lines, and functions.",{text:c}=await(0,N.generateText)({model:g,prompt:m}),f=c.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/),l=f?f[1].trim():c.trim();return pe(l,t),l}async function z(t,e,s){let o=W(e,s),{text:n}=await(0,N.generateText)({model:o,prompt:t}),i=n.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);return i?i[1].trim():n.trim()}var ye=L(require("simple-git")),ve=require("@octokit/rest"),Ve=require("dotenv/config");async function Le(t){console.log(`
|
|
54
|
+
[Lisa.ai PR Engine] Initializing Pull Request for ${t}...`);let e=(0,ye.default)(),o=`lisa-fix/build-error-${new Date().getTime()}`,n=`fix: automated auto-heal by Lisa.ai for ${t}`,i="main";try{i=(await e.revparse(["--abbrev-ref","HEAD"])).trim()}catch{}try{await e.addConfig("user.name","Lisa.ai"),await e.addConfig("user.email","lisa@lisa.ai"),await e.checkoutLocalBranch(o),await e.add(t),await e.commit(n),console.log(`[Lisa.ai PR Engine] Committed changes to branch ${o}`),console.log("[Lisa.ai PR Engine] Pushing branch to remote origin..."),await e.push("origin",o,{"--set-upstream":null});let r=new ve.Octokit({auth:process.env.GITHUB_TOKEN}),g=process.env.GITHUB_REPOSITORY;if(g&&process.env.GITHUB_TOKEN){let[d,m]=g.split("/");console.log(`[Lisa.ai PR Engine] Opening Pull Request against ${d}/${m}...`);let c=await r.rest.pulls.create({owner:d,repo:m,title:n,body:`### Lisa.ai Auto-Healed Pull Request
|
|
55
|
+
This PR was automatically generated by Lisa.ai to resolve a failing compilation/build step.
|
|
56
|
+
|
|
57
|
+
**Healed File:** \`${t}\`
|
|
58
|
+
|
|
59
|
+
Please review the changes.`,head:o,base:i});console.log(`\u2705 [Lisa.ai PR Engine] Pull Request created successfully: ${c.data.html_url}`)}else console.log("\u26A0\uFE0F [Lisa.ai PR Engine] GITHUB_TOKEN or GITHUB_REPOSITORY not set. Skipping GitHub Pull Request creation.")}catch(r){console.error(`
|
|
60
|
+
\u{1F6A8} [Lisa.ai PR Engine Error] Failed to create Pull Request:`,r.message)}finally{try{await e.checkout(i)}catch{}}}var xe=L(require("dotenv"));xe.config({quiet:!0});async function I(t){let s=`${process.env.LISA_CONTROL_PLANE_URL||"http://localhost:3000"}/api/telemetry`;fetch(s,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}).catch(o=>{console.debug(`[Lisa.ai Agent Debug] Failed to report telemetry: ${o.message}`)})}var E=L(require("fs")),A=L(require("path"));var V=L(require("fs")),we=L(require("path")),te=".lisai.json",k=class{static _config=null;static _loaded=!1;static load(e=process.cwd()){if(this._loaded)return this._config;this._loaded=!0;let s=we.resolve(e,te);if(!V.existsSync(s))return null;try{let o=V.readFileSync(s,"utf-8");return this._config=JSON.parse(o),console.log(`[Lisa.ai Config] Loaded project config from ${te}`),this._config}catch(o){return console.warn(`[Lisa.ai Config] Warning: ${te} found but failed to parse \u2014 ${o.message}. Using defaults.`),null}}static get(){return this._config}static applyEnvDefaults(e){if(!e.model)return;let s={claude:"LISA_CLAUDE_MODEL",openai:"LISA_OPENAI_MODEL",gemini:"LISA_GOOGLE_MODEL"},o=e.provider;o&&s[o]&&!process.env[s[o]]&&(process.env[s[o]]=e.model,console.log(`[Lisa.ai Config] Model override applied: ${o} \u2192 ${e.model}`))}static printSummary(e){let s=[];e.provider&&s.push(`provider=${e.provider}`),e.model&&s.push(`model=${e.model}`),e.maxRetries!==void 0&&s.push(`maxRetries=${e.maxRetries}`),e.testCommand&&s.push(`testCommand="${e.testCommand}"`),s.length&&console.log(`[Lisa.ai Config] Settings: ${s.join(", ")}`),e.skipFiles?.length&&console.log(`[Lisa.ai Config] Extra skip files : ${e.skipFiles.join(", ")}`),e.skipDirs?.length&&console.log(`[Lisa.ai Config] Extra skip dirs : ${e.skipDirs.join(", ")}`),e.skipPaths?.length&&console.log(`[Lisa.ai Config] Extra skip paths : ${e.skipPaths.join(", ")}`)}};var T=class{static scanRepository(e=process.cwd()){let s=A.resolve(e,"package.json"),o={type:"unknown",testingFramework:"none"};if(!E.existsSync(s))return console.warn(`[Lisa.ai AutoDiscovery] No package.json found at ${s}. Defaulting to Generic Node.`),o.type="node",o;let n=JSON.parse(E.readFileSync(s,"utf8")),i={...n.dependencies||{},...n.devDependencies||{}},r=n.scripts||{};return i["@angular/core"]?o.type="angular":i.react?o.type="react":i.vue?o.type="vue":o.type="node",i.jest||r.test?.includes("jest")?o.testingFramework="jest":i.karma||r.test?.includes("karma")||r.test?.includes("ng test")?o.testingFramework="karma":(i.vitest||r.test?.includes("vitest"))&&(o.testingFramework="vitest"),o.testingFramework!=="none"&&(r.test?o.suggestedTestCommand="npm run test":o.testingFramework==="karma"?o.suggestedTestCommand="npx karma start":o.testingFramework==="jest"?o.suggestedTestCommand="npx jest --coverage":o.testingFramework==="vitest"&&(o.suggestedTestCommand="npx vitest run --coverage")),o}static findUntestedFiles(e,s=[]){let o=[];if(!E.existsSync(e))return o;let n=E.readdirSync(e),i=k.get(),r=i?.skipDirs??[],g=i?.skipFiles??[],d=i?.skipPaths??[],m=["node_modules","dist","build",".git",".angular","coverage","public","assets",...r],c=["main.ts","index.ts","index.js","index.jsx","app.config.ts","app.routes.ts","styles.css","styles.scss","tailwind.config.js","tailwind.config.ts","eslint.config.js","eslint.config.ts","jest.config.js","jest.config.ts","jest.config.mjs","jest.setup.js","jest.setup.ts","webpack.config.js","webpack.config.ts","babel.config.js","babel.config.ts","rollup.config.js","rollup.config.ts","vite.config.js","vite.config.ts","vitest.config.js","vitest.config.ts","karma.conf.js","karma.config.js","prettier.config.js","prettier.config.ts","postcss.config.js","postcss.config.ts","gulpfile.js","Gulpfile.js",".eslintrc.js",".eslintrc.ts",".babelrc.js"],f=[/^\./,/\.config\.(js|ts|mjs|cjs)$/,/\.conf\.(js|ts|mjs|cjs)$/];for(let l of n){let v=A.join(e,l);if(m.includes(l))continue;let a;try{a=E.statSync(v)}catch{continue}if(a.isDirectory())o.push(...this.findUntestedFiles(v,s));else if(l.match(/\.(ts|tsx|js|jsx|vue)$/)&&!l.includes(".spec.")&&!l.includes(".test.")){if(c.includes(l)||g.includes(l)||f.some(h=>h.test(l)))continue;let u=A.extname(l),p=l.slice(0,-u.length);if(![A.join(e,`${p}.spec${u}`),A.join(e,`${p}.test${u}`),A.join(e,`${p}.spec.js`),A.join(e,`${p}.test.js`),A.join(e,`${p}.spec.ts`),A.join(e,`${p}.test.ts`)].some(h=>E.existsSync(h))){let h=A.relative(process.cwd(),v);if(d.some(j=>h.startsWith(j)))continue;s.includes(h)||o.push(h)}}}return o}};var Ce=require("child_process"),R=class{static async installMissingFramework(e,s=process.cwd()){if(e.testingFramework!=="none")return e;console.log(`
|
|
61
|
+
[Lisa.ai Auto-Installer] \u{1F6A8} No testing framework detected for ${e.type} architecture.`),console.log("[Lisa.ai Auto-Installer] \u{1FA84} Initiating Zero-Config Installation Protocol...");let o="",n="none";switch(e.type){case"angular":console.log("[Lisa.ai Auto-Installer] Provisioning Angular TestBed environment..."),o="npm install --save-dev karma karma-chrome-launcher karma-coverage karma-jasmine jasmine-core @types/jasmine",n="karma",e.suggestedTestCommand="npm run test";break;case"react":case"node":default:console.log("[Lisa.ai Auto-Installer] Provisioning Universal Jest environment..."),o="npm install --save-dev jest @types/jest ts-jest",n="jest",e.suggestedTestCommand="npx jest --coverage";break;case"vue":console.log("[Lisa.ai Auto-Installer] Provisioning Vue Vitest environment..."),o="npm install --save-dev vitest @vue/test-utils jsdom",n="vitest",e.suggestedTestCommand="npx vitest run --coverage";break}if(o){console.log(`[Lisa.ai Executing] ${o}`);let i=o.split(" "),r=process.platform==="win32"?`${i[0]}.cmd`:i[0];return(0,Ce.spawnSync)(r,i.slice(1),{cwd:s,stdio:"inherit"}).status!==0&&(console.error(`
|
|
62
|
+
\u274C [Lisa.ai Auto-Installer] Failed to construct dynamic testing environment.`),process.exit(1)),console.log("\u2705 [Lisa.ai Auto-Installer] Framework successfully injected."),e.testingFramework=n,e}return e}};var Z=L(require("fs")),se=L(require("path"));var D=class{static async provisionConfigurationFiles(e,s,o,n=process.cwd()){if(e.testingFramework==="none")return;if(console.log(`
|
|
63
|
+
[Lisa.ai Auto-Generator] \u{1FA84} Analyzing ${e.type} architecture to generate ${e.testingFramework} configuration specs...`),["jest.config.js","jest.config.ts","karma.conf.js","vitest.config.ts","vitest.config.js"].some(d=>Z.existsSync(se.join(n,d)))){console.log("[Lisa.ai Auto-Generator] Setup file detected. Bypassing initialization.");return}let g=`You are an expert ${e.type} architect.
|
|
64
|
+
Write a production-ready '${e.testingFramework}' configuration file for a standard '${e.type}' application.
|
|
65
|
+
The application resides in the root directory.
|
|
66
|
+
|
|
67
|
+
Requirements:
|
|
68
|
+
1. Ensure it specifically instruments code coverage.
|
|
69
|
+
2. Ensure standard transpilation (ts-jest for jest, or standard karma-webpack/karma-typescript implementations).
|
|
70
|
+
3. Do NOT wrap your response in markdown formatting (no \`\`\`javascript).
|
|
71
|
+
4. Return ONLY the raw code string block.`;try{let d=await z(g,s,o),m="";e.testingFramework==="jest"&&(m="jest.config.js"),e.testingFramework==="karma"&&(m="karma.conf.js"),e.testingFramework==="vitest"&&(m="vitest.config.ts");let c=se.join(n,m);Z.writeFileSync(c,d,"utf-8"),console.log(`\u2705 [Lisa.ai Auto-Generator] Natively wrote ${m} to repository root.`)}catch(d){console.error(`
|
|
72
|
+
\u274C [Lisa.ai Auto-Generator] Failed to author configuration file: ${d.message}`),process.exit(1)}}};var K=3;function Ne(t){let e=t.replace(/\.(spec|test)\.(ts|js|jsx|tsx)$/,".$2");if(e!==t&&S.existsSync(e))try{return console.log(`[Lisa.ai Context Engine] \u{1F9E0} Located sibling logic structure at ${e}`),S.readFileSync(e,"utf-8")}catch{return}}function Ue(t,e){let s=t.toLowerCase(),o=U.parse(e);if(s.includes("ng test")||s.includes("karma"))return`${t} --include **/${o.base}`;if(s.includes("jest")||s.includes("vitest")||s.includes("playwright"))return`${t} ${e}`;if(s.includes("cypress"))return`${t} --spec ${e}`;if(s.includes("npm")||s.includes("yarn")||s.includes("pnpm")){try{let i=U.resolve(process.cwd(),"package.json");if(S.existsSync(i)){let r=JSON.parse(S.readFileSync(i,"utf8")),g="test";s.includes("npm run ")?g=s.split("npm run ")[1].split(" ")[0]:s.includes("yarn ")?g=s.split("yarn ")[1].split(" ")[0]:s.includes("pnpm ")&&(g=s.split("pnpm ")[1].split(" ")[0]);let d=r.scripts?.[g]?.toLowerCase()||"",m=s.includes("npm")?" --":"";if(d.includes("ng test")||d.includes("karma"))return`${t}${m} --include **/${o.base}`;if(d.includes("jest")||d.includes("vitest")||d.includes("playwright"))return`${t}${m} ${e}`}}catch{}let n=s.includes("npm")?" --":"";return`${t}${n} ${e}`}return t}async function M(t,e,s=1,o=null,n=3,i="local",r,g,d=[],m=0,c={}){if(!t){console.log(`
|
|
73
|
+
[Lisa.ai Auto-Discovery] No explicit --command provided. Initiating Autonomous Framework Fingerprinting...`);let l=T.scanRepository();l.testingFramework==="none"&&(l=await R.installMissingFramework(l),await D.provisionConfigurationFiles(l,e,g)),l.suggestedTestCommand?(t=l.suggestedTestCommand,console.log(`[Lisa.ai Auto-Discovery] Bootstrapping execution with natively discovered command: ${t}`)):(console.error(`
|
|
74
|
+
\u{1F6A8} [Lisa.ai Fatal Error] Agent could not dynamically extrapolate a testing command for a Generic Node Environment. Please pass --command explicitly.`),process.exit(1))}console.log(`
|
|
75
|
+
[Lisa.ai Executing] ${t} (Global Engine) Using Model: ${e}`);let f=(l,v=!1)=>new Promise((a,u)=>{let p=(0,$e.spawn)(l,{shell:!0,stdio:["ignore","pipe","pipe"]}),y="",x="";p.stdout?.on("data",h=>{let j=h.toString();y+=j}),p.stderr?.on("data",h=>{x+=h.toString()}),p.on("close",h=>{h===0?a({stdout:y,stderr:x}):u({message:`Process exited with code ${h}`,stdout:y,stderr:x})})});try{console.log("\u23F3 [Lisa.ai Testing] Booting Headless Test Suite in background..."),await f(t,!1),console.log(`
|
|
76
|
+
\u2705 [Lisa.ai Success] Global Command executed successfully.`),o&&await Le(o);return}catch(l){console.log(`
|
|
77
|
+
\u274C [Lisa.ai Failure] Global Command failed.`);let v=(l.stderr||"")+`
|
|
78
|
+
`+(l.stdout||"")+`
|
|
79
|
+
`+(l.message||"");console.log(`
|
|
80
|
+
[Lisa.ai Auto-Heal] Analyzing errors...`);let a=q(v,d,process.cwd());a||(console.error(`
|
|
81
|
+
\u{1F6A8} [Lisa.ai Error] Could not parse a valid failing file path from the logs (or all were explicitly skipped).`),process.exit(1));let u=U.resolve(process.cwd(),a);S.existsSync(u)||(console.error(`
|
|
82
|
+
\u{1F6A8} [Lisa.ai Error] Extracted file path does not exist: ${u}`),process.exit(1));let p=c[a]||0;if(p>=K){console.warn(`
|
|
83
|
+
[Lisa.ai Auto-Heal] \u26A0\uFE0F ${a} has failed globally ${p} time(s) \u2014 exceeds threshold of ${K}. Permanently skipping.`),d.push(a),await M(t,e,1,null,n,i,void 0,g,d,0,c);return}console.log(`[Lisa.ai Auto-Heal] Identified bleeding file: ${a} (global fail count: ${p}/${K})`);let y=1,x=!1,h=v,j,w="",P=Ue(t,a),Q=Ne(u);for(await I({projectId:i,type:"heal",filePath:a,modelUsed:e,status:"running",details:"Agent is currently analyzing and applying patches..."});y<=n&&!x;){console.log(`
|
|
84
|
+
[Lisa.ai Micro-Heal] Isolating ${a} (Attempt ${y}/${n})`);let _=S.readFileSync(u,"utf-8"),B=await me(a,_,h,e,g,j,Q);S.writeFileSync(u,B,"utf-8"),console.log(`[Lisa.ai Micro-Heal] Applied isolated patch to ${a}`),w=`### Auto-Heal Analysis Triggered
|
|
85
|
+
**Caught Error:**
|
|
86
|
+
\`\`\`bash
|
|
87
|
+
${h}
|
|
88
|
+
\`\`\`
|
|
89
|
+
|
|
90
|
+
**Applied Fix (${e}):**
|
|
91
|
+
\`\`\`typescript
|
|
92
|
+
${B}
|
|
93
|
+
\`\`\``;try{console.log(`[Lisa.ai Micro-Heal] Verifying fix with isolated command: ${P}`),await f(P,!1),console.log(`\u2705 [Lisa.ai Micro-Heal] Isolated verification passed for ${a}!`),x=!0}catch(O){console.log("\u274C [Lisa.ai Micro-Heal] Isolated verification failed.");let Ie=O.stdout?O.stdout.toString():"";h=(O.stderr?O.stderr.toString():"")+`
|
|
94
|
+
`+Ie+`
|
|
95
|
+
`+(O.message||""),j=`### Attempt ${y} Failed
|
|
96
|
+
\`\`\`typescript
|
|
97
|
+
${B}
|
|
98
|
+
\`\`\`
|
|
99
|
+
|
|
100
|
+
**New Error:**
|
|
101
|
+
${h}`,y++}}if(x)await I({projectId:i,type:"heal",filePath:a,modelUsed:e,status:"success",details:w});else if(console.warn(`
|
|
102
|
+
[Lisa.ai Auto-Heal] \u26A0\uFE0F Agent failed to locally heal ${a} after ${n} isolated attempts. Marking as Skipped to avoid infinite Loop.`),await I({projectId:i,type:"heal",filePath:a,modelUsed:e,status:"error",details:`Agent exhausted all ${n} attempts without success.
|
|
103
|
+
|
|
104
|
+
`+w}),c[a]=(c[a]||0)+1,console.log(`[Lisa.ai Auto-Heal] Global fail counter for ${a}: ${c[a]}/${K}`),d.push(a),a.match(/\.(spec|test)\.(ts|js|tsx|jsx|vue)$/)){let _=u+".broken";try{S.renameSync(u,_),console.log(`[Lisa.ai Auto-Heal] \u{1F6A8} Renamed unhealable spec to ${a}.broken to force the test runner to bypass it!`)}catch{}}await M(t,e,1,x?a:null,n,i,void 0,g,d,0,c)}}var be=require("child_process"),F=L(require("fs")),$=L(require("path"));var J=L(require("fs")),ke=L(require("path"));function Ae(t){let e=ke.resolve(process.cwd(),t);if(!J.existsSync(e))throw new Error(`[Lisa.ai Coverage Error] Coverage file not found at ${e}`);let s=J.readFileSync(e,"utf-8"),o=JSON.parse(s),n=[];for(let[i,r]of Object.entries(o))i!=="total"&&(r.lines.pct<100||r.statements.pct<100||r.functions.pct<100||r.branches.pct<100)&&n.push(i);return n}var je=new Set,X=0,oe=3;async function H(t,e,s=1,o=3,n="local",i){let r=k.load(process.cwd());r&&(k.applyEnvDefaults(r),!t&&r.testCommand&&(t=r.testCommand,console.log(`[Lisa.ai Config] Using testCommand from .lisai.json: ${t}`)));let g=T.scanRepository(),d=g.testingFramework!=="none"?g.testingFramework:void 0;if(!t){console.log(`
|
|
105
|
+
[Lisa.ai Auto-Discovery] No explicit --command provided. Initiating Autonomous Framework Fingerprinting...`);let c=g;if(c.testingFramework==="none"&&(c=await R.installMissingFramework(c),await D.provisionConfigurationFiles(c,e,i)),c.suggestedTestCommand){t=c.suggestedTestCommand;let f=c.testingFramework;(f==="jest"||f==="vitest")&&!t.includes("--coverage")&&(t=t.includes("npm run")?`${t} -- --coverage`:`${t} --coverage`,console.log(`[Lisa.ai Coverage] Coverage flag appended: ${t}`)),console.log(`[Lisa.ai Auto-Discovery] Bootstrapping execution with natively discovered command: ${t}`)}else console.error(`
|
|
106
|
+
\u{1F6A8} [Lisa.ai Fatal Error] Agent could not dynamically extrapolate a testing command for a Generic Node Environment. Please pass --command explicitly.`),process.exit(1)}console.log(`
|
|
107
|
+
[Lisa.ai Coverage] ${t} (Attempt ${s}/${o}) Using Model: ${e}`),await I({projectId:n,type:"coverage",filePath:"global-test-suite",modelUsed:e,status:"running",details:"Agent is currently executing testing suite and validating coverage drops..."});let m=(c,f=!1)=>new Promise((l,v)=>{let a=(0,be.spawn)(c,{shell:!0,stdio:["ignore","pipe","pipe"]}),u="",p="";a.stdout?.on("data",y=>{let x=y.toString();if(u+=x,f){let h=x.split(`
|
|
108
|
+
`);for(let j of h){let w=j.match(/Executed\s+(\d+)\s+of\s+(\d+)/);if(w){let P=j.match(/(\d+)\s+FAILED/),Q=P?P[1]:0,_=`\r\u23F3 [Lisa.ai Testing] Executed ${w[1]} of ${w[2]} (${Q} FAILED) `;process.stdout.write(_)}}}}),a.stderr?.on("data",y=>{p+=y.toString()}),a.on("close",y=>{y===0?l():v({message:`Test process exited with code ${y}`,stdout:u,stderr:p})})});try{console.log("[Lisa.ai Coverage] Booting testing framework in the background. This may take a moment..."),await m(t,!0),console.log(`
|
|
109
|
+
\u2705 [Lisa.ai Coverage] Tests passed successfully on attempt ${s}.`)}catch(c){let f=(c.stderr||"")+`
|
|
110
|
+
`+(c.stdout||"")+`
|
|
111
|
+
`+(c.message||"");if(q(f,[],process.cwd())!==null){console.log(`
|
|
112
|
+
\u274C [Lisa.ai Coverage] Tests failed. Delegating to Auto-Heal...`),await M(t,e,1,null,o,n,void 0,i),console.log(`
|
|
113
|
+
\u{1F504} [Lisa.ai Coverage] Auto-Heal successful. Restarting coverage analysis...`),await H(t,e,s+1,o,n,i);return}console.log(`
|
|
114
|
+
[Lisa.ai Coverage] No failing spec file detected in error output. Tests may not exist yet \u2014 initiating Cold-Start Discovery...`)}try{let c=$.resolve(process.cwd(),"coverage/coverage-summary.json"),f=[];if(F.existsSync(c))console.log("[Lisa.ai Coverage] Evaluating summary..."),f=Ae("coverage/coverage-summary.json");else{if(console.log(`
|
|
115
|
+
[Lisa.ai Coverage] No coverage-summary.json found. Initiating Cold-Start Project Crawler...`),f=T.findUntestedFiles(process.cwd(),[...je]),f.length===0){console.log("\u2705 [Lisa.ai Coverage] Zero-Test scan complete. No untested source files discovered.");return}console.log(`[Lisa.ai Coverage] Discovered ${f.length} untested file(s). Seeding first test suite...`)}if(f.length===0){console.log("\u2705 [Lisa.ai Coverage] 100% Logic Coverage Verified! No un-covered files remaining.");return}console.log(`[Lisa.ai Coverage] Found ${f.length} file(s) below 100% threshold:`,f);let l=f[0],v=$.resolve(process.cwd(),l),a=$.parse(v),u=[$.join(a.dir,`${a.name}.spec${a.ext}`),$.join(a.dir,`${a.name}.test${a.ext}`),$.join(a.dir,`${a.name}.spec.js`),$.join(a.dir,`${a.name}.test.js`),$.join(a.dir,`${a.name}.spec.ts`),$.join(a.dir,`${a.name}.test.ts`)],p=null,y=$.join(a.dir,`${a.name}.spec${a.ext}`);for(let w of u)if(F.existsSync(w)){y=w,p=F.readFileSync(w,"utf-8");break}let x="",h=!1,j=F.readFileSync(v,"utf-8");try{p?(console.log(`[Lisa.ai Coverage] Existing test suite discovered for ${l}. Requesting single-pass logic coverage append...`),x=await he(l,j,$.relative(process.cwd(),y),p,e,i,d)):(console.log(`[Lisa.ai Coverage] Requesting newly generated test suite for ${l}...`),x=await fe(l,j,e,i,d)),X=0,h=!0}catch(w){X++,je.add(l);let P=w.cause?.message?`${w.message} (cause: ${w.cause.message})`:w.message;if(console.error(`
|
|
116
|
+
\u274C [Lisa.ai Coverage] LLM call failed for ${l} \u2014 consecutive failure #${X}/${oe}`),console.error(` Error: ${P}`),X>=oe)throw new Error(`Systematic LLM failure: ${oe} consecutive API calls failed.
|
|
117
|
+
This usually means your API key has insufficient credits, hit a rate limit, or the model is unavailable.
|
|
118
|
+
Last error: ${P}
|
|
119
|
+
Tip: Check your API key, account credits, and rate-limit quota for provider '${e}'.`)}if(!h){await H(t,e,s+1,o,n,i);return}F.writeFileSync(y,x,"utf-8"),console.log(`[Lisa.ai Coverage] Wrote Spec File to ${y}`),await I({projectId:n,type:"coverage",filePath:l,modelUsed:e,status:"success",details:`### Logic Coverage Action Detected
|
|
120
|
+
**Auto-Generated Specification Append (${e}):**
|
|
121
|
+
\`\`\`typescript
|
|
122
|
+
${x}
|
|
123
|
+
\`\`\``}),await H(t,e,s+1,o,n,i)}catch(c){console.error("[Lisa.ai Coverage loop failure]:",c.message),await I({projectId:n,type:"coverage",filePath:"coverage-loop",modelUsed:e,status:"error",details:`Coverage loop encountered an unrecoverable error: ${c.message}`})}}var Pe=Se();function ne(){console.log(`
|
|
124
|
+
======================================================`),console.log(`\u2728 Lisa.ai Agent v${Pe.version} Running... `),console.log(" Where pure magic happens! \u{1FA84}"),console.log(`======================================================
|
|
125
|
+
`)}var G=new Ee.Command;G.name("lisa-agent").description("Lisa.ai - Autonomous CI/CD Platform Worker Agent").version(Pe.version);G.command("heal").description("Run a command and autonomously heal errors").option("-c, --command <type>","Command to execute (Optional for Auto-Discovery)").option("-m, --model <provider>","LLM provider to use (gemini, claude, openai)","gemini").option("-p, --project-id <id>","Control Plane Project ID to fetch dynamic config").action(async t=>{ne();let e=5,s=t.model,o;if(t.projectId){let i=await Y(t.projectId);if(!i.ok)i.reason==="not_found"?(console.error(`
|
|
126
|
+
\u{1F6A8} [Lisa.ai Agent Error] Project '${t.projectId}' does not exist on the Control Plane. Please create it in the Dashboard first or run locally without a project ID.`),process.exit(1)):console.warn(`
|
|
127
|
+
\u26A0\uFE0F [Lisa.ai Warning] Control Plane is unreachable. Continuing with local CLI defaults (model=${s}, maxRetries=${e}).`);else{let r=i.config;console.log(`[Lisa.ai Agent] Dynamic Config Loaded: Provider=${r.modelProvider}, MaxRetries=${r.maxRetries}`),r.maxRetries<5&&console.warn(`
|
|
128
|
+
\u26A0\uFE0F [Lisa.ai Warning] Your Dashboard Analytics Config has maxRetries set to ${r.maxRetries}. Consider increasing this to 5+ in the WEB UI for complex Angular Healing!`),s=r.modelProvider,e=r.maxRetries,o=r.apiKey,r.autoHealEnabled===!1&&(console.log("[Lisa.ai Agent] Auto-heal is disabled by Control Plane."),process.exit(1))}}let n=k.load(process.cwd());n&&(k.applyEnvDefaults(n),!t.projectId&&n.maxRetries!==void 0&&(e=n.maxRetries),!t.projectId&&n.provider&&t.model==="gemini"&&(s=n.provider),k.printSummary(n)),await M(t.command,s,1,null,e,t.projectId||"local",void 0,o)});G.command("coverage").description("Run test command dynamically generating missing specs for 100% coverage").option("-c, --command <type>","Test command to execute (Optional for Auto-Discovery)").option("-m, --model <provider>","LLM provider to use (gemini, claude, openai)","gemini").option("-p, --project-id <id>","Control Plane Project ID to fetch dynamic config").action(async t=>{ne();let e=5,s=t.model,o;if(t.projectId){let i=await Y(t.projectId);if(!i.ok)i.reason==="not_found"?(console.error(`
|
|
129
|
+
\u{1F6A8} [Lisa.ai Agent Error] Project '${t.projectId}' does not exist on the Control Plane. Please create it in the Dashboard first or run locally without a project ID.`),process.exit(1)):console.warn(`
|
|
130
|
+
\u26A0\uFE0F [Lisa.ai Warning] Control Plane is unreachable. Continuing with local CLI defaults (model=${s}, maxRetries=${e}).`);else{let r=i.config;console.log(`[Lisa.ai Agent] Dynamic Config Loaded: Provider=${r.modelProvider}, MaxRetries=${r.maxRetries}`),r.maxRetries<5&&console.warn(`
|
|
131
|
+
\u26A0\uFE0F [Lisa.ai Warning] Your Dashboard Analytics Config has maxRetries set to ${r.maxRetries}. Consider increasing this to 5+ in the WEB UI for complex Angular Healing!`),s=r.modelProvider,e=r.maxRetries,o=r.apiKey,r.autoHealEnabled===!1&&(console.log("[Lisa.ai Agent] Auto-heal is disabled by Control Plane."),process.exit(1))}}let n=k.load(process.cwd());n&&(k.applyEnvDefaults(n),!t.projectId&&n.maxRetries!==void 0&&(e=n.maxRetries),!t.projectId&&n.provider&&t.model==="gemini"&&(s=n.provider),k.printSummary(n)),await H(t.command,s,1,e,t.projectId||"local",o)});G.command("diagnose").description("Test LLM connectivity and API key health without running the full agent").option("-m, --model <provider>","LLM provider to test (gemini, claude, openai)","claude").option("-p, --project-id <id>","Control Plane Project ID to fetch API key from").action(async t=>{ne();let e=t.model,s;if(t.projectId){let i=await Y(t.projectId);i.ok?(e=i.config.modelProvider,s=i.config.apiKey,console.log(`[Lisa.ai Diagnose] Config fetched from Control Plane. Provider=${e}`)):console.warn(`[Lisa.ai Diagnose] Control Plane ${i.reason}. Using local env for API key.`)}let n={claude:process.env.LISA_CLAUDE_MODEL||"claude-haiku-4-5",openai:process.env.LISA_OPENAI_MODEL||"gpt-4o-mini",gemini:process.env.LISA_GOOGLE_MODEL||"gemini-2.0-flash"}[e]||e;console.log(`
|
|
132
|
+
[Lisa.ai Diagnose] Testing ${e} \u2192 model ID: ${n}`);try{let i=await z("Reply with exactly: LISA_OK",e,s);console.log(`
|
|
133
|
+
\u2705 [Lisa.ai Diagnose] ${e} is working correctly.`),console.log(` Model response: "${i}"`)}catch(i){console.error(`
|
|
134
|
+
\u274C [Lisa.ai Diagnose] ${e} call FAILED.`),console.error(` Error: ${i.message}`),i.cause&&console.error(` Cause: ${i.cause?.message??i.cause}`),console.error(`
|
|
135
|
+
Common causes:`),console.error(" \u2022 API key is invalid, expired, or has no credits"),console.error(" \u2022 Model ID is deprecated (current: claude-sonnet-4-6, gemini-2.0-flash, gpt-4o-mini)"),console.error(" \u2022 Rate limit exceeded \u2014 wait a minute and retry"),console.error(" \u2022 Network/proxy issue blocking api.anthropic.com"),process.exit(1)}});G.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lisa.ai/agent",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Lisa.ai Autonomous CI/CD Worker Agent",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
"dist/"
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
|
-
"build": "
|
|
13
|
+
"build": "tsup",
|
|
14
|
+
"typecheck": "tsc --noEmit",
|
|
14
15
|
"start": "node dist/index.js",
|
|
15
16
|
"dev": "ts-node src/index.ts",
|
|
16
|
-
"prepublishOnly": "npm run build"
|
|
17
|
+
"prepublishOnly": "npm run typecheck && npm run build"
|
|
17
18
|
},
|
|
18
19
|
"keywords": [
|
|
19
20
|
"lisa",
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
"@types/jest": "^30.0.0",
|
|
38
39
|
"@types/node": "^20.0.0",
|
|
39
40
|
"ts-node": "^10.9.1",
|
|
41
|
+
"tsup": "^8.5.1",
|
|
40
42
|
"typescript": "^5.0.0"
|
|
41
43
|
}
|
|
42
|
-
}
|
|
44
|
+
}
|
|
@@ -1,247 +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
|
-
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);
|
|
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
|
-
});
|
|
246
|
-
}
|
|
247
|
-
}
|