@lisa.ai/agent 2.11.1 → 2.11.3
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/README.md +6 -2
- package/dist/index.js +120 -109
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -161,6 +161,7 @@ lisa-agent chat -m claude
|
|
|
161
161
|
|
|
162
162
|
| Command | Description |
|
|
163
163
|
|---|---|
|
|
164
|
+
| `/read <file>` | Load a file into conversation context |
|
|
164
165
|
| `/test [cmd]` | Run tests and show results |
|
|
165
166
|
| `/explain <file>` | Analyze why a test is failing (no file changes) |
|
|
166
167
|
| `/heal <file>` | Auto-heal a specific failing test file |
|
|
@@ -171,8 +172,11 @@ lisa-agent chat -m claude
|
|
|
171
172
|
|
|
172
173
|
**Example session:**
|
|
173
174
|
```
|
|
174
|
-
lisa>
|
|
175
|
-
|
|
175
|
+
lisa> /read src/services/auth.service.ts
|
|
176
|
+
✓ Loaded auth.service.ts (142 lines) into context
|
|
177
|
+
|
|
178
|
+
lisa> write a unit test for this file
|
|
179
|
+
[streams a test based on the actual file content]
|
|
176
180
|
|
|
177
181
|
lisa> /test
|
|
178
182
|
Running: npx ng test --no-watch --browsers=ChromeHeadless...
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var
|
|
2
|
+
"use strict";var Vt=Object.create;var tt=Object.defineProperty;var zt=Object.getOwnPropertyDescriptor;var Zt=Object.getOwnPropertyNames;var Jt=Object.getPrototypeOf,Qt=Object.prototype.hasOwnProperty;var Xt=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var es=(e,t,s,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Zt(t))!Qt.call(e,o)&&o!==s&&tt(e,o,{get:()=>t[o],enumerable:!(n=zt(t,o))||n.enumerable});return e};var F=(e,t,s)=>(s=e!=null?Vt(Jt(e)):{},es(t||!e||!e.__esModule?tt(s,"default",{value:e,enumerable:!0}):s,e));var Bt=Xt((Kn,xs)=>{xs.exports={name:"@lisa.ai/agent",version:"2.11.3",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 qt=require("commander"),re=F(require("fs")),be=F(require("path")),Yt=F(require("readline"));var st=F(require("dotenv"));st.config({quiet:!0});async function je(e){let t=process.env.LISA_CONTROL_PLANE_URL||"http://localhost:3088";try{let s=`${t}/api/v1/lisa/config/${e}`;console.log(`[Lisa.ai Agent] Fetching dynamic configuration from ${s}...`);let n=await fetch(s);if(!n.ok){let i=n.status===404?"not_found":"unreachable";return console.warn(i==="not_found"?`[Lisa.ai Agent Warning] Control Plane returned 404. Project '${e}' not found.`:`[Lisa.ai Agent Warning] Control Plane returned ${n.status}. Falling back to local defaults.`),{ok:!1,reason:i}}return{ok:!0,config:await n.json()}}catch{return console.warn(`[Lisa.ai Agent Warning] Failed to reach Control Plane (${t}). Using local fallback configuration.`),{ok:!1,reason:"unreachable"}}}var $t=require("child_process"),q=F(require("fs")),se=F(require("path"));var E=F(require("path")),D=F(require("fs"));function Ee(e){return e.replace(/\x1B\[[0-9;]*[a-zA-Z]/g,"")}function B(e){let t=Ee(e),s=[...t.matchAll(/Executed\s+(\d+)\s+of\s+(\d+)(?:\s+\((\d+)\s+FAILED\))?/g)];if(s.length>0){let u=[...s].reverse().find(L=>L[3]!==void 0)??s[s.length-1],p=parseInt(u[2]),$=u[3]?parseInt(u[3]):0,g=parseInt(u[1])-$;return{total:p,passed:g,failed:$}}let n=t.match(/Tests:\s+(?:(\d+)\s+failed,?\s*)?(?:(\d+)\s+passed,?\s*)?(\d+)\s+total/);if(n){let u=parseInt(n[3]),p=n[1]?parseInt(n[1]):0,$=n[2]?parseInt(n[2]):0;return{total:u,passed:$,failed:p}}let o=t.match(/(\d+)\s+failed/),i=t.match(/(\d+)\s+passed/);if(i||o){let u=o?parseInt(o[1]):0,p=i?parseInt(i[1]):0,$=t.match(/(\d+)\s+skipped/),g=$?parseInt($[1]):0;return{total:p+u+g,passed:p,failed:u}}let a=t.match(/Tests\s+(?:(\d+)\s+failed\s*\|?\s*)?(\d+)\s+passed\s*\((\d+)\)/);if(a){let u=parseInt(a[3]),p=a[1]?parseInt(a[1]):0,$=parseInt(a[2]);return{total:u,passed:$,failed:p}}let r=t.match(/(\d+)\s+passing/),c=t.match(/(\d+)\s+failing/);if(r){let u=parseInt(r[1]),p=c?parseInt(c[1]):0;return{total:u+p,passed:u,failed:p}}return null}function Ge(e,t=process.cwd(),s=[]){let n=e.replace(/\x1B\[[0-9;]*[a-zA-Z]/g,""),o=s.map(d=>E.resolve(t,d)),i=new Map,a=new Set,r=/(?:Chrome|Firefox|Safari)(?:\s+Headless)?\s+[\d.]+(?:\s+[\d.]+)*\s+\([^)]+\)\s+(.+?)\s+FAILED/g,c;for(;(c=r.exec(n))!==null;){let l=c[1].trim().split(" ");for(let y=1;y<Math.min(l.length,6);y++){let h=l.slice(0,y).join(" ");if(a.has(h))continue;a.add(h);let C=Be(h,t,o);if(C){let v=E.resolve(C);i.has(v)||i.set(v,E.relative(t,C));break}}}let u=/^FAIL\s+([a-zA-Z0-9_./-\\]+\.(?:spec|test)\.(ts|tsx|js|jsx))/gm,p;for(;(p=u.exec(n))!==null;){let d=p[1],l=E.isAbsolute(d)?d:E.resolve(t,d);!o.includes(l)&&D.existsSync(l)&&!i.has(l)&&i.set(l,E.relative(t,l))}let $=/\[(?:chromium|firefox|webkit)\]\s+[>›]\s+([^\s:]+\.(?:spec|test)\.[tj]sx?):\d+:\d+\s+[>›]/g,g;for(;(g=$.exec(n))!==null;){let d=g[1],l=E.isAbsolute(d)?d:E.resolve(t,d);!o.includes(l)&&D.existsSync(l)&&!i.has(l)&&i.set(l,E.relative(t,l))}let L=/Running:\s+([^\s]+\.(?:cy|spec|test)\.[tj]sx?)/g,m;for(;(m=L.exec(n))!==null;){let d=m[1],l=E.isAbsolute(d)?d:E.resolve(t,d);if(!o.includes(l)&&D.existsSync(l)&&!i.has(l)){let y=d.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");new RegExp(y+"\\s+\\d+\\s+\\d+\\s+([1-9]\\d*)").test(n)&&i.set(l,E.relative(t,l))}}return[...i.values()]}function Ae(e,t=[],s=process.cwd()){let n=e.replace(/\x1B\[[0-9;]*[a-zA-Z]/g,""),o=t.map(d=>E.resolve(d));{let d=/(?:Chrome|Firefox|Safari)(?:\s+Headless)?\s+[\d.]+(?:\s+[\d.]+)*\s+\([^)]+\)\s+(.+?)\s+FAILED/g,l=new Set,y;for(;(y=d.exec(n))!==null;){let C=y[1].trim().split(" ");for(let v=1;v<Math.min(C.length,5);v++){let x=C.slice(0,v).join(" ");if(l.has(x))continue;l.add(x);let f=Be(x,s,o);if(f)return E.relative(s,f)}}}let i=/([a-zA-Z]:[a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue)|[a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue))(?:\s*[:(])/g,a;for(;(a=i.exec(n))!==null;){let d=a[1];if(d){if(/(?:^|[/\\])(node_modules|dist|build)[/\\]/.test(d))continue;let l=E.isAbsolute(d)?d:E.resolve(s,d);if(!o.includes(l)&&D.existsSync(l))return d}}let r=/FAIL\s+([a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue))/gi,c;for(;(c=r.exec(n))!==null;){let d=c[1],l=E.isAbsolute(d)?d:E.resolve(s,d);if(!o.includes(l)&&D.existsSync(l))return d}{let d=/\[(?:chromium|firefox|webkit)\]\s+[>›]\s+([^\s:]+\.(?:spec|test)\.[tj]sx?):\d+:\d+\s+[>›]/g,l;for(;(l=d.exec(n))!==null;){let y=l[1],h=E.isAbsolute(y)?y:E.resolve(s,y);if(!o.includes(h)&&D.existsSync(h))return y}}let u=/([a-zA-Z]:[a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue)|[a-zA-Z0-9_.\-\/\\]+\.(?:ts|tsx|js|jsx|vue))/g,p;for(;(p=u.exec(n))!==null;){let d=p[1];if(d){if(/(?:^|[/\\])(node_modules|dist|build)[/\\]/.test(d))continue;let l=E.isAbsolute(d)?d:E.resolve(s,d);if(!o.includes(l)&&D.existsSync(l))return d}}let $=/\b([A-Z][a-zA-Z0-9]{3,})\b/g,g,L=new Set,m=["Error","TypeError","SyntaxError","ReferenceError","RangeError","Object","Boolean","String","Number","Array","NullInjectorError","NullInjector","R3Injector","ChainedInjector","Module","Component","Standalone","Application","App2","TestBed","UserContext","Chrome","ChromeHeadless","Karma","Launching","Starting","Headless","Connected","Executed","INFO","SUCCESS","FAILED","Building","Initial","Names","Lazy","Source","Find","NotFound","NG0201","Windows","Linux","Macintosh","Users","AppData","Local","Temp","Process","Unexpected","Expected","Validation","Directory","Configuration","Documentation"];for(;(g=$.exec(n))!==null;){let d=g[1];if(!d||m.includes(d)||L.has(d)||/^[A-Z][A-Z0-9]{5,11}$/.test(d)||/^[A-Za-z0-9]{16,}$/.test(d))continue;L.add(d);let l=nt(d,s,o);if(l)return E.relative(s,l)}return null}function Be(e,t,s){if(!D.existsSync(t))return null;let n=D.readdirSync(t);for(let o of n){let i=E.join(t,o);if(["node_modules","dist","build",".git",".angular"].includes(o))continue;let a;try{a=D.statSync(i)}catch{continue}if(a.isDirectory()){let r=Be(e,i,s);if(r)return r}else if(/\.(spec|test)\.(ts|tsx|js|jsx)$/.test(o)){if(s.includes(E.resolve(i)))continue;try{let r=D.readFileSync(i,"utf8");if(r.includes(`describe('${e}'`)||r.includes(`describe("${e}"`))return i}catch{continue}}}return null}function nt(e,t,s){if(!D.existsSync(t))return null;let n=D.readdirSync(t);for(let o of n){let i=E.join(t,o);if(o==="node_modules"||o==="dist"||o==="build"||o===".git"||o===".angular")continue;let a;try{a=D.statSync(i)}catch{continue}if(a.isDirectory()){let r=nt(e,i,s);if(r)return r}else if(o.match(/\.(ts|tsx|js|jsx|vue)$/)){let r=D.readFileSync(i,"utf8");if(r.includes(`class ${e}`)||r.includes(`function ${e}`)||r.includes(`const ${e}`)||r.includes(`let ${e}`)||r.includes(`exports.${e}`)||r.includes(`module.exports.${e}`)){let c=i,u=E.extname(i),p=i.slice(0,-u.length);if(!o.includes(".spec.")&&!o.includes(".test.")){let $=[`${p}.spec${u}`,`${p}.test${u}`,`${p}.spec.js`,`${p}.test.js`];for(let g of $)if(D.existsSync(g)){c=g;break}}if(!s.includes(E.resolve(c)))return c}}}return null}var ce=require("ai"),Te=require("@ai-sdk/openai"),ot=require("@ai-sdk/anthropic"),it=require("@ai-sdk/google"),at=F(require("dotenv"));at.config({quiet:!0});var qe=15e3;function Pe(e,t){return e.length<=qe?e:(console.warn(`[Lisa.ai LLM] ${t} is ${e.length} chars \u2014 truncating to ${qe} to stay within context window.`),e.slice(0,qe)+`
|
|
3
3
|
|
|
4
4
|
// ... (truncated)`)}function Ie(e,t){if(!/\b(describe|it\(|test\(|expect\(|beforeEach|afterEach|suite|assert\.|cy\.)\b/.test(e))throw new Error(`LLM returned a non-code response for ${t} (possible context overflow). Skipping to prevent file corruption.`)}function rt(e){if(!e||e.length===0)return"";let t={unit:"unit tests \u2014 test every function/method in complete isolation, mocking ALL external dependencies, services, and I/O",integration:"integration tests \u2014 test how modules work together using real service/repository layers; for HTTP routes use supertest or equivalent",e2e:"end-to-end tests \u2014 simulate complete user flows using the configured e2e framework (Playwright/Cypress); cover the full path from HTTP request to response or UI action to assertion"};return`5. Test scope \u2014 generate ALL of the following test types for this file:
|
|
5
5
|
${e.map(n=>` \u2022 ${t[n]??n}`).join(`
|
|
@@ -45,7 +45,7 @@ You previously attempted to fix this file but the compiler REJECTED your fix!
|
|
|
45
45
|
Here is the previous analysis and failed fix you attempted:
|
|
46
46
|
`+i+`
|
|
47
47
|
|
|
48
|
-
DO NOT repeat the identical code changes. Try a completely different programming approach, fix syntax typos, or check for missing imports.`);let{text:p}=await(0,ce.generateText)({model:c,prompt:u}),$=p.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);return $?$[1].trim():p.trim()}async function
|
|
48
|
+
DO NOT repeat the identical code changes. Try a completely different programming approach, fix syntax typos, or check for missing imports.`);let{text:p}=await(0,ce.generateText)({model:c,prompt:u}),$=p.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);return $?$[1].trim():p.trim()}async function ee(e,t,s,n,o,i){console.log(`[Lisa.ai Coverage] Requesting test generation from ${s} for ${e}...`);let a=pe(s,n),r=o?`3. You MUST use the '${o}' testing framework exclusively. All imports, describe/it/test blocks, and mock utilities must follow '${o}' conventions.
|
|
49
49
|
`:`3. Include all necessary imports assuming a standard testing framework (Jest/Karma/Vitest) is available.
|
|
50
50
|
`,c=rt(i),u=`You are Lisa.ai, an autonomous CI/CD expert platform.
|
|
51
51
|
A source file lacks 100% test coverage. Your task is to generate a comprehensive testing suite covering all branches, lines, and functions.
|
|
@@ -84,7 +84,7 @@ Generate a comprehensive test suite for the file below. Use the PROVEN PATTERNS
|
|
|
84
84
|
--- PROVEN PATTERNS ---
|
|
85
85
|
`+s.map((L,m)=>`[Pattern ${m+1}]
|
|
86
86
|
${L}`).join(`
|
|
87
|
-
`)+"\n\n--- Constraints ---\n1. Return ONLY the test code in ```typescript ... ```.\n2. Aim for 100% logic coverage.\n"+c,{text:p}=await(0,ce.generateText)({model:r(n.model),prompt:u,abortSignal:i.signal});clearTimeout(a);let $=p.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/),g=$?$[1].trim():p.trim();return Ie(g,e),g}catch(i){return console.log(` [Lisa LLM] Generation failed: ${i.message?.slice(0,120)??String(i)}`),null}}async function ge(e,t,s){let n=pe(t,s),{text:o}=await(0,ce.generateText)({model:n,prompt:e}),i=o.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);return i?i[1].trim():o.trim()}var pt=
|
|
87
|
+
`)+"\n\n--- Constraints ---\n1. Return ONLY the test code in ```typescript ... ```.\n2. Aim for 100% logic coverage.\n"+c,{text:p}=await(0,ce.generateText)({model:r(n.model),prompt:u,abortSignal:i.signal});clearTimeout(a);let $=p.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/),g=$?$[1].trim():p.trim();return Ie(g,e),g}catch(i){return console.log(` [Lisa LLM] Generation failed: ${i.message?.slice(0,120)??String(i)}`),null}}async function ge(e,t,s){let n=pe(t,s),{text:o}=await(0,ce.generateText)({model:n,prompt:e}),i=o.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);return i?i[1].trim():o.trim()}var pt=F(require("fs")),gt=F(require("simple-git")),ft=require("@octokit/rest"),As=require("dotenv/config");function ts(e){let t=e.healedFiles.map(o=>`- \`${o}\``).join(`
|
|
88
88
|
`),s=e.quarantinedFiles.length>0?e.quarantinedFiles.map(o=>`- \`${o}\``).join(`
|
|
89
89
|
`):"_None \u2014 all failures healed successfully._",n=e.testCounts?["| Metric | Before | After (est.) |","|---|---|---|",`| Passing | ${e.testCounts.passed} | ~${e.testCounts.passed+e.healedFiles.length} |`,`| Failing | ${e.testCounts.failed} | ~${Math.max(0,e.testCounts.failed-e.healedFiles.length)} |`,`| Total | ${e.testCounts.total} | ${e.testCounts.total} |`].join(`
|
|
90
90
|
`):"_Test counts not available._";return`## Lisa.ai Auto-Heal Report
|
|
@@ -106,10 +106,10 @@ ${n}
|
|
|
106
106
|
> This PR was generated automatically by [Lisa.ai](https://www.npmjs.com/package/@lisa.ai/agent).
|
|
107
107
|
> Please review the changes before merging.`}async function mt(e){if(e.healedFiles.length===0)return console.log("[Lisa.ai CI] No files were healed. Skipping PR creation."),null;let t=process.env.GITHUB_TOKEN,s=process.env.GITHUB_REPOSITORY;if(!t||!s)return console.log("[Lisa.ai CI] GITHUB_TOKEN or GITHUB_REPOSITORY not set. Skipping PR creation."),null;let n=(0,gt.default)(),i=`lisa-fix/auto-heal-${Date.now()}`,a=process.env.GITHUB_REF_NAME||"main";try{await n.addConfig("user.name","Lisa.ai"),await n.addConfig("user.email","lisa@lisa.ai"),await n.checkoutLocalBranch(i),await n.add(e.healedFiles);let r=e.healedFiles.length===1?`fix: Lisa.ai auto-healed ${e.healedFiles[0]}`:`fix: Lisa.ai auto-healed ${e.healedFiles.length} failing test specs`;await n.commit(r),console.log(`[Lisa.ai CI] Committed ${e.healedFiles.length} file(s) to branch ${i}`),await n.push("origin",i,{"--set-upstream":null}),console.log("[Lisa.ai CI] Pushed branch to origin.");let[c,u]=s.split("/"),$=await new ft.Octokit({auth:t}).rest.pulls.create({owner:c,repo:u,title:r,body:ts(e),head:i,base:a});return console.log(`[Lisa.ai CI] Pull Request created: ${$.data.html_url}`),$.data.html_url}catch(r){return console.error(`[Lisa.ai CI] Failed to create PR: ${r.message}`),null}}function Ye(e,t){let s=process.env.GITHUB_STEP_SUMMARY;if(!s)return;let n=["## Lisa.ai Heal Results","","| Metric | Value |","|---|---|",`| Files Healed | ${e.healedFiles.length} |`,`| Files Quarantined | ${e.quarantinedFiles.length} |`,`| Model | ${e.modelProvider} |`,`| Framework | ${e.framework} |`,`| Scope | ${e.scope} |`];e.testCounts&&n.push(`| Tests Passing (before) | ${e.testCounts.passed} |`,`| Tests Failing (before) | ${e.testCounts.failed} |`),t&&n.push("",`**Pull Request:** ${t}`),e.healedFiles.length>0&&(n.push("","### Healed Files"),e.healedFiles.forEach(o=>n.push(`- \`${o}\``))),e.quarantinedFiles.length>0&&(n.push("","### Quarantined Files"),e.quarantinedFiles.forEach(o=>n.push(`- \`${o}\``)));try{pt.appendFileSync(s,n.join(`
|
|
108
108
|
`)+`
|
|
109
|
-
`)}catch{}}var ht=
|
|
109
|
+
`)}catch{}}var ht=F(require("dotenv"));ht.config({quiet:!0});async function N(e){let s=`${process.env.LISA_CONTROL_PLANE_URL||"http://localhost:3088"}/api/v1/lisa/telemetry`;fetch(s,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}).catch(n=>{console.debug(`[Lisa.ai Agent Debug] Failed to report telemetry: ${n.message}`)})}var Q=F(require("fs")),Y=F(require("path"));var Re=F(require("fs")),yt=F(require("path")),de=".lisai.json",$e={enabled:!1,provider:"ollama",model:"qwen2.5-coder:7b",baseUrl:"http://localhost:11434",confidenceThreshold:.8};function We(e){let t=process.env;return{enabled:t.LISA_LLM_ENABLED==="true"||e?.enabled||$e.enabled,provider:e?.provider??$e.provider,model:t.LISA_LLM_MODEL||(e?.model??$e.model),baseUrl:t.LISA_LLM_URL||(e?.baseUrl??$e.baseUrl),confidenceThreshold:e?.confidenceThreshold??$e.confidenceThreshold}}var M=class{static _config=null;static _loaded=!1;static load(t=process.cwd()){if(this._loaded)return this._config;this._loaded=!0;let s=yt.resolve(t,de);if(!Re.existsSync(s))return null;try{let n=Re.readFileSync(s,"utf-8");return this._config=JSON.parse(n),console.log(`[Lisa.ai Config] Loaded project config from ${de}`),this._config}catch(n){return console.warn(`[Lisa.ai Config] Warning: ${de} found but failed to parse \u2014 ${n.message}. Using defaults.`),null}}static get(){return this._config}static applyEnvDefaults(t){if(!t.model)return;let s={claude:"LISA_CLAUDE_MODEL",openai:"LISA_OPENAI_MODEL",gemini:"LISA_GOOGLE_MODEL"},n=t.provider;n&&s[n]&&!process.env[s[n]]&&(process.env[s[n]]=t.model,console.log(`[Lisa.ai Config] Model override applied: ${n} \u2192 ${t.model}`))}static printSummary(t){let s=[];t.projectId&&s.push(`projectId=${t.projectId}`),t.provider&&s.push(`provider=${t.provider}`),t.model&&s.push(`model=${t.model}`),t.testingFramework&&s.push(`testingFramework=${t.testingFramework}`),t.testTypes?.length&&s.push(`testTypes=[${t.testTypes.join(",")}]`),t.maxRetries!==void 0&&s.push(`maxRetries=${t.maxRetries}`),t.testCommand&&s.push(`testCommand="${t.testCommand}"`),t.e2eFramework&&s.push(`e2eFramework=${t.e2eFramework}`),t.e2eCommand&&s.push(`e2eCommand="${t.e2eCommand}"`),s.length&&console.log(`[Lisa.ai Config] Settings: ${s.join(", ")}`),t.skipFiles?.length&&console.log(`[Lisa.ai Config] Extra skip files : ${t.skipFiles.join(", ")}`),t.skipDirs?.length&&console.log(`[Lisa.ai Config] Extra skip dirs : ${t.skipDirs.join(", ")}`),t.skipPaths?.length&&console.log(`[Lisa.ai Config] Extra skip paths : ${t.skipPaths.join(", ")}`);let n=We(t.lisaLlm);n.enabled&&console.log(`[Lisa.ai Config] Lisa LLM enabled: ${n.model} @ ${n.baseUrl} (threshold=${n.confidenceThreshold})`)}};var K=class{static scanRepository(t=process.cwd()){let s=Y.resolve(t,"package.json"),n={type:"unknown",testingFramework:"none"};if(!Q.existsSync(s))return console.warn(`[Lisa.ai AutoDiscovery] No package.json found at ${s}. Defaulting to Generic Node.`),n.type="node",n;let o=JSON.parse(Q.readFileSync(s,"utf8")),i={...o.dependencies||{},...o.devDependencies||{}},a=o.scripts||{};return i["@angular/core"]?n.type="angular":i.react?n.type="react":i.vue?n.type="vue":n.type="node",i.jest||a.test?.includes("jest")?n.testingFramework="jest":i.karma||a.test?.includes("karma")||a.test?.includes("ng test")?n.testingFramework="karma":(i.vitest||a.test?.includes("vitest"))&&(n.testingFramework="vitest"),n.testingFramework!=="none"&&(n.testingFramework==="karma"?n.type==="angular"?n.suggestedTestCommand="npx ng test --no-watch --browsers=ChromeHeadless":n.suggestedTestCommand="npx karma start --single-run --browsers=ChromeHeadless":a.test?n.suggestedTestCommand="npm run test":n.testingFramework==="jest"?n.suggestedTestCommand="npx jest --coverage":n.testingFramework==="vitest"&&(n.suggestedTestCommand="npx vitest run --coverage")),n}static findUntestedFiles(t,s=[]){let n=[];if(!Q.existsSync(t))return n;let o=Q.readdirSync(t),i=M.get(),a=i?.skipDirs??[],r=i?.skipFiles??[],c=i?.skipPaths??[],u=["node_modules","dist","build",".git",".angular","coverage","public","assets",...a],p=["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"],$=[/^\./,/\.config\.(js|ts|mjs|cjs)$/,/\.conf\.(js|ts|mjs|cjs)$/];for(let g of o){let L=Y.join(t,g);if(u.includes(g))continue;let m;try{m=Q.statSync(L)}catch{continue}if(m.isDirectory())n.push(...this.findUntestedFiles(L,s));else if(g.match(/\.(ts|tsx|js|jsx|vue)$/)&&!g.includes(".spec.")&&!g.includes(".test.")){if(p.includes(g)||r.includes(g)||$.some(C=>C.test(g)))continue;let d=Y.extname(g),l=g.slice(0,-d.length);if(![Y.join(t,`${l}.spec${d}`),Y.join(t,`${l}.test${d}`),Y.join(t,`${l}.spec.js`),Y.join(t,`${l}.test.js`),Y.join(t,`${l}.spec.ts`),Y.join(t,`${l}.test.ts`)].some(C=>Q.existsSync(C))){let C=Y.relative(process.cwd(),L);if(c.some(v=>C.startsWith(v)))continue;s.includes(C)||n.push(C)}}}return n}};var wt=require("child_process"),fe=class{static async installMissingFramework(t,s=process.cwd()){if(t.testingFramework!=="none")return t;console.log(`
|
|
110
110
|
[Lisa.ai Auto-Installer] \u{1F6A8} No testing framework detected for ${t.type} architecture.`),console.log("[Lisa.ai Auto-Installer] \u{1FA84} Initiating Zero-Config Installation Protocol...");let n="",o="none";switch(t.type){case"angular":console.log("[Lisa.ai Auto-Installer] Provisioning Angular TestBed environment..."),n="npm install --save-dev karma karma-chrome-launcher karma-coverage karma-jasmine jasmine-core @types/jasmine",o="karma",t.suggestedTestCommand="npm run test";break;case"react":case"node":default:console.log("[Lisa.ai Auto-Installer] Provisioning Universal Jest environment..."),n="npm install --save-dev jest @types/jest ts-jest",o="jest",t.suggestedTestCommand="npx jest --coverage";break;case"vue":console.log("[Lisa.ai Auto-Installer] Provisioning Vue Vitest environment..."),n="npm install --save-dev vitest @vue/test-utils jsdom",o="vitest",t.suggestedTestCommand="npx vitest run --coverage";break}if(n){console.log(`[Lisa.ai Executing] ${n}`);let i=n.split(" "),a=process.platform==="win32"?`${i[0]}.cmd`:i[0];return(0,wt.spawnSync)(a,i.slice(1),{cwd:s,stdio:"inherit"}).status!==0&&(console.error(`
|
|
111
|
-
\u274C [Lisa.ai Auto-Installer] Failed to construct dynamic testing environment.`),process.exit(1)),console.log("\u2705 [Lisa.ai Auto-Installer] Framework successfully injected."),t.testingFramework=o,t}return t}};var Me=
|
|
112
|
-
[Lisa.ai Auto-Generator] \u{1FA84} Analyzing ${t.type} architecture to generate ${t.testingFramework} configuration specs...`),["jest.config.js","jest.config.ts","karma.conf.js","vitest.config.ts","vitest.config.js"].some(c=>Me.existsSync(
|
|
111
|
+
\u274C [Lisa.ai Auto-Installer] Failed to construct dynamic testing environment.`),process.exit(1)),console.log("\u2705 [Lisa.ai Auto-Installer] Framework successfully injected."),t.testingFramework=o,t}return t}};var Me=F(require("fs")),Ke=F(require("path"));var me=class{static async provisionConfigurationFiles(t,s,n,o=process.cwd()){if(t.testingFramework==="none")return;if(console.log(`
|
|
112
|
+
[Lisa.ai Auto-Generator] \u{1FA84} Analyzing ${t.type} architecture to generate ${t.testingFramework} configuration specs...`),["jest.config.js","jest.config.ts","karma.conf.js","vitest.config.ts","vitest.config.js"].some(c=>Me.existsSync(Ke.join(o,c)))){console.log("[Lisa.ai Auto-Generator] Setup file detected. Bypassing initialization.");return}let r=`You are an expert ${t.type} architect.
|
|
113
113
|
Write a production-ready '${t.testingFramework}' configuration file for a standard '${t.type}' application.
|
|
114
114
|
The application resides in the root directory.
|
|
115
115
|
|
|
@@ -117,8 +117,8 @@ Requirements:
|
|
|
117
117
|
1. Ensure it specifically instruments code coverage.
|
|
118
118
|
2. Ensure standard transpilation (ts-jest for jest, or standard karma-webpack/karma-typescript implementations).
|
|
119
119
|
3. Do NOT wrap your response in markdown formatting (no \`\`\`javascript).
|
|
120
|
-
4. Return ONLY the raw code string block.`;try{let c=await ge(r,s,n),u="";t.testingFramework==="jest"&&(u="jest.config.js"),t.testingFramework==="karma"&&(u="karma.conf.js"),t.testingFramework==="vitest"&&(u="vitest.config.ts");let p=
|
|
121
|
-
\u274C [Lisa.ai Auto-Generator] Failed to author configuration file: ${c.message}`),process.exit(1)}}};var vt=
|
|
120
|
+
4. Return ONLY the raw code string block.`;try{let c=await ge(r,s,n),u="";t.testingFramework==="jest"&&(u="jest.config.js"),t.testingFramework==="karma"&&(u="karma.conf.js"),t.testingFramework==="vitest"&&(u="vitest.config.ts");let p=Ke.join(o,u);Me.writeFileSync(p,c,"utf-8"),console.log(`\u2705 [Lisa.ai Auto-Generator] Natively wrote ${u} to repository root.`)}catch(c){console.error(`
|
|
121
|
+
\u274C [Lisa.ai Auto-Generator] Failed to author configuration file: ${c.message}`),process.exit(1)}}};var vt=F(require("dotenv"));vt.config({quiet:!0});function ne(e){let t=e.toLowerCase();return t.includes("karma")||t.includes("ng test")?"karma":t.includes("jest")?"jest":t.includes("vitest")?"vitest":t.includes("playwright")?"playwright":t.includes("cypress")?"cypress":t.includes("mocha")?"mocha":"unknown"}async function Ne(e,t,s){if(s==="local")return[];let n=process.env.LISA_CONTROL_PLANE_URL||"http://localhost:3088";try{let o=new URLSearchParams({projectId:s,errorLog:e.slice(0,1e3),framework:t}),i=await fetch(`${n}/api/v1/lisa/memory/lookup?${o}`);if(!i.ok)return[];let a=await i.json();return a.data??a}catch{return[]}}async function Ve(e,t,s,n){if(n==="local")return;let o=process.env.LISA_CONTROL_PLANE_URL||"http://localhost:3088";try{await fetch(`${o}/api/v1/lisa/memory/record`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({projectId:n,errorLog:e.slice(0,1e3),fixHint:t.slice(0,2e3),framework:s})}),console.log("[Lisa.ai Memory] Fix pattern recorded to Control Plane.")}catch(i){console.debug(`[Lisa.ai Agent Debug] Failed to record memory pattern: ${i.message}`)}}var _=F(require("fs")),W=F(require("path")),ze=require("child_process");function Ue(e){if(_.existsSync(W.join(e,"playwright.config.ts"))||_.existsSync(W.join(e,"playwright.config.js")))return{framework:"playwright",configFile:_.existsSync(W.join(e,"playwright.config.ts"))?"playwright.config.ts":"playwright.config.js",command:"npx playwright test",installed:!0};if(_.existsSync(W.join(e,"cypress.config.ts"))||_.existsSync(W.join(e,"cypress.config.js"))||_.existsSync(W.join(e,"cypress.json")))return{framework:"cypress",configFile:_.existsSync(W.join(e,"cypress.config.ts"))?"cypress.config.ts":_.existsSync(W.join(e,"cypress.config.js"))?"cypress.config.js":"cypress.json",command:"npx cypress run",installed:!0};let t=W.join(e,"package.json");if(_.existsSync(t)){let s=JSON.parse(_.readFileSync(t,"utf-8")),n={...s.dependencies||{},...s.devDependencies||{}};if(n["@playwright/test"])return{framework:"playwright",configFile:null,command:"npx playwright test",installed:!0};if(n.cypress)return{framework:"cypress",configFile:null,command:"npx cypress run",installed:!0}}return{framework:"playwright",configFile:null,command:"npx playwright test",installed:!1}}function De(e){console.log("[Lisa.ai E2E] No E2E framework detected. Installing Playwright...");try{(0,ze.execSync)("npm install -D @playwright/test",{cwd:e,stdio:"pipe"}),console.log("[Lisa.ai E2E] Playwright installed.")}catch(s){throw console.error(`[Lisa.ai E2E] Failed to install Playwright: ${s.message}`),new Error("Could not install @playwright/test. Please install it manually.")}try{console.log("[Lisa.ai E2E] Installing Playwright browsers..."),(0,ze.execSync)("npx playwright install --with-deps chromium",{cwd:e,stdio:"pipe"}),console.log("[Lisa.ai E2E] Chromium browser installed.")}catch{console.warn("[Lisa.ai E2E Warning] Browser install failed. You may need to run 'npx playwright install' manually.")}_.existsSync(W.join(e,"playwright.config.ts"))||(_.writeFileSync(W.join(e,"playwright.config.ts"),`import { defineConfig } from '@playwright/test';
|
|
122
122
|
|
|
123
123
|
export default defineConfig({
|
|
124
124
|
testDir: './e2e',
|
|
@@ -135,17 +135,17 @@ export default defineConfig({
|
|
|
135
135
|
{ name: 'chromium', use: { browserName: 'chromium' } },
|
|
136
136
|
],
|
|
137
137
|
});
|
|
138
|
-
`,"utf-8"),console.log("[Lisa.ai E2E] Generated playwright.config.ts"));let t=
|
|
139
|
-
`)){let o=n.trim();if(/^FAILED/.test(o)||/ FAILED$/.test(o)){let a=o.replace(/^(?:Chrome|Firefox|Safari)(?:\s+Headless)?\s+[\d.]+(?:[\s.]\d+)*\s+\([^)]+\)\s+/,"").replace(/\s+FAILED$/,"").trim();if(s.push(a||o),s.length>=t)break}}return s}function ss(e){let t=e.replace(/\.(spec|test)\.(ts|js|jsx|tsx)$/,".$2");if(t!==e&&
|
|
138
|
+
`,"utf-8"),console.log("[Lisa.ai E2E] Generated playwright.config.ts"));let t=W.join(e,"e2e");_.existsSync(t)||(_.mkdirSync(t,{recursive:!0}),console.log("[Lisa.ai E2E] Created e2e/ directory"))}var te=0,Le=0,he=0,xe=0,Ce=new Set;function Ze(e){let s=[...Ee(e).matchAll(/Executed\s+(\d+)\s+of\s+(\d+)(?:\s+\((\d+)\s+FAILED\))?/g)];if(s.length===0)return null;let n=[...s].reverse().find(u=>u[3]!==void 0)??s[s.length-1],o=parseInt(n[2]),i=n[3]?parseInt(n[3]):0,a=parseInt(n[1])-i,r=o>0?Math.round(a/o*100):0;return` [${"\u2588".repeat(Math.round(r/5))+"\u2591".repeat(20-Math.round(r/5))}] ${r}% ${a} passed ${i} failed ${o} total`}function Lt(e,t=10){let s=[];for(let n of Ee(e).split(`
|
|
139
|
+
`)){let o=n.trim();if(/^FAILED/.test(o)||/ FAILED$/.test(o)){let a=o.replace(/^(?:Chrome|Firefox|Safari)(?:\s+Headless)?\s+[\d.]+(?:[\s.]\d+)*\s+\([^)]+\)\s+/,"").replace(/\s+FAILED$/,"").trim();if(s.push(a||o),s.length>=t)break}}return s}function ss(e){let t=e.replace(/\.(spec|test)\.(ts|js|jsx|tsx)$/,".$2");if(t!==e&&q.existsSync(t))try{return console.log(` [Lisa.ai] \u{1F9E0} Sibling source found: ${se.basename(t)}`),q.readFileSync(t,"utf-8")}catch{return}}function Je(e,t){let s=e.toLowerCase(),n=se.parse(t);if(s.includes("ng test")||s.includes("karma"))return`${e} --include **/${n.base}`;if(s.includes("jest")||s.includes("vitest")||s.includes("playwright"))return`${e} ${t}`;if(s.includes("cypress"))return`${e} --spec ${t}`;if(s.includes("npm")||s.includes("yarn")||s.includes("pnpm")){try{let i=JSON.parse(q.readFileSync(se.resolve(process.cwd(),"package.json"),"utf8")),a="test";s.includes("npm run ")?a=s.split("npm run ")[1].split(" ")[0]:s.includes("yarn ")?a=s.split("yarn ")[1].split(" ")[0]:s.includes("pnpm ")&&(a=s.split("pnpm ")[1].split(" ")[0]);let r=i.scripts?.[a]?.toLowerCase()||"",c=s.includes("npm")?" --":"";if(r.includes("ng test")||r.includes("karma"))return`${e}${c} --include **/${n.base}`;if(r.includes("jest")||r.includes("vitest")||r.includes("playwright"))return`${e}${c} ${t}`}catch{}let o=s.includes("npm")?" --":"";return`${e}${o} ${t}`}return e}function oe(e,t=!1){return new Promise((s,n)=>{let o=(0,$t.spawn)(e,{shell:!0,stdio:["ignore","pipe","pipe"]}),i="",a="";o.stdout?.on("data",r=>{let c=r.toString();if(i+=c,t)for(let u of c.split(`
|
|
140
140
|
`)){let p=u.replace(/\x1B\[[0-9;]*[a-zA-Z]/g,"").trim();p&&(/Executed\s+\d+\s+of\s+\d+/.test(p)?process.stdout.write(`\r ${p.padEnd(72)}`):/^\s*FAILED\b/.test(p)&&(process.stdout.write(`
|
|
141
|
-
`),console.log(` \u2717 ${p.trim()}`)))}}),o.stderr?.on("data",r=>{a+=r.toString()}),o.on("close",r=>{r===0?s({stdout:i,stderr:a}):n({message:`Process exited with code ${r}`,stdout:i,stderr:a})})})}function ns(e,t,s){if(!t.match(/\.(spec|test)\.(ts|js|tsx|jsx|vue)$/))return;if((s||"").toLowerCase().includes("ng test")||(s||"").toLowerCase().includes("karma"))try{
|
|
141
|
+
`),console.log(` \u2717 ${p.trim()}`)))}}),o.stderr?.on("data",r=>{a+=r.toString()}),o.on("close",r=>{r===0?s({stdout:i,stderr:a}):n({message:`Process exited with code ${r}`,stdout:i,stderr:a})})})}function ns(e,t,s){if(!t.match(/\.(spec|test)\.(ts|js|tsx|jsx|vue)$/))return;if((s||"").toLowerCase().includes("ng test")||(s||"").toLowerCase().includes("karma"))try{q.writeFileSync(e,`// Quarantined by Lisa.ai \u2014 could not be automatically healed
|
|
142
142
|
describe('', () => {});
|
|
143
|
-
`,"utf-8"),console.log(" \u{1F6A8} Replaced with empty stub (Angular requires valid TS).")}catch{}else try{
|
|
143
|
+
`,"utf-8"),console.log(" \u{1F6A8} Replaced with empty stub (Angular requires valid TS).")}catch{}else try{q.renameSync(e,e+".broken"),console.log(` \u{1F6A8} Renamed to ${se.basename(e)}.broken to skip in test runner.`)}catch{}}async function Qe(e,t,s,n,o,i,a,r,c,u,p){let $=1,g=!1,L=s,m,d="",l="",y=ss(t),h=await Ne(s,i,c),C=h.filter(x=>x.confidence>=.5).map(x=>x.fixHint);C.length>0&&console.log(` [Memory] ${C.length} proven pattern(s) found \u2014 injecting as LLM hint.`);let v=h.filter(x=>x.confidence>=(p?.confidenceThreshold??.8));if(p?.enabled&&v.length>0){let x=v[0].confidence;console.log(` [Lisa LLM] High-confidence pattern (${x.toFixed(2)}) \u2014 trying ${p.model}...`);let f=q.readFileSync(t,"utf-8"),w=await dt(e,f,s,v.map(S=>S.fixHint),p);if(w){q.writeFileSync(t,w,"utf-8");try{return await oe(o,!1),console.log(" [Lisa LLM] \u2705 Fix verified. Zero API cost."),d=`### Auto-Heal (Lisa LLM)
|
|
144
144
|
**Model:** ${p.model}
|
|
145
145
|
**Fix:**
|
|
146
146
|
\`\`\`typescript
|
|
147
147
|
${w}
|
|
148
|
-
\`\`\``,await
|
|
148
|
+
\`\`\``,await Ve(s,w,i,c),{status:"healed",details:d,lastCode:w,resolvedBy:"lisa-llm"}}catch{console.log(" [Lisa LLM] \u274C Verification failed. Escalating to cloud..."),q.writeFileSync(t,f,"utf-8")}}else console.log(" [Lisa LLM] No usable output. Escalating to cloud...")}for(;$<=r&&!g;){console.log(` [Attempt ${$}/${r}] Requesting fix from ${a}...`);let x=q.readFileSync(t,"utf-8"),f;for(let w=0;w<=3;w++)try{f=await lt(e,x,L,a,u,m,y,C);break}catch(S){let j=(S?.lastError??S)?.statusCode;if((j===529||j===500)&&w<3){let A=30*(w+1);console.warn(` \u26A0\uFE0F LLM overloaded (HTTP ${j}). Waiting ${A}s...`),await new Promise(I=>setTimeout(I,A*1e3))}else{console.error(` \u{1F6A8} LLM API failed: ${S?.message??String(S)}`);break}}if(f===void 0)break;q.writeFileSync(t,f,"utf-8"),d=`### Auto-Heal Analysis
|
|
149
149
|
**Error:**
|
|
150
150
|
\`\`\`bash
|
|
151
151
|
${L.slice(0,2e3)}
|
|
@@ -156,13 +156,13 @@ ${L.slice(0,2e3)}
|
|
|
156
156
|
${f}
|
|
157
157
|
\`\`\``;try{await oe(o,!1),console.log(" \u2705 Isolated verification passed."),g=!0,l=f}catch(w){let S=w.stdout?w.stdout.toString():"";L=(w.stderr?w.stderr.toString():"")+`
|
|
158
158
|
`+S+`
|
|
159
|
-
`+(w.message||""),console.log(" \u274C Isolated verification failed.");let
|
|
159
|
+
`+(w.message||""),console.log(" \u274C Isolated verification failed.");let A=Ze(L);if(A&&console.log(A),Lt(L,3).forEach(I=>console.log(` \u2717 ${I}`)),(n||"").toLowerCase().includes("ng test")||(n||"").toLowerCase().includes("karma")){let I=L.includes(se.basename(e)+":"),k=/ FAILED/.test(L);if(!I&&!k){console.log(" \u2705 Fix verified (build error is from other spec files, not this one)."),g=!0,l=f;continue}}m=`### Attempt ${$} Failed
|
|
160
160
|
\`\`\`typescript
|
|
161
161
|
${f}
|
|
162
162
|
\`\`\`
|
|
163
163
|
|
|
164
164
|
**New Error:**
|
|
165
|
-
${L}`,$++}}return{status:g?"healed":"quarantined",details:d,lastCode:l,resolvedBy:g?a:void 0}}async function ye(e,t,s=1,n=3,o="local",i,a,r="unit",c,u=!1,p){
|
|
165
|
+
${L}`,$++}}return{status:g?"healed":"quarantined",details:d,lastCode:l,resolvedBy:g?a:void 0}}async function ye(e,t,s=1,n=3,o="local",i,a,r="unit",c,u=!1,p){te=0,Le=0,he=0,xe=0,Ce.clear();let $=r==="unit"?"":` (${r})`,g=M.load(process.cwd());if(g&&M.applyEnvDefaults(g),r==="e2e"){if(!e&&g?.e2eCommand&&(e=g.e2eCommand,console.log(`[Lisa.ai Config] Using e2eCommand from .lisai.json: ${e}`)),!e){console.log(`
|
|
166
166
|
[Lisa.ai Heal${$}] Auto-detecting E2E framework...`);let f=Ue(process.cwd());!f.installed&&f.framework==="playwright"&&De(process.cwd()),e=f.command,console.log(`[Lisa.ai Heal${$}] E2E command: ${e}`)}}else if(!e&&g?.testCommand&&(e=g.testCommand,console.log(`[Lisa.ai Config] Using testCommand from .lisai.json: ${e}`)),!e){console.log(`
|
|
167
167
|
[Lisa.ai Auto-Discovery] No --command provided. Scanning framework...`);let f=K.scanRepository();f.testingFramework==="none"&&(f=await fe.installMissingFramework(f),await me.provisionConfigurationFiles(f,t,a)),f.suggestedTestCommand?(e=f.suggestedTestCommand,console.log(`[Lisa.ai Auto-Discovery] Discovered command: ${e}`)):(console.error(`
|
|
168
168
|
\u{1F6A8} [Lisa.ai] Could not discover a test command. Pass --command explicitly.`),process.exit(1))}let L=ne(e),m=[],d="",l=null,y;if(c&&c.length>0)y=c,console.log(`
|
|
@@ -172,54 +172,54 @@ ${L}`,$++}}return{status:g?"healed":"quarantined",details:d,lastCode:l,resolvedB
|
|
|
172
172
|
\u2705 [Lisa.ai] All tests passing. Nothing to heal!`);let w={healedFiles:[],quarantinedFiles:[],testCounts:null,allPassing:!0};return u&&Ye({healedFiles:[],quarantinedFiles:[],testCounts:null,modelProvider:t,framework:"unknown",scope:r},null),w}catch(w){process.stdout.write(`
|
|
173
173
|
`),d=(w.stderr||"")+`
|
|
174
174
|
`+(w.stdout||"")+`
|
|
175
|
-
`+(w.message||"")}l=
|
|
176
|
-
${f}`);let w=Lt(d,10);if(w.length>0){let S=l?.failed??w.length;console.log(` Failing (${S}):`),w.forEach(
|
|
175
|
+
`+(w.message||"")}l=B(d);let f=Ze(d);if(f){console.log(`
|
|
176
|
+
${f}`);let w=Lt(d,10);if(w.length>0){let S=l?.failed??w.length;console.log(` Failing (${S}):`),w.forEach(j=>console.log(` \u2717 ${j}`)),S>w.length&&console.log(` ... and ${S-w.length} more`)}}if(y=Ge(d,process.cwd(),m),y.length===0){let w=Ae(d,m,process.cwd());w||(console.error(`
|
|
177
177
|
\u{1F6A8} [Lisa.ai] Could not identify any failing spec files. Output format may be unsupported.`),process.exit(1)),y=[w]}}let h=y.length;console.log(`
|
|
178
|
-
[Lisa.ai] Found ${h} failing spec(s). Healing each in isolation...`),console.log(`${"\u2500".repeat(60)}`);for(let f of y)N({projectId:o,type:"heal",filePath:f,modelUsed:t,status:"running",details:"Queued for isolated healing.",...l&&{testTotal:l.total,testPassed:l.passed,testFailed:l.failed},testFramework:L});for(let f=0;f<y.length;f++){let w=y[f],S=
|
|
179
|
-
[${f+1}/${h}] ${w}`),!
|
|
180
|
-
`+
|
|
181
|
-
${"\u2500".repeat(60)}`);let C=!1;if(
|
|
178
|
+
[Lisa.ai] Found ${h} failing spec(s). Healing each in isolation...`),console.log(`${"\u2500".repeat(60)}`);for(let f of y)N({projectId:o,type:"heal",filePath:f,modelUsed:t,status:"running",details:"Queued for isolated healing.",...l&&{testTotal:l.total,testPassed:l.passed,testFailed:l.failed},testFramework:L});for(let f=0;f<y.length;f++){let w=y[f],S=se.resolve(process.cwd(),w);if(console.log(`
|
|
179
|
+
[${f+1}/${h}] ${w}`),!q.existsSync(S)){console.warn(" \u26A0\uFE0F File not found \u2014 skipping.");continue}if(/[\\/](node_modules|dist|build)[\\/]/.test(S)){console.warn(" \u26A0\uFE0F Library file \u2014 refusing to modify.");continue}let j=Je(e,w),A=await Qe(w,S,d,e,j,L,t,n,o,a,p);A.status==="healed"?(te++,A.resolvedBy==="lisa-llm"?he++:xe++,Ce.add(w),console.log(` \u2705 Healed [${te} healed ${Le} quarantined so far]`),N({projectId:o,type:"heal",filePath:w,modelUsed:A.resolvedBy==="lisa-llm"?p.model:t,status:"success",details:A.details,...l&&{testTotal:l.total,testPassed:l.passed,testFailed:l.failed},testFramework:L,resolvedBy:A.resolvedBy}),A.resolvedBy!=="lisa-llm"&&await Ve(d,A.lastCode,L,o)):(Le++,m.push(w),console.warn(` \u{1F6A8} Quarantined [${te} healed ${Le} quarantined so far]`),N({projectId:o,type:"heal",filePath:w,modelUsed:t,status:"error",details:`Exhausted all ${n} attempts.
|
|
180
|
+
`+A.details,...l&&{testTotal:l.total,testPassed:l.passed,testFailed:l.failed},testFramework:L}),ns(S,w,e))}console.log(`
|
|
181
|
+
${"\u2500".repeat(60)}`);let C=!1;if(te>0){console.log("[Lisa.ai] Final verification run...");try{await oe(e,!0),process.stdout.write(`
|
|
182
182
|
`),console.log(`
|
|
183
183
|
\u2705 All tests passing!`),C=!0}catch(f){process.stdout.write(`
|
|
184
184
|
`);let w=(f.stderr||"")+`
|
|
185
185
|
`+(f.stdout||"")+`
|
|
186
186
|
`+(f.message||""),S=Ze(w);S&&console.log(`
|
|
187
|
-
${S}`);let
|
|
188
|
-
\u26A0\uFE0F ${
|
|
189
|
-
\u2139\uFE0F ${I.length} additional failing spec(s) not in this run's queue:`),I.forEach(k=>console.warn(` \u2717 ${k}`)),console.warn(" Run lisa-agent heal again to address these."))}}let v=(l?.passed??0)+
|
|
190
|
-
\u2705 Healed: ${
|
|
191
|
-
`);let x={healedFiles:[...Ce],quarantinedFiles:[...m],testCounts:l,allPassing:C};if(u){let f={healedFiles:x.healedFiles,quarantinedFiles:x.quarantinedFiles,testCounts:x.testCounts,modelProvider:t,framework:L,scope:r},w=await mt(f);Ye(f,w),x.allPassing||(process.exitCode=1)}return x}var kt=require("child_process"),
|
|
187
|
+
${S}`);let j=Ge(w,process.cwd(),[]),A=j.filter(k=>Ce.has(k)),I=j.filter(k=>!Ce.has(k)&&!m.includes(k));A.length>0&&(console.warn(`
|
|
188
|
+
\u26A0\uFE0F ${A.length} spec(s) passed isolated verification but still fail globally:`),A.forEach(k=>console.warn(` \u2717 ${k}`)),console.warn(" These likely have cross-test shared state. Try higher maxRetries or fix manually.")),I.length>0&&(console.warn(`
|
|
189
|
+
\u2139\uFE0F ${I.length} additional failing spec(s) not in this run's queue:`),I.forEach(k=>console.warn(` \u2717 ${k}`)),console.warn(" Run lisa-agent heal again to address these."))}}let v=(l?.passed??0)+te;if(console.log(`
|
|
190
|
+
\u2705 Healed: ${te} / ${h}`),p?.enabled&&(he>0||xe>0)){let f=te>0?Math.round(he/te*100):0;console.log(` \u{1F9E0} Lisa LLM: ${he} fix${he!==1?"es":""} (${f}%)`),console.log(` \u2601\uFE0F Cloud: ${xe} fix${xe!==1?"es":""}`)}console.log(` \u{1F6A8} Quarantined: ${Le}`),l&&console.log(` \u{1F4CA} Suite: ${l.passed} passing \u2192 ~${v} passing (est.)`),console.log(`${"\u2500".repeat(60)}
|
|
191
|
+
`);let x={healedFiles:[...Ce],quarantinedFiles:[...m],testCounts:l,allPassing:C};if(u){let f={healedFiles:x.healedFiles,quarantinedFiles:x.quarantinedFiles,testCounts:x.testCounts,modelProvider:t,framework:L,scope:r},w=await mt(f);Ye(f,w),x.allPassing||(process.exitCode=1)}return x}var kt=require("child_process"),X=F(require("fs")),O=F(require("path"));var Oe=F(require("fs")),xt=F(require("path"));function Ct(e){let t=xt.resolve(process.cwd(),e);if(!Oe.existsSync(t))throw new Error(`[Lisa.ai Coverage Error] Coverage file not found at ${t}`);let s=Oe.readFileSync(t,"utf-8"),n=JSON.parse(s),o=[];for(let[i,a]of Object.entries(n))i!=="total"&&(a.lines.pct<100||a.statements.pct<100||a.functions.pct<100||a.branches.pct<100)&&o.push(i);return o}var _e=new Set,ke=0,Xe=3;function os(e){let t=e.testingFramework,s=process.cwd();return X.existsSync(O.join(s,"angular.json"))?"npx ng test --no-watch --code-coverage --browsers=ChromeHeadless":t==="jest"?"npx jest --coverage --coverageReporters=json-summary":t==="vitest"?"npx vitest run --coverage":[".mocharc.yml",".mocharc.yaml",".mocharc.json",".mocharc.js"].some(o=>X.existsSync(O.join(s,o)))?"npx nyc --reporter=json-summary mocha":null}async function St(e,t,s=1,n=3,o="local",i,a,r){let c=M.load(process.cwd());c&&(M.applyEnvDefaults(c),!e&&c.testCommand&&(e=c.testCommand,console.log(`[Lisa.ai Config] Using testCommand from .lisai.json: ${e}`)));let u=K.scanRepository(),p=c?.testingFramework||(u.testingFramework!=="none"?u.testingFramework:void 0),$=c?.testTypes;if(!e){console.log(`
|
|
192
192
|
[Lisa.ai Auto-Discovery] No explicit --command provided. Auto-detecting coverage command...`);let m=os(u);if(m)e=m,console.log(`[Lisa.ai Auto-Discovery] Detected coverage command: ${e}`);else{let d=u;if(d.testingFramework==="none"&&(d=await fe.installMissingFramework(d),await me.provisionConfigurationFiles(d,t,i)),d.suggestedTestCommand){e=d.suggestedTestCommand;let l=d.testingFramework;(l==="jest"||l==="vitest")&&!e.includes("--coverage")&&(e=e.includes("npm run")?`${e} -- --coverage`:`${e} --coverage`,console.log(`[Lisa.ai Unit] Coverage flag appended: ${e}`)),console.log(`[Lisa.ai Auto-Discovery] Bootstrapping with discovered command: ${e}`)}else console.error(`
|
|
193
193
|
[Lisa.ai Fatal Error] Could not auto-detect a test framework. Please pass --command explicitly or add a .lisai.json config.`),process.exit(1)}}console.log(`
|
|
194
194
|
[Lisa.ai Unit] ${e} (Attempt ${s}/${n}) Using Model: ${t}`);let g=ne(e);await N({projectId:o,type:"unit",filePath:"global-test-suite",modelUsed:t,status:"running",details:"Agent is executing unit test suite and validating coverage...",testFramework:g});let L=(m,d=!1)=>new Promise((l,y)=>{let h=(0,kt.spawn)(m,{shell:!0,stdio:["ignore","pipe","pipe"]}),C="",v="";h.stdout?.on("data",x=>{let f=x.toString();if(C+=f,d){let w=f.split(`
|
|
195
|
-
`);for(let S of w){let
|
|
196
|
-
`+m.stderr,l=
|
|
195
|
+
`);for(let S of w){let j=S.match(/Executed\s+(\d+)\s+of\s+(\d+)/);if(j){let A=S.match(/(\d+)\s+FAILED/),I=A?A[1]:0,k=`\r[Lisa.ai Testing] Executed ${j[1]} of ${j[2]} (${I} FAILED) `;process.stdout.write(k)}}}}),h.stderr?.on("data",x=>{v+=x.toString()}),h.on("close",x=>{x===0?l({stdout:C,stderr:v}):y({message:`Test process exited with code ${x}`,stdout:C,stderr:v})})});try{console.log("[Lisa.ai Unit] Running tests with coverage. This may take a moment...");let m=await L(e,!0),d=m.stdout+`
|
|
196
|
+
`+m.stderr,l=B(d);l&&await N({projectId:o,type:"unit",filePath:"global-test-suite",modelUsed:t,status:"success",details:`Tests passed on attempt ${s}.`,testTotal:l.total,testPassed:l.passed,testFailed:l.failed,testFramework:g}),console.log(`
|
|
197
197
|
[Lisa.ai Unit] Tests passed on attempt ${s}.`)}catch(m){let d=(m.stderr||"")+`
|
|
198
198
|
`+(m.stdout||"")+`
|
|
199
199
|
`+(m.message||"");Ae(d,[],process.cwd())!==null?(console.log(`
|
|
200
200
|
[Lisa.ai Unit] Tests failed. Delegating to Auto-Heal...`),await ye(e,t,1,n,o,void 0,i,"unit",void 0,!1,r),console.log(`
|
|
201
201
|
[Lisa.ai Unit] Auto-Heal complete. Continuing to test generation...`)):console.log(`
|
|
202
202
|
[Lisa.ai Unit] No failing spec detected. Tests may not exist yet \u2014 initiating Cold-Start Discovery...`)}try{let m=[];if(a&&a.length>0)m=a,console.log(`
|
|
203
|
-
[Lisa.ai Unit] Targeting ${a.length} file(s) from --files`),m.forEach(l=>console.log(` - ${l}`));else{let l=O.resolve(process.cwd(),"coverage/coverage-summary.json");if(
|
|
204
|
-
[Lisa.ai Unit] No coverage-summary.json found. Initiating Cold-Start Discovery...`),m=K.findUntestedFiles(process.cwd(),[..._e]),m.length===0){console.log("[Lisa.ai Unit] No untested source files discovered.");return}console.log(`[Lisa.ai Unit] Discovered ${m.length} untested file(s). Generating specs...`)}let y=K.findUntestedFiles(process.cwd(),[..._e]);if(y.length>0){let h=new Set(m.map(v=>O.resolve(process.cwd(),v))),C=y.filter(v=>!h.has(O.resolve(process.cwd(),v)));C.length>0&&(console.log(`[Lisa.ai Unit] Discovered ${C.length} additional source file(s) with no tests.`),m.push(...C))}if(m.length===0){console.log("[Lisa.ai Unit] 100% coverage achieved. No uncovered files remaining.");return}}console.log(`[Lisa.ai Unit] Found ${m.length} file(s) below threshold:`,m);let d=0;for(let l of m){if(_e.has(l))continue;let y=O.resolve(process.cwd(),l),h=O.parse(y),C=[O.join(h.dir,`${h.name}.spec${h.ext}`),O.join(h.dir,`${h.name}.test${h.ext}`),O.join(h.dir,`${h.name}.spec.js`),O.join(h.dir,`${h.name}.test.js`),O.join(h.dir,`${h.name}.spec.ts`),O.join(h.dir,`${h.name}.test.ts`)],v=null,x=O.join(h.dir,`${h.name}.spec${h.ext}`);for(let
|
|
205
|
-
[Lisa.ai Unit] [${d+1}/${m.length}] Trying Lisa LLM for ${l}...`);let I=await ut(l,S,
|
|
203
|
+
[Lisa.ai Unit] Targeting ${a.length} file(s) from --files`),m.forEach(l=>console.log(` - ${l}`));else{let l=O.resolve(process.cwd(),"coverage/coverage-summary.json");if(X.existsSync(l))console.log("[Lisa.ai Unit] Evaluating coverage summary..."),m=Ct("coverage/coverage-summary.json");else{if(console.log(`
|
|
204
|
+
[Lisa.ai Unit] No coverage-summary.json found. Initiating Cold-Start Discovery...`),m=K.findUntestedFiles(process.cwd(),[..._e]),m.length===0){console.log("[Lisa.ai Unit] No untested source files discovered.");return}console.log(`[Lisa.ai Unit] Discovered ${m.length} untested file(s). Generating specs...`)}let y=K.findUntestedFiles(process.cwd(),[..._e]);if(y.length>0){let h=new Set(m.map(v=>O.resolve(process.cwd(),v))),C=y.filter(v=>!h.has(O.resolve(process.cwd(),v)));C.length>0&&(console.log(`[Lisa.ai Unit] Discovered ${C.length} additional source file(s) with no tests.`),m.push(...C))}if(m.length===0){console.log("[Lisa.ai Unit] 100% coverage achieved. No uncovered files remaining.");return}}console.log(`[Lisa.ai Unit] Found ${m.length} file(s) below threshold:`,m);let d=0;for(let l of m){if(_e.has(l))continue;let y=O.resolve(process.cwd(),l),h=O.parse(y),C=[O.join(h.dir,`${h.name}.spec${h.ext}`),O.join(h.dir,`${h.name}.test${h.ext}`),O.join(h.dir,`${h.name}.spec.js`),O.join(h.dir,`${h.name}.test.js`),O.join(h.dir,`${h.name}.spec.ts`),O.join(h.dir,`${h.name}.test.ts`)],v=null,x=O.join(h.dir,`${h.name}.spec${h.ext}`);for(let j of C)if(X.existsSync(j)){x=j,v=X.readFileSync(j,"utf-8");break}let f="",w=!1,S=X.readFileSync(y,"utf-8");if(r?.enabled&&!v&&o!=="local"){let A=(await Ne(l,g,o)).filter(I=>I.confidence>=(r.confidenceThreshold??.8));if(A.length>0){console.log(`
|
|
205
|
+
[Lisa.ai Unit] [${d+1}/${m.length}] Trying Lisa LLM for ${l}...`);let I=await ut(l,S,A.map(k=>k.fixHint),r,p);I&&(f=I,ke=0,w=!0,console.log(" [Lisa LLM] \u2705 Test generated. Zero API cost."))}}if(!w)try{v?(console.log(`
|
|
206
206
|
[Lisa.ai Unit] [${d+1}/${m.length}] Existing spec found for ${l}. Requesting coverage append...`),f=await ct(l,S,O.relative(process.cwd(),x),v,t,i,p,$)):(console.log(`
|
|
207
|
-
[Lisa.ai Unit] [${d+1}/${m.length}] Generating new spec for ${l}...`),f=await
|
|
208
|
-
[Lisa.ai Unit] LLM call failed for ${l} \u2014 consecutive failure #${ke}/${Xe}`),console.error(` Error: ${
|
|
207
|
+
[Lisa.ai Unit] [${d+1}/${m.length}] Generating new spec for ${l}...`),f=await ee(l,S,t,i,p,$)),ke=0,w=!0}catch(j){ke++,_e.add(l);let A=j.cause?.message?`${j.message} (cause: ${j.cause.message})`:j.message;if(console.error(`
|
|
208
|
+
[Lisa.ai Unit] LLM call failed for ${l} \u2014 consecutive failure #${ke}/${Xe}`),console.error(` Error: ${A}`),ke>=Xe)throw new Error(`Systematic LLM failure: ${Xe} consecutive API calls failed.
|
|
209
209
|
This usually means your API key has insufficient credits, hit a rate limit, or the model is unavailable.
|
|
210
|
-
Last error: ${
|
|
211
|
-
Tip: Check your API key, account credits, and rate-limit quota for provider '${t}'.`)}w&&(
|
|
210
|
+
Last error: ${A}
|
|
211
|
+
Tip: Check your API key, account credits, and rate-limit quota for provider '${t}'.`)}w&&(X.writeFileSync(x,f,"utf-8"),d++,console.log(`[Lisa.ai Unit] Wrote spec to ${x}`),await N({projectId:o,type:"unit",filePath:l,modelUsed:t,status:"success",details:`### Unit Test Generated
|
|
212
212
|
**Auto-Generated Spec (${t}):**
|
|
213
213
|
\`\`\`typescript
|
|
214
214
|
${f}
|
|
215
215
|
\`\`\``,testFramework:g}))}if(console.log(`
|
|
216
216
|
[Lisa.ai Unit] Generated ${d} spec file(s).`),d>0){console.log("[Lisa.ai Unit] Running verification pass...");try{let l=await L(e,!0),y=l.stdout+`
|
|
217
|
-
`+l.stderr,h=
|
|
217
|
+
`+l.stderr,h=B(y);console.log(`
|
|
218
218
|
[Lisa.ai Unit] Verification passed.`),h&&(console.log(`[Lisa.ai Unit] Final: ${h.passed}/${h.total} passing, ${h.failed} failing.`),await N({projectId:o,type:"unit",filePath:"global-test-suite",modelUsed:t,status:h.failed===0?"success":"error",details:`Verification after generating ${d} specs.`,testTotal:h.total,testPassed:h.passed,testFailed:h.failed,testFramework:g})),h&&h.failed>0&&s<n&&(console.log(`
|
|
219
219
|
[Lisa.ai Unit] ${h.failed} generated test(s) failing. Delegating to Auto-Heal...`),await ye(e,t,1,n,o,void 0,i,"unit",void 0,!1,r))}catch(l){let y=(l.stderr||"")+`
|
|
220
|
-
`+(l.stdout||""),h=
|
|
220
|
+
`+(l.stdout||""),h=B(y);h&&h.failed>0&&s<n?(console.log(`
|
|
221
221
|
[Lisa.ai Unit] ${h.failed} generated test(s) failing. Delegating to Auto-Heal...`),await ye(e,t,1,n,o,void 0,i,"unit",void 0,!1,r)):console.log(`
|
|
222
|
-
[Lisa.ai Unit] Some generated tests failed. Run 'lisa-agent heal' to fix them.`)}}}catch(m){console.error("[Lisa.ai Unit loop failure]:",m.message),await N({projectId:o,type:"unit",filePath:"unit-loop",modelUsed:t,status:"error",details:`Unit test generation encountered an unrecoverable error: ${m.message}`,testFramework:g})}}var bt=require("child_process"),U=
|
|
222
|
+
[Lisa.ai Unit] Some generated tests failed. Run 'lisa-agent heal' to fix them.`)}}}catch(m){console.error("[Lisa.ai Unit loop failure]:",m.message),await N({projectId:o,type:"unit",filePath:"unit-loop",modelUsed:t,status:"error",details:`Unit test generation encountered an unrecoverable error: ${m.message}`,testFramework:g})}}var bt=require("child_process"),U=F(require("fs")),V=F(require("path"));function is(e){let t=[],s=["src/app/app.routes.ts","src/app/app-routing.module.ts"];for(let o of s){let i=V.join(e,o);if(U.existsSync(i)){let r=U.readFileSync(i,"utf-8").matchAll(/path:\s*['"`]([^'"`]+)['"`]/g);for(let c of r)c[1]&&c[1]!=="**"&&t.push(c[1])}}let n=["src/App.tsx","src/App.jsx","src/routes.tsx","src/routes.jsx"];for(let o of n){let i=V.join(e,o);if(U.existsSync(i)){let r=U.readFileSync(i,"utf-8").matchAll(/path=["']([^"']+)["']/g);for(let c of r)c[1]&&t.push(c[1])}}return[...new Set(t)].sort()}async function Ft(e,t,s=1,n=3,o="local",i,a){if(s>n){console.log(`
|
|
223
223
|
[Lisa.ai E2E] Reached maximum retries (${n}). Stopping.`);return}let r=process.cwd(),c=M.load(r);c&&(M.applyEnvDefaults(c),!e&&c.e2eCommand&&(e=c.e2eCommand,console.log(`[Lisa.ai Config] Using e2eCommand from .lisai.json: ${e}`)));let u=Ue(r),p=c?.e2eFramework||u.framework;console.log(`
|
|
224
224
|
[Lisa.ai E2E] Framework: ${p} (Attempt ${s}/${n}) Model: ${t}`),!u.installed&&p==="playwright"&&De(r),e||(e=u.command,console.log(`[Lisa.ai E2E] Using auto-detected command: ${e}`)),await N({projectId:o,type:"e2e",filePath:"e2e-suite",modelUsed:t,status:"running",details:`E2E test generation started with ${p}`,testFramework:p});let $=0;if(a&&a.length>0){console.log(`
|
|
225
225
|
[Lisa.ai E2E] Targeting ${a.length} file(s) from --files`),a.forEach(y=>console.log(` - ${y}`));for(let y of a){let h=V.resolve(r,y);if(U.existsSync(h)){console.log(`[Lisa.ai E2E] Spec already exists: ${y} \u2014 will run as-is.`);continue}let C=V.dirname(h);U.existsSync(C)||U.mkdirSync(C,{recursive:!0}),console.log(`[Lisa.ai E2E] Generating E2E spec: ${y}...`);let v=`
|
|
@@ -237,7 +237,7 @@ The test should:
|
|
|
237
237
|
4. Assert critical UI elements are visible
|
|
238
238
|
|
|
239
239
|
Use @playwright/test imports. Keep it concise and practical.
|
|
240
|
-
`;try{let x=await
|
|
240
|
+
`;try{let x=await ee(y,v,t,i,p,["e2e"]);U.writeFileSync(h,x,"utf-8"),console.log(`[Lisa.ai E2E] Wrote ${y}`),$++,await N({projectId:o,type:"e2e",filePath:y,modelUsed:t,status:"success",details:`Generated E2E spec: ${y}`,testFramework:p})}catch(x){console.error(`[Lisa.ai E2E] LLM failed for ${y}: ${x.message}`),await N({projectId:o,type:"e2e",filePath:y,modelUsed:t,status:"error",details:`LLM failed to generate E2E spec: ${x.message}`,testFramework:p})}}if($===0&&a.every(y=>!U.existsSync(V.resolve(r,y)))){console.log("[Lisa.ai E2E] No specs generated. Check LLM connectivity with 'lisa-agent diagnose'.");return}}else{let y=is(r);y.length===0?(console.log("[Lisa.ai E2E] No routes discovered. Will generate basic smoke test."),y.push("")):console.log(`[Lisa.ai E2E] Discovered ${y.length} route(s):`,y);let h=V.join(r,"e2e");U.existsSync(h)||U.mkdirSync(h,{recursive:!0});let C=U.existsSync(h)?U.readdirSync(h).filter(v=>v.endsWith(".spec.ts")||v.endsWith(".test.ts")):[];for(let v of y){let f=`${(v||"home").replace(/\//g,"-")}.spec.ts`,w=V.join(h,f);if(C.includes(f)){console.log(`[Lisa.ai E2E] Spec already exists for /${v} \u2014 skipping.`);continue}console.log(`[Lisa.ai E2E] Generating E2E spec for /${v}...`);try{let S=`
|
|
241
241
|
E2E Test Generation Context:
|
|
242
242
|
- Framework: ${p}
|
|
243
243
|
- Route: /${v}
|
|
@@ -252,18 +252,18 @@ The test should:
|
|
|
252
252
|
4. Assert critical UI elements are visible
|
|
253
253
|
|
|
254
254
|
Use @playwright/test imports. Keep it concise and practical.
|
|
255
|
-
`,
|
|
255
|
+
`,j=await ee(`e2e/${f}`,S,t,i,p,["e2e"]);U.writeFileSync(w,j,"utf-8"),console.log(`[Lisa.ai E2E] Wrote ${f}`),$++,await N({projectId:o,type:"e2e",filePath:`e2e/${f}`,modelUsed:t,status:"success",details:`Generated E2E spec for route /${v}`,testFramework:p})}catch(S){console.error(`[Lisa.ai E2E] LLM failed for /${v}: ${S.message}`),await N({projectId:o,type:"e2e",filePath:`e2e/${f}`,modelUsed:t,status:"error",details:`LLM failed to generate E2E spec: ${S.message}`,testFramework:p})}}if($===0&&C.length===0){console.log("[Lisa.ai E2E] No specs generated. Check LLM connectivity with 'lisa-agent diagnose'.");return}}console.log(`
|
|
256
256
|
[Lisa.ai E2E] Running E2E tests: ${e}`);let g="",L="",m=!1;try{await new Promise((y,h)=>{let C=(0,bt.spawn)(e,{shell:!0,stdio:["ignore","pipe","pipe"],cwd:r});C.stdout?.on("data",v=>{let x=v.toString();g+=x,process.stdout.write(x)}),C.stderr?.on("data",v=>{let x=v.toString();L+=x,process.stderr.write(x)}),C.on("close",v=>{v===0?y():h(new Error(`E2E tests exited with code ${v}`))})}),m=!0,console.log(`
|
|
257
257
|
[Lisa.ai E2E] All E2E tests passed.`)}catch{console.log(`
|
|
258
258
|
[Lisa.ai E2E] Some E2E tests failed. Run 'lisa-agent heal --type e2e' to auto-fix.`)}let d=g+`
|
|
259
|
-
`+L,l=
|
|
259
|
+
`+L,l=B(d);await N({projectId:o,type:"e2e",filePath:"e2e-suite",modelUsed:t,status:m?"success":"error",details:`E2E generation complete. ${$} new spec(s) generated.`,...l&&{testTotal:l.total,testPassed:l.passed,testFailed:l.failed},testFramework:p})}var jt=require("child_process"),R=F(require("fs")),P=F(require("path"));function as(e){let t=P.join(e,"package.json");if(!R.existsSync(t))return"node";let s=JSON.parse(R.readFileSync(t,"utf-8")),n={...s.dependencies||{},...s.devDependencies||{}};return n["@angular/core"]?"angular":n.react?"react":n.vue?"vue":n["@nestjs/core"]?"nestjs":n.express?"express":"node"}function rs(e,t){let s=[];if(t==="angular"){let n=P.join(e,"src","app");if(!R.existsSync(n))return s;let o=Se(n,/\.(ts)$/).filter(i=>!i.includes(".spec.")&&!i.includes(".test.")&&!i.includes("node_modules"));for(let i of o){let a=R.readFileSync(i,"utf-8");if(a.includes("@Component")&&a.includes("inject(")){let r=P.relative(e,i),c=r.replace(/\.ts$/,".integration.spec.ts");if(R.existsSync(P.join(e,c)))continue;let p=[...a.matchAll(/inject\((\w+)\)/g)].map(g=>g[1]),$=[];for(let g of p){let L=Se(n,new RegExp(`${ls(g)}\\.ts$`));$.push(...L.map(m=>P.relative(e,m)))}s.push({filePath:r,relatedFiles:$,description:`Angular component+service integration: ${P.basename(i)} injects ${p.join(", ")}`})}}}else if(t==="express"||t==="nestjs"){let n=["src","server","api","routes","controllers"];for(let o of n){let i=P.join(e,o);if(!R.existsSync(i))continue;let a=Se(i,/\.(controller|route|router)\.(ts|js)$/).filter(r=>!r.includes(".spec.")&&!r.includes(".test.")&&!r.includes("node_modules"));for(let r of a){let c=P.relative(e,r),u=c.replace(/\.(ts|js)$/,".integration.spec.$1");R.existsSync(P.join(e,u))||s.push({filePath:c,relatedFiles:[],description:`API endpoint integration test for ${P.basename(r)} \u2014 test with supertest against real middleware chain`})}}}else{let n=P.join(e,"src");if(R.existsSync(n)){let o=Se(n,/\.(ts|js|tsx|jsx)$/).filter(i=>!i.includes(".spec.")&&!i.includes(".test.")&&!i.includes("node_modules"));for(let i of o){let r=[...R.readFileSync(i,"utf-8").matchAll(/from\s+['"]\.\.?\//g)];if(r.length>=2){let c=P.relative(e,i),u=c.replace(/\.(ts|js|tsx|jsx)$/,".integration.spec.$1");if(R.existsSync(P.join(e,u)))continue;s.push({filePath:c,relatedFiles:[],description:`Module integration: ${P.basename(i)} imports ${r.length} local modules`})}}}}return s}function Se(e,t){let s=[],n=["node_modules","dist","build",".git",".angular","coverage"];if(!R.existsSync(e))return s;let o=R.readdirSync(e);for(let i of o){if(n.includes(i))continue;let a=P.join(e,i);try{R.statSync(a).isDirectory()?s.push(...Se(a,t)):t.test(i)&&s.push(a)}catch{continue}}return s}function ls(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()}async function Et(e,t,s=1,n=3,o="local",i,a){if(s>n){console.log(`
|
|
260
260
|
[Lisa.ai Integration] Reached maximum retries (${n}). Stopping.`);return}let r=process.cwd(),c=M.load(r);c&&(M.applyEnvDefaults(c),!e&&c.testCommand&&(e=c.testCommand,console.log(`[Lisa.ai Config] Using testCommand from .lisai.json: ${e}`)));let u=as(r),p=K.scanRepository(),$=c?.testingFramework||(p.testingFramework!=="none"?p.testingFramework:void 0);console.log(`
|
|
261
261
|
[Lisa.ai Integration] App: ${u}, Framework: ${$||"auto"} (Attempt ${s}/${n}) Model: ${t}`),e||(p.suggestedTestCommand?e=p.suggestedTestCommand:R.existsSync(P.join(r,"angular.json"))?e="npx ng test --no-watch --browsers=ChromeHeadless":(console.error(`
|
|
262
262
|
[Lisa.ai Fatal Error] Could not auto-detect a test command. Please pass --command explicitly.`),process.exit(1)),console.log(`[Lisa.ai Integration] Auto-detected command: ${e}`));let g=ne(e);await N({projectId:o,type:"integration",filePath:"integration-suite",modelUsed:t,status:"running",details:`Integration test generation started for ${u} app`,testFramework:g});let L;if(a&&a.length>0)L=a.map(v=>({filePath:v,relatedFiles:[],description:"User-specified"})),console.log(`
|
|
263
|
-
[Lisa.ai Integration] Targeting ${a.length} file(s) from --files`),L.forEach(v=>console.log(` - ${v.filePath}`));else{if(L=rs(r,u),L.length===0){console.log("[Lisa.ai Integration] No integration targets discovered. The project may already have full integration coverage.");return}console.log(`[Lisa.ai Integration] Found ${L.length} integration target(s):`),L.forEach(v=>console.log(` - ${v.filePath}: ${v.description}`))}let m=0;for(let v of L){let x=P.resolve(r,v.filePath);if(!R.existsSync(x))continue;let f=P.parse(x),w=P.join(f.dir,`${f.name}.integration.spec${f.ext}`);if(R.existsSync(w)){console.log(`[Lisa.ai Integration] Spec exists for ${v.filePath} \u2014 skipping.`);continue}console.log(`[Lisa.ai Integration] Generating integration spec for ${v.filePath}...`);let S=R.readFileSync(x,"utf-8"),
|
|
263
|
+
[Lisa.ai Integration] Targeting ${a.length} file(s) from --files`),L.forEach(v=>console.log(` - ${v.filePath}`));else{if(L=rs(r,u),L.length===0){console.log("[Lisa.ai Integration] No integration targets discovered. The project may already have full integration coverage.");return}console.log(`[Lisa.ai Integration] Found ${L.length} integration target(s):`),L.forEach(v=>console.log(` - ${v.filePath}: ${v.description}`))}let m=0;for(let v of L){let x=P.resolve(r,v.filePath);if(!R.existsSync(x))continue;let f=P.parse(x),w=P.join(f.dir,`${f.name}.integration.spec${f.ext}`);if(R.existsSync(w)){console.log(`[Lisa.ai Integration] Spec exists for ${v.filePath} \u2014 skipping.`);continue}console.log(`[Lisa.ai Integration] Generating integration spec for ${v.filePath}...`);let S=R.readFileSync(x,"utf-8"),j="";for(let I of v.relatedFiles.slice(0,3)){let k=P.resolve(r,I);if(R.existsSync(k)){let H=R.readFileSync(k,"utf-8");j+=`
|
|
264
264
|
|
|
265
265
|
// --- Related file: ${I} ---
|
|
266
|
-
${H.slice(0,5e3)}`}}let
|
|
266
|
+
${H.slice(0,5e3)}`}}let A=`
|
|
267
267
|
INTEGRATION TEST \u2014 ${v.description}
|
|
268
268
|
|
|
269
269
|
Generate an integration test that exercises real module interactions (NOT mocked).
|
|
@@ -285,13 +285,13 @@ ${u==="angular"?`
|
|
|
285
285
|
Source file:
|
|
286
286
|
${S.slice(0,1e4)}
|
|
287
287
|
|
|
288
|
-
${
|
|
289
|
-
${
|
|
290
|
-
`;try{let I=await
|
|
288
|
+
${j?`Related modules:
|
|
289
|
+
${j}`:""}
|
|
290
|
+
`;try{let I=await ee(v.filePath,A,t,i,$,["integration"]);R.writeFileSync(w,I,"utf-8"),console.log(`[Lisa.ai Integration] Wrote ${P.relative(r,w)}`),m++,await N({projectId:o,type:"integration",filePath:v.filePath,modelUsed:t,status:"success",details:`Generated integration spec: ${v.description}`,testFramework:g})}catch(I){console.error(`[Lisa.ai Integration] LLM failed for ${v.filePath}: ${I.message}`)}}if(m===0){console.log("[Lisa.ai Integration] No new integration specs generated.");return}console.log(`
|
|
291
291
|
[Lisa.ai Integration] Running tests to verify generated specs: ${e}`);let d="",l="",y=!1;try{await new Promise((v,x)=>{let f=(0,jt.spawn)(e,{shell:!0,stdio:["ignore","pipe","pipe"],cwd:r});f.stdout?.on("data",w=>{let S=w.toString();d+=S,process.stdout.write(S)}),f.stderr?.on("data",w=>{let S=w.toString();l+=S,process.stderr.write(S)}),f.on("close",w=>{w===0?v():x(new Error(`Tests exited with code ${w}`))})}),y=!0,console.log(`
|
|
292
292
|
[Lisa.ai Integration] All tests passed including integration specs.`)}catch{console.log(`
|
|
293
293
|
[Lisa.ai Integration] Some tests failed. Run 'lisa-agent heal --type integration' to auto-fix.`)}let h=d+`
|
|
294
|
-
`+l,C=
|
|
294
|
+
`+l,C=B(h);await N({projectId:o,type:"integration",filePath:"integration-suite",modelUsed:t,status:y?"success":"error",details:`Integration test generation complete. ${m} new spec(s) generated.`,...C&&{testTotal:C.total,testPassed:C.passed,testFailed:C.failed},testFramework:g})}var Pt=F(require("dotenv")),It=require("child_process"),ae=F(require("fs")),ie=F(require("path"));Pt.config({quiet:!0});function we(e){return new Promise(t=>{let s=(0,It.spawn)(e,{shell:!0,stdio:["ignore","pipe","pipe"]}),n="",o="";s.stdout?.on("data",i=>{n+=i.toString()}),s.stderr?.on("data",i=>{o+=i.toString()}),s.on("close",i=>t({stdout:n,stderr:o,code:i??1}))})}function cs(){return ae.existsSync(ie.resolve(process.cwd(),"pnpm-lock.yaml"))?"pnpm":ae.existsSync(ie.resolve(process.cwd(),"yarn.lock"))?"yarn":"npm"}var ds={critical:"\u{1F534}",high:"\u{1F7E0}",moderate:"\u{1F7E1}",low:"\u{1F535}",info:"\u26AA"},At={critical:5,high:4,moderate:3,low:2,info:1};function Tt(e){let t=["critical","high","moderate","low","info"];for(let s of t){let n=e[s]??0;n>0&&console.log(` ${ds[s]} ${s.padEnd(10)} ${n}`)}console.log(` ${"\u2500".repeat(22)}`),console.log(` ${"Total".padEnd(10)} ${e.total}`)}async function et(e){try{return JSON.parse(e)}catch{return null}}async function Rt(e,t="local",s){ae.existsSync(ie.resolve(process.cwd(),"package.json"))||(console.error(`
|
|
295
295
|
\u{1F6A8} [Lisa.ai Audit] No package.json found in ${process.cwd()}.`),console.error(" The audit command currently supports Node.js / npm projects only."),process.exit(1));let n=cs();console.log(`[Lisa.ai Audit] Package manager: ${n}`),console.log(`
|
|
296
296
|
[Lisa.ai Audit] Scanning for vulnerabilities...`);let{stdout:o,code:i}=await we(n==="npm"?"npm audit --json":n==="yarn"?"yarn audit --json":"pnpm audit --json");if(i===0){console.log(`
|
|
297
297
|
\u2705 [Lisa.ai Audit] No vulnerabilities found! Repository is clean.`);return}let a=await et(o);a||(console.error("[Lisa.ai Audit] Could not parse audit output. Run 'npm audit' manually to investigate."),process.exit(1));let r=a.metadata.vulnerabilities;console.log(`
|
|
@@ -300,11 +300,11 @@ ${F}`:""}
|
|
|
300
300
|
`)){let H=k.trim();H&&!H.startsWith("npm warn")&&!H.startsWith("npm notice")&&console.log(` ${H}`)}let u=n==="npm"?"npm audit --json":n==="yarn"?"yarn audit --json":"pnpm audit --json",{stdout:p,code:$}=await we(u);if($===0){console.log(`
|
|
301
301
|
\u2705 [Lisa.ai Audit] All vulnerabilities fixed by safe auto-fix!`);return}let g=await et(p);if(!g){console.warn("[Lisa.ai Audit] Could not parse post-fix audit output.");return}let L=Object.values(g.vulnerabilities),m=g.metadata.vulnerabilities,d=r.total-m.total;console.log(`
|
|
302
302
|
[Lisa.ai Audit] Safe auto-fix resolved ${d} vulnerability(-ies).`),m.total>0&&(console.log(" Remaining:"),Tt(m));let l=L.filter(k=>typeof k.fixAvailable=="object"&&!k.fixAvailable.isSemVerMajor).map(k=>k.fixAvailable),y=[...new Map(l.map(k=>[k.name,k])).values()];if(y.length>0){console.log(`
|
|
303
|
-
[Lisa.ai Audit] Applying ${y.length} non-breaking targeted upgrade(s)...`);for(let ue of y){let
|
|
303
|
+
[Lisa.ai Audit] Applying ${y.length} non-breaking targeted upgrade(s)...`);for(let ue of y){let J=`npm install ${ue.name}@${ue.version}`;console.log(` \u2192 ${J}`);let{stderr:ve}=await we(J);ve&&!ve.includes("npm warn")&&console.warn(` ${ve.trim()}`)}let{stdout:k,code:H}=await we(u);if(H===0){console.log(`
|
|
304
304
|
\u2705 [Lisa.ai Audit] All vulnerabilities resolved!`);return}}let{stdout:h}=await we(u),C=await et(h)??g,x=Object.values(C.vulnerabilities).sort((k,H)=>(At[H.severity]??0)-(At[k.severity]??0)).slice(0,10).filter(k=>k.fixAvailable===!1?!0:typeof k.fixAvailable=="object"?k.fixAvailable.isSemVerMajor:!1);if(x.length===0){console.log(`
|
|
305
305
|
\u2705 [Lisa.ai Audit] No breaking-change or unfixable vulnerabilities remain.`);return}console.log(`
|
|
306
|
-
[Lisa.ai Audit] ${x.length} vulnerability(-ies) require manual attention.`),console.log(`[Lisa.ai Audit] Consulting ${e} for remediation guidance...`);let f={};try{f=JSON.parse(ae.readFileSync(ie.resolve(process.cwd(),"package.json"),"utf8"))}catch{}let w={...f.dependencies??{},...f.devDependencies??{}},S=x.map(k=>{let H=k.fixAvailable,ue=H===!1?"no fix available yet":`upgrade to ${H.name}@${H.version} (BREAKING \u2014 major version bump)`,
|
|
307
|
-
`),
|
|
306
|
+
[Lisa.ai Audit] ${x.length} vulnerability(-ies) require manual attention.`),console.log(`[Lisa.ai Audit] Consulting ${e} for remediation guidance...`);let f={};try{f=JSON.parse(ae.readFileSync(ie.resolve(process.cwd(),"package.json"),"utf8"))}catch{}let w={...f.dependencies??{},...f.devDependencies??{}},S=x.map(k=>{let H=k.fixAvailable,ue=H===!1?"no fix available yet":`upgrade to ${H.name}@${H.version} (BREAKING \u2014 major version bump)`,J=w[k.name]?`current: ${w[k.name]}`:"transitive dependency";return`\u2022 ${k.name} (${k.severity}) \u2014 ${J} \u2014 ${ue}`}).join(`
|
|
307
|
+
`),j=`You are a Node.js security expert helping a developer fix npm vulnerabilities.
|
|
308
308
|
|
|
309
309
|
These vulnerabilities remain after safe auto-fixes were applied. Each requires either a breaking upgrade or has no automated fix:
|
|
310
310
|
|
|
@@ -316,15 +316,15 @@ For each vulnerability:
|
|
|
316
316
|
3. If the upgrade is breaking (major version), note the most likely code change needed (e.g. renamed API, changed import path).
|
|
317
317
|
4. If no fix exists, suggest a workaround or safe alternative package.
|
|
318
318
|
|
|
319
|
-
Be concrete. Use numbered items. If a vulnerability is a transitive dependency, explain how to force a resolution override in package.json.`;try{let k=await ge(
|
|
319
|
+
Be concrete. Use numbered items. If a vulnerability is a transitive dependency, explain how to force a resolution override in package.json.`;try{let k=await ge(j,e,s);console.log(`
|
|
320
320
|
Remediation guidance:
|
|
321
|
-
`);for(let
|
|
322
|
-
`))console.log(` ${
|
|
321
|
+
`);for(let J of k.split(`
|
|
322
|
+
`))console.log(` ${J}`);let H=ie.resolve(process.cwd(),"lisa-audit-report.md"),ue=["# Lisa.ai Security Audit Report","",`Generated: ${new Date().toISOString()}`,`Project: ${f.name??ie.basename(process.cwd())}`,"","## Summary","","| Severity | Initial | Remaining |","|----------|---------|-----------|",...["critical","high","moderate","low"].map(J=>{let ve=r[J]??0,Kt=C.metadata.vulnerabilities[J]??0;return`| ${J} | ${ve} | ${Kt} |`}),"","## Vulnerabilities Requiring Manual Action","",S,"",`## Remediation Guidance (${e})`,"",k].join(`
|
|
323
323
|
`);ae.writeFileSync(H,ue,"utf-8"),console.log(`
|
|
324
324
|
[Lisa.ai Audit] Full report saved to: lisa-audit-report.md`)}catch(k){console.error(`
|
|
325
|
-
[Lisa.ai Audit] LLM guidance failed: ${k.message}`),console.log(" Run 'npm audit' manually and address the remaining vulnerabilities.")}let
|
|
326
|
-
${"\u2500".repeat(50)}`),console.log(" Lisa.ai Audit Complete"),console.log(` \u2705 Fixed: ${I} / ${r.total}`),console.log(` \u26A0\uFE0F Remaining: ${
|
|
327
|
-
`)}var Dt=
|
|
325
|
+
[Lisa.ai Audit] LLM guidance failed: ${k.message}`),console.log(" Run 'npm audit' manually and address the remaining vulnerabilities.")}let A=C.metadata.vulnerabilities,I=r.total-A.total;console.log(`
|
|
326
|
+
${"\u2500".repeat(50)}`),console.log(" Lisa.ai Audit Complete"),console.log(` \u2705 Fixed: ${I} / ${r.total}`),console.log(` \u26A0\uFE0F Remaining: ${A.total} (see lisa-audit-report.md)`),console.log(`${"\u2500".repeat(50)}
|
|
327
|
+
`)}var Dt=F(require("readline")),G=F(require("fs")),z=F(require("path")),Ot=require("ai");var Mt=40,Nt=8e3,Ut=15e3,T="\x1B[38;2;99;102;241m",b="\x1B[0m",He="\x1B[2m",_t="\x1B[1m";function us(){let e="unknown";try{e=JSON.parse(G.readFileSync(z.resolve(process.cwd(),"package.json"),"utf-8")).name||"unknown"}catch{}let t={type:"unknown",testingFramework:"none"};try{t=K.scanRepository()}catch{}let s=M.get();return{packageName:e,frameworkType:t.type,testingFramework:s?.testingFramework||t.testingFramework,testCommand:s?.testCommand||t.suggestedTestCommand||null,e2eFramework:s?.e2eFramework||null,hasLisaiConfig:!!s}}function ps(e){return`You are Lisa.ai, an autonomous CI/CD expert focused on test healing and generation.
|
|
328
328
|
You are chatting with a developer about their project in an interactive terminal session.
|
|
329
329
|
|
|
330
330
|
Project: ${e.packageName}
|
|
@@ -339,26 +339,28 @@ Guidelines:
|
|
|
339
339
|
- Do not generate full test files unless explicitly asked \u2014 prefer explaining the approach.
|
|
340
340
|
- If the user shares test output, analyze it and suggest specific fixes.
|
|
341
341
|
- When the user loads a file via /read, use its content to answer questions or generate code.
|
|
342
|
-
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
${T}/
|
|
347
|
-
${T}/
|
|
348
|
-
${T}/
|
|
349
|
-
${T}/
|
|
350
|
-
${T}/
|
|
351
|
-
${T}/
|
|
352
|
-
${T}/
|
|
342
|
+
- When generating code, always wrap it in a markdown code block so the user can save it with /write.
|
|
343
|
+
- The user can use slash commands (/read, /write, /heal, /test, /explain, /generate, /status, /help, /exit) \u2014 you don't need to explain them unless asked.`}function gs(){console.log(`
|
|
344
|
+
${_t}Available commands:${b}
|
|
345
|
+
|
|
346
|
+
${T}/read${b} <file> Load a file into conversation context
|
|
347
|
+
${T}/write${b} <file> Save last code block from Lisa's response to a file
|
|
348
|
+
${T}/test${b} [cmd] Run tests and show results
|
|
349
|
+
${T}/explain${b} <file> Analyze why a test is failing (no file changes)
|
|
350
|
+
${T}/heal${b} <file> Auto-heal a specific failing test file
|
|
351
|
+
${T}/generate${b} <file> Generate test spec for a source file
|
|
352
|
+
${T}/status${b} Show test pass/fail summary
|
|
353
|
+
${T}/help${b} Show this help
|
|
354
|
+
${T}/exit${b} Exit chat
|
|
353
355
|
|
|
354
356
|
Or just type a question to chat with Lisa.ai.
|
|
355
357
|
`)}async function fs(e,t){let s=t.trim()||e.projectContext.testCommand;if(!s){console.log(`
|
|
356
|
-
${T}Error:${
|
|
358
|
+
${T}Error:${b} No test command found. Use /test <command> or set testCommand in .lisai.json
|
|
357
359
|
`);return}console.log(`
|
|
358
|
-
Running: ${He}${s}${
|
|
360
|
+
Running: ${He}${s}${b}
|
|
359
361
|
`);try{let{stdout:n,stderr:o}=await oe(s,!0);process.stdout.write(`
|
|
360
362
|
`);let i=o+`
|
|
361
|
-
`+n,a=
|
|
363
|
+
`+n,a=B(i);console.log(a?`
|
|
362
364
|
\u2705 ${a.passed} passed ${a.failed} failed ${a.total} total
|
|
363
365
|
`:`
|
|
364
366
|
\u2705 Tests passed.
|
|
@@ -366,39 +368,48 @@ Guidelines:
|
|
|
366
368
|
${e.lastTestOutput}`})}catch(n){process.stdout.write(`
|
|
367
369
|
`);let o=(n.stderr||"")+`
|
|
368
370
|
`+(n.stdout||"")+`
|
|
369
|
-
`+(n.message||""),i=
|
|
371
|
+
`+(n.message||""),i=B(o);console.log(i?`
|
|
370
372
|
\u2717 ${i.passed} passed ${i.failed} failed ${i.total} total
|
|
371
373
|
`:`
|
|
372
374
|
\u2717 Tests failed.
|
|
373
375
|
`),e.lastTestOutput=o.slice(0,Nt),e.messages.push({role:"user",content:`[Test Output \u2014 failures detected]
|
|
374
376
|
${e.lastTestOutput}`})}}function ms(e,t){let s=t.trim();if(!s){console.log(`
|
|
375
|
-
${T}Usage:${
|
|
376
|
-
`);return}let n=
|
|
377
|
-
${T}Error:${
|
|
378
|
-
`);return}let o=
|
|
377
|
+
${T}Usage:${b} /read <file-path>
|
|
378
|
+
`);return}let n=z.resolve(process.cwd(),s);if(!G.existsSync(n)){console.log(`
|
|
379
|
+
${T}Error:${b} File not found: ${s}
|
|
380
|
+
`);return}let o=G.readFileSync(n,"utf-8"),i=o.split(`
|
|
379
381
|
`).length,a=o.length>Ut?o.slice(0,Ut)+`
|
|
380
382
|
// ... (truncated)`:o;e.messages.push({role:"user",content:`[File Loaded: ${s}]
|
|
381
383
|
\`\`\`
|
|
382
384
|
${a}
|
|
383
385
|
\`\`\``}),console.log(`
|
|
384
386
|
\u2713 Loaded ${s} (${i} lines) into context
|
|
385
|
-
`)}
|
|
386
|
-
${T}Usage:${
|
|
387
|
-
`);return}let n=
|
|
388
|
-
${T}Error:${
|
|
389
|
-
`);return}let o
|
|
387
|
+
`)}function hs(e,t){let s=t.trim();if(!s){console.log(`
|
|
388
|
+
${T}Usage:${b} /write <file-path>
|
|
389
|
+
`);return}let n=[...e.messages].reverse().find(p=>p.role==="assistant");if(!n||typeof n.content!="string"){console.log(`
|
|
390
|
+
${T}Error:${b} No previous Lisa response to extract code from.
|
|
391
|
+
`);return}let o=/```(?:\w*)\n([\s\S]*?)```/g,i=[...n.content.matchAll(o)];if(i.length===0){console.log(`
|
|
392
|
+
${T}Error:${b} No code block found in Lisa's last response.
|
|
393
|
+
`);return}let a=i[i.length-1][1],r=z.resolve(process.cwd(),s),c=z.dirname(r);G.existsSync(c)||G.mkdirSync(c,{recursive:!0}),G.writeFileSync(r,a,"utf-8");let u=a.split(`
|
|
394
|
+
`).length;console.log(`
|
|
395
|
+
\u2713 Saved to ${s} (${u} lines)
|
|
396
|
+
`),e.messages.push({role:"user",content:`[File Written: ${s}] (${u} lines saved from last response)`})}async function ys(e,t){let s=t.trim();if(!s){console.log(`
|
|
397
|
+
${T}Usage:${b} /explain <file-path>
|
|
398
|
+
`);return}let n=z.resolve(process.cwd(),s);if(!G.existsSync(n)){console.log(`
|
|
399
|
+
${T}Error:${b} File not found: ${s}
|
|
400
|
+
`);return}let o=G.readFileSync(n,"utf-8"),i=o.length>1e4?o.slice(0,1e4)+`
|
|
390
401
|
// ... (truncated)`:o,a=`The user wants you to analyze why a test file is failing. Do NOT generate a fix \u2014 just explain the root cause.
|
|
391
402
|
|
|
392
403
|
--- File: ${s} ---
|
|
393
404
|
${i}`;e.lastTestOutput&&(a+=`
|
|
394
405
|
|
|
395
406
|
--- Recent Test Output ---
|
|
396
|
-
${e.lastTestOutput.slice(0,4e3)}`),e.messages.push({role:"user",content:a}),console.log(),await Ht(e)}async function
|
|
397
|
-
${T}Usage:${
|
|
398
|
-
`);return}let n=
|
|
399
|
-
${T}Error:${
|
|
407
|
+
${e.lastTestOutput.slice(0,4e3)}`),e.messages.push({role:"user",content:a}),console.log(),await Ht(e)}async function ws(e,t){let s=t.trim();if(!s){console.log(`
|
|
408
|
+
${T}Usage:${b} /heal <file-path>
|
|
409
|
+
`);return}let n=z.resolve(process.cwd(),s);if(!G.existsSync(n)){console.log(`
|
|
410
|
+
${T}Error:${b} File not found: ${s}
|
|
400
411
|
`);return}let o=e.projectContext.testCommand;if(!o){console.log(`
|
|
401
|
-
${T}Error:${
|
|
412
|
+
${T}Error:${b} No test command found. Set testCommand in .lisai.json
|
|
402
413
|
`);return}let i=ne(o),a=Je(o,s),r="";try{await oe(a,!1),console.log(`
|
|
403
414
|
\u2705 ${s} is already passing. Nothing to heal.
|
|
404
415
|
`),e.messages.push({role:"user",content:`[Heal Result] ${s} \u2014 already passing, no changes needed.`});return}catch(u){r=(u.stderr||"")+`
|
|
@@ -409,47 +420,47 @@ ${e.lastTestOutput.slice(0,4e3)}`),e.messages.push({role:"user",content:a}),cons
|
|
|
409
420
|
\u2705 Healed via ${u}.
|
|
410
421
|
`),e.messages.push({role:"user",content:`[Heal Result] ${s} \u2014 healed successfully via ${u} (${c.resolvedBy}).`})}else console.log(`
|
|
411
422
|
\u2717 Could not heal ${s} after all retries.
|
|
412
|
-
`),e.messages.push({role:"user",content:`[Heal Result] ${s} \u2014 failed to heal. Quarantined.`})}async function
|
|
413
|
-
${T}Usage:${
|
|
414
|
-
`);return}let n=
|
|
415
|
-
${T}Error:${
|
|
416
|
-
`);return}let o=
|
|
423
|
+
`),e.messages.push({role:"user",content:`[Heal Result] ${s} \u2014 failed to heal. Quarantined.`})}async function vs(e,t){let s=t.trim();if(!s){console.log(`
|
|
424
|
+
${T}Usage:${b} /generate <source-file-path>
|
|
425
|
+
`);return}let n=z.resolve(process.cwd(),s);if(!G.existsSync(n)){console.log(`
|
|
426
|
+
${T}Error:${b} File not found: ${s}
|
|
427
|
+
`);return}let o=G.readFileSync(n,"utf-8"),i=e.projectContext.testingFramework!=="none"?e.projectContext.testingFramework:void 0;console.log(`
|
|
417
428
|
Generating test for ${s}...
|
|
418
|
-
`);try{let a=await
|
|
429
|
+
`);try{let a=await ee(s,o,e.model,e.apiKey,i),r=z.extname(s),c=s.replace(r,`.spec${r}`),u=z.resolve(process.cwd(),c);G.writeFileSync(u,a,"utf-8"),console.log(` \u2705 Generated: ${c}
|
|
419
430
|
`),e.messages.push({role:"user",content:`[Generate Result] Test generated for ${s} \u2192 ${c}`})}catch(a){console.log(` \u2717 Generation failed: ${a.message}
|
|
420
|
-
`),e.messages.push({role:"user",content:`[Generate Result] Failed to generate test for ${s}: ${a.message}`})}}async function
|
|
421
|
-
${T}Error:${
|
|
431
|
+
`),e.messages.push({role:"user",content:`[Generate Result] Failed to generate test for ${s}: ${a.message}`})}}async function $s(e){let t=e.projectContext.testCommand;if(!t){console.log(`
|
|
432
|
+
${T}Error:${b} No test command found.
|
|
422
433
|
`);return}console.log(`
|
|
423
|
-
Running: ${He}${t}${
|
|
424
|
-
`+s,i=
|
|
434
|
+
Running: ${He}${t}${b}`);try{let{stdout:s,stderr:n}=await oe(t,!1),o=n+`
|
|
435
|
+
`+s,i=B(o);if(i){let a=i.total>0?Math.round(i.passed/i.total*100):0,r="\u2588".repeat(Math.round(a/5))+"\u2591".repeat(20-Math.round(a/5));console.log(` [${r}] ${a}% \u2705 ${i.passed} passed ${i.total} total
|
|
425
436
|
`)}else console.log(` \u2705 All tests passing.
|
|
426
437
|
`)}catch(s){let n=(s.stderr||"")+`
|
|
427
438
|
`+(s.stdout||"")+`
|
|
428
|
-
`+(s.message||""),o=
|
|
439
|
+
`+(s.message||""),o=B(n);if(o){let i=o.total>0?Math.round(o.passed/o.total*100):0,a="\u2588".repeat(Math.round(i/5))+"\u2591".repeat(20-Math.round(i/5));console.log(` [${a}] ${i}% ${o.passed} passed ${o.failed} failed ${o.total} total
|
|
429
440
|
`)}else console.log(` \u2717 Tests failing.
|
|
430
441
|
`)}}async function Ht(e){try{let t=(0,Ot.streamText)({model:pe(e.model,e.apiKey),messages:e.messages}),s="";for await(let n of t.textStream)process.stdout.write(n),s+=n;if(process.stdout.write(`
|
|
431
442
|
|
|
432
443
|
`),e.messages.push({role:"assistant",content:s}),e.messages.length>Mt){let n=e.messages[0];e.messages=[n,...e.messages.slice(-(Mt-2))]}}catch(t){let s=t.message||String(t);s.includes("API key")?console.log(`
|
|
433
|
-
${T}Error:${
|
|
444
|
+
${T}Error:${b} Invalid or missing API key for ${e.model}. Check your env vars.
|
|
434
445
|
`):console.log(`
|
|
435
|
-
${T}Error:${
|
|
436
|
-
`)}}async function
|
|
437
|
-
${He}Goodbye!${
|
|
438
|
-
`),t.close();return;case"/help":gs();break;case"/read":ms(e,a);break;case"/test":await fs(e,a);break;case"/explain":await
|
|
446
|
+
${T}Error:${b} ${s.slice(0,200)}
|
|
447
|
+
`)}}async function Ls(e){let t=Dt.createInterface({input:process.stdin,output:process.stdout,prompt:`${T}lisa>${b} `});t.prompt(),t.on("line",async s=>{let n=s.trim();if(!n){t.prompt();return}if(t.pause(),n.startsWith("/")){let o=n.indexOf(" "),i=o>0?n.slice(0,o).toLowerCase():n.toLowerCase(),a=o>0?n.slice(o+1):"";switch(i){case"/exit":case"/quit":console.log(`
|
|
448
|
+
${He}Goodbye!${b}
|
|
449
|
+
`),t.close();return;case"/help":gs();break;case"/read":ms(e,a);break;case"/write":hs(e,a);break;case"/test":await fs(e,a);break;case"/explain":await ys(e,a);break;case"/heal":await ws(e,a);break;case"/generate":await vs(e,a);break;case"/status":await $s(e);break;default:console.log(`
|
|
439
450
|
Unknown command: ${i}. Type /help for available commands.
|
|
440
|
-
`)}}else e.messages.push({role:"user",content:n}),console.log(),await Ht(e);t.resume(),t.prompt()}),t.on("close",()=>{process.exit(0)})}async function Gt(e,t,s,n){let o=us(),a={messages:[{role:"system",content:ps(o)}],model:e,apiKey:s,projectId:t,projectContext:o,lisaLlmConfig:n},r=o;console.log(` ${T}\u2554\u2550\u2557${
|
|
451
|
+
`)}}else e.messages.push({role:"user",content:n}),console.log(),await Ht(e);t.resume(),t.prompt()}),t.on("close",()=>{process.exit(0)})}async function Gt(e,t,s,n){let o=us(),a={messages:[{role:"system",content:ps(o)}],model:e,apiKey:s,projectId:t,projectContext:o,lisaLlmConfig:n},r=o;console.log(` ${T}\u2554\u2550\u2557${b} ${_t}Interactive Mode${b}`),console.log(` ${T}\u2551${b}${T}\u26A1${T}\u2551${b} Type /help for commands, /exit to quit`),console.log(` ${T}\u255A\u2550\u255D${b} ${He}Project: ${r.packageName} | Framework: ${r.testingFramework} | Model: ${e}${b}`),console.log(),await Ls(a)}var Wt=Bt();function le(){let e=Wt.version;console.log(`
|
|
441
452
|
\x1B[38;2;99;102;241m\u2554\u2550\u2557\x1B[0m \x1B[1mLisa.ai Agent\x1B[0m v${e}
|
|
442
453
|
\x1B[38;2;99;102;241m\u2551\x1B[38;2;129;140;248m\u26A1\x1B[38;2;99;102;241m\u2551\x1B[0m Autonomous CI/CD Platform
|
|
443
454
|
\x1B[38;2;99;102;241m\u255A\u2550\u255D\x1B[0m \x1B[2mheal \xB7 unit \xB7 e2e \xB7 integration \xB7 audit \xB7 chat\x1B[0m
|
|
444
455
|
`)}async function Fe(e){let t=5,s=e.model,n,o=M.load(process.cwd()),i=e.projectId||o?.projectId||"local";if(i!=="local"){let r=await je(i);if(!r.ok)r.reason==="not_found"?(console.error(`
|
|
445
456
|
[Lisa.ai Agent Error] Project '${i}' 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(`
|
|
446
457
|
[Lisa.ai Warning] Control Plane is unreachable. Continuing with local CLI defaults (model=${s}, maxRetries=${t}).`);else{let c=r.config;console.log(`[Lisa.ai Agent] Dynamic Config Loaded: Provider=${c.modelProvider}, MaxRetries=${c.maxRetries}`),s=c.modelProvider,t=c.maxRetries,n=c.apiKey,c.agentEnabled===!1&&(console.log("[Lisa.ai Agent] Agent is disabled by Control Plane for this project."),process.exit(1))}}o&&(M.applyEnvDefaults(o),o.maxRetries!==void 0&&(t=o.maxRetries),o.provider&&e.model==="gemini"&&(s=o.provider),M.printSummary(o)),t<5&&console.warn(`
|
|
447
|
-
[Lisa.ai Warning] maxRetries is ${t}. Consider increasing to 5+ for complex projects.`);let a=
|
|
448
|
-
[Lisa.ai Error] Invalid heal type '${e.type}'. Valid types: ${t.join(", ")}`),process.exit(1));let n=e.files?e.files.split(",").map(a=>a.trim()):void 0,o=e.ci===!0,i=await Fe(e);await ye(e.command,i.model,1,i.maxRetries,i.projectId,void 0,i.apiKey,s,n,o,i.lisaLlmConfig)});
|
|
458
|
+
[Lisa.ai Warning] maxRetries is ${t}. Consider increasing to 5+ for complex projects.`);let a=We(o?.lisaLlm);return a.enabled&&console.log(`[Lisa.ai Config] Lisa LLM enabled: ${a.model} @ ${a.baseUrl}`),{model:s,maxRetries:t,apiKey:n,projectId:i,lisaLlmConfig:a}}var Z=new qt.Command;Z.name("lisa-agent").description("Lisa.ai - Autonomous CI/CD Platform Worker Agent").version(Wt.version);Z.command("heal").description("Run tests and autonomously heal all failures via LLM + Memory Engine").option("-c, --command <type>","Test command to execute (auto-detected if omitted)").option("-m, --model <provider>","LLM provider to use (gemini, claude, openai)","gemini").option("-p, --project-id <id>","Control Plane Project ID").option("-t, --type <scope>","Test type to heal: unit (default), e2e, integration","unit").option("-f, --files <paths>","Target specific files (comma-separated, bypasses discovery)").option("--ci","CI mode: commit healed files, open PR, write GitHub Actions step summary").action(async e=>{le();let t=["unit","e2e","integration"],s=e.type;t.includes(s)||(console.error(`
|
|
459
|
+
[Lisa.ai Error] Invalid heal type '${e.type}'. Valid types: ${t.join(", ")}`),process.exit(1));let n=e.files?e.files.split(",").map(a=>a.trim()):void 0,o=e.ci===!0,i=await Fe(e);await ye(e.command,i.model,1,i.maxRetries,i.projectId,void 0,i.apiKey,s,n,o,i.lisaLlmConfig)});Z.command("unit").description("Generate missing unit test specs to reach 100% coverage (auto-detects framework)").option("-c, --command <type>","Test+coverage command to execute (auto-detected if omitted)").option("-m, --model <provider>","LLM provider to use (gemini, claude, openai)","gemini").option("-p, --project-id <id>","Control Plane Project ID").option("-f, --files <paths>","Target specific files (comma-separated, bypasses discovery)").action(async e=>{le();let t=e.files?e.files.split(",").map(n=>n.trim()):void 0,s=await Fe(e);await St(e.command,s.model,1,s.maxRetries,s.projectId,s.apiKey,t,s.lisaLlmConfig)});Z.command("e2e").description("Generate E2E tests via Playwright (auto-installs if none detected)").option("-c, --command <type>","E2E command to execute (auto-detected if omitted)").option("-m, --model <provider>","LLM provider to use (gemini, claude, openai)","gemini").option("-p, --project-id <id>","Control Plane Project ID").option("-f, --files <paths>","Target specific files (comma-separated, bypasses discovery)").action(async e=>{le();let t=e.files?e.files.split(",").map(n=>n.trim()):void 0,s=await Fe(e);await Ft(e.command,s.model,1,s.maxRetries,s.projectId,s.apiKey,t)});Z.command("integration").description("Generate integration tests for module seams (component+service, API+DB)").option("-c, --command <type>","Test command to execute (auto-detected if omitted)").option("-m, --model <provider>","LLM provider to use (gemini, claude, openai)","gemini").option("-p, --project-id <id>","Control Plane Project ID").option("-f, --files <paths>","Target specific files (comma-separated, bypasses discovery)").action(async e=>{le();let t=e.files?e.files.split(",").map(n=>n.trim()):void 0,s=await Fe(e);await Et(e.command,s.model,1,s.maxRetries,s.projectId,s.apiKey,t)});Z.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 e=>{le();let t=e.model,s,n=M.load(process.cwd()),o=e.projectId||n?.projectId||"local";if(o!=="local"){let r=await je(o);r.ok?(t=r.config.modelProvider,s=r.config.apiKey,console.log(`[Lisa.ai Diagnose] Config fetched from Control Plane. Provider=${t}`)):console.warn(`[Lisa.ai Diagnose] Control Plane ${r.reason}. Using local env for API key.`)}let a={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"}[t]||t;console.log(`
|
|
449
460
|
[Lisa.ai Diagnose] Testing ${t} \u2192 model ID: ${a}`);try{let r=await ge("Reply with exactly: LISA_OK",t,s);console.log(`
|
|
450
461
|
[Lisa.ai Diagnose] ${t} is working correctly.`),console.log(` Model response: "${r}"`)}catch(r){console.error(`
|
|
451
462
|
[Lisa.ai Diagnose] ${t} call FAILED.`),console.error(` Error: ${r.message}`),r.cause&&console.error(` Cause: ${r.cause?.message??r.cause}`),console.error(`
|
|
452
|
-
Common causes:`),console.error(" - API key is invalid, expired, or has no credits"),console.error(" - Model ID is deprecated (current: claude-sonnet-4-6, gemini-2.0-flash, gpt-4o-mini)"),console.error(" - Rate limit exceeded \u2014 wait a minute and retry"),console.error(" - Network/proxy issue blocking api.anthropic.com"),process.exit(1)}});
|
|
463
|
+
Common causes:`),console.error(" - API key is invalid, expired, or has no credits"),console.error(" - Model ID is deprecated (current: claude-sonnet-4-6, gemini-2.0-flash, gpt-4o-mini)"),console.error(" - Rate limit exceeded \u2014 wait a minute and retry"),console.error(" - Network/proxy issue blocking api.anthropic.com"),process.exit(1)}});Z.command("init").description("Interactively create a .lisai.json config file in the current project").option("--force","Overwrite existing .lisai.json if it exists").action(async e=>{let t=be.resolve(process.cwd(),de);re.existsSync(t)&&!e.force&&(console.error(`
|
|
453
464
|
[Lisa.ai Init] ${de} already exists. Use --force to overwrite.`),process.exit(1));let s=Yt.createInterface({input:process.stdin,output:process.stdout}),n=(L,m="")=>new Promise(d=>s.question(m?`${L} [${m}]: `:`${L}: `,l=>d(l.trim()||m)));console.log(`
|
|
454
465
|
Lisa.ai Project Setup \u2014 press Enter to accept the default shown in [ ]
|
|
455
466
|
`);let o=await n("Control Plane project ID (leave empty for local-only mode)",""),i=await n("LLM provider (gemini / claude / openai)","gemini"),a=await n("Testing framework (jest / vitest / mocha / karma / jasmine \u2014 leave empty to auto-detect)",""),r=await n("E2E framework (playwright / cypress \u2014 leave empty for Playwright default)",""),c=await n("Test types to generate (unit / integration / e2e \u2014 comma-separated)","unit"),u=await n("Test command (leave empty for auto-discovery)",""),p=await n("E2E command (leave empty for auto-discovery)",""),$=await n("Max heal retries","5");s.close();let g={...o?{projectId:o}:{},provider:i,testTypes:c.split(",").map(L=>L.trim()).filter(Boolean),maxRetries:parseInt($,10)||5,skipFiles:[],skipDirs:[],skipPaths:[]};a&&(g.testingFramework=a),r&&(g.e2eFramework=r),u&&(g.testCommand=u),p&&(g.e2eCommand=p),re.writeFileSync(t,JSON.stringify(g,null,2)+`
|
|
@@ -457,7 +468,7 @@ ${e.lastTestOutput.slice(0,4e3)}`),e.messages.push({role:"user",content:a}),cons
|
|
|
457
468
|
Created ${de}:
|
|
458
469
|
`),console.log(JSON.stringify(g,null,2)),console.log(`
|
|
459
470
|
Tip: add "model" to pin a specific model ID (e.g. "claude-sonnet-4-6").`),console.log(` Edit skipFiles / skipDirs / skipPaths to exclude files from test analysis.
|
|
460
|
-
`)});function
|
|
471
|
+
`)});function Cs(e,t){return`# Lisa.ai Auto-Heal Workflow
|
|
461
472
|
# Generated by: lisa-agent init-ci
|
|
462
473
|
# Docs: https://www.npmjs.com/package/@lisa.ai/agent
|
|
463
474
|
|
|
@@ -498,9 +509,9 @@ jobs:
|
|
|
498
509
|
${t}: \${{ secrets.${t} }}
|
|
499
510
|
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
500
511
|
run: lisa-agent heal --model ${e} --ci
|
|
501
|
-
`}
|
|
502
|
-
[Lisa.ai Init-CI] .github/workflows/lisa-heal.yml already exists. Use --force to overwrite.`),process.exit(1));let n={gemini:"GOOGLE_GENERATIVE_AI_API_KEY",claude:"ANTHROPIC_API_KEY",openai:"OPENAI_API_KEY"},o=e.model||"gemini",i=n[o]||"GOOGLE_GENERATIVE_AI_API_KEY",a=
|
|
512
|
+
`}Z.command("init-ci").description("Generate a GitHub Actions workflow file for Lisa.ai CI/CD healing").option("--force","Overwrite existing workflow file").option("-m, --model <provider>","LLM provider for the workflow (gemini, claude, openai)","gemini").action(async e=>{le();let t=be.resolve(process.cwd(),".github","workflows"),s=be.join(t,"lisa-heal.yml");re.existsSync(s)&&!e.force&&(console.error(`
|
|
513
|
+
[Lisa.ai Init-CI] .github/workflows/lisa-heal.yml already exists. Use --force to overwrite.`),process.exit(1));let n={gemini:"GOOGLE_GENERATIVE_AI_API_KEY",claude:"ANTHROPIC_API_KEY",openai:"OPENAI_API_KEY"},o=e.model||"gemini",i=n[o]||"GOOGLE_GENERATIVE_AI_API_KEY",a=Cs(o,i);re.mkdirSync(t,{recursive:!0}),re.writeFileSync(s,a,"utf-8"),console.log(`
|
|
503
514
|
Created: .github/workflows/lisa-heal.yml`),console.log(`
|
|
504
515
|
Required repository secret: ${i}`),console.log(" Set it in: GitHub repo > Settings > Secrets and variables > Actions"),console.log(`
|
|
505
516
|
The workflow will:`),console.log(" 1. Run on every push to main/master/develop"),console.log(" 2. Install dependencies + run lisa-agent heal --ci"),console.log(" 3. If tests fail and Lisa heals them, open a PR automatically"),console.log(` 4. If all tests pass or nothing can be healed, report status
|
|
506
|
-
`)});
|
|
517
|
+
`)});Z.command("audit").description("Scan for npm vulnerabilities and auto-fix where safe; consult LLM for manual fixes").option("-m, --model <provider>","LLM provider to use (gemini, claude, openai)","gemini").option("-p, --project-id <id>","Control Plane Project ID").action(async e=>{le();let t=e.model,s,n=M.load(process.cwd()),o=e.projectId||n?.projectId||"local";if(o!=="local"){let i=await je(o);i.ok?(t=i.config.modelProvider,s=i.config.apiKey,console.log(`[Lisa.ai Audit] Config fetched from Control Plane. Provider=${t}`)):console.warn(`[Lisa.ai Audit] Control Plane ${i.reason}. Using local env for API key.`)}await Rt(t,o,s)});Z.command("chat").description("Interactive chat \u2014 ask questions, heal files, run tests").option("-m, --model <provider>","LLM provider to use (gemini, claude, openai)","gemini").option("-p, --project-id <id>","Control Plane Project ID").action(async e=>{le();let t=await Fe(e);await Gt(t.model,t.projectId,t.apiKey,t.lisaLlmConfig)});Z.parse(process.argv);
|