glenn-code 1.0.19 → 1.0.21
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 +1 -1
- package/dist/adapters/mcps/stdio-server.js +58 -73
- package/dist/adapters/signalr/index.js +49 -35
- package/dist/cli.js +49 -35
- package/dist/core.js +25 -12
- package/dist/index.js +48 -35
- package/package.json +1 -1
package/dist/core.js
CHANGED
|
@@ -15,14 +15,14 @@ Workflow:
|
|
|
15
15
|
2. save_specification(name: "User Authentication", filePath: "/tmp/user-auth-spec.md")`,{name:se.string().describe("Specification name (e.g., 'User Authentication')"),filePath:se.string().describe("Path to the file containing the specification content (written via Write tool)")},async e=>{if(!D)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let t=De.resolve(e.filePath);if(!oe.existsSync(t))return{content:[{type:"text",text:`\u274C File not found: ${e.filePath}. Use Write tool first to create the file.`}]};let n=oe.readFileSync(t,"utf-8"),a=me(e.name);return await D.saveSpecification(a,n),{content:[{type:"text",text:`\u2705 Saved specification: ${a}.spec.md`}]}}),Dt=re("read_specification","Read the content of a specification.",{name:se.string().describe("Name of the specification to read")},async e=>{if(!D)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let t=me(e.name),n=await D.getSpecification(t);return n?{content:[{type:"text",text:n}]}:{content:[{type:"text",text:`\u274C Specification "${e.name}" not found.`}]}}),Ot=re("list_specifications","List all specifications.",{},async()=>{if(!D)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let e=await D.listSpecifications();return e.length===0?{content:[{type:"text",text:"\u{1F4CB} No specifications found."}]}:{content:[{type:"text",text:`\u{1F4CB} Specifications:
|
|
16
16
|
|
|
17
17
|
${e.map(n=>`- ${n.name} (${n.slug}.spec.md)`).join(`
|
|
18
|
-
`)}`}]}}),Ft=re("delete_specification","Delete a specification.",{name:se.string().describe("Name of the specification to delete")},async e=>{if(!D)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let t=me(e.name);return await D.deleteSpecification(t)?{content:[{type:"text",text:`\u{1F5D1}\uFE0F Deleted: ${t}.spec.md`}]}:{content:[{type:"text",text:`\u274C Specification "${e.name}" not found.`}]}});import{tool as M}from"@anthropic-ai/claude-agent-sdk";import{z as
|
|
18
|
+
`)}`}]}}),Ft=re("delete_specification","Delete a specification.",{name:se.string().describe("Name of the specification to delete")},async e=>{if(!D)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let t=me(e.name);return await D.deleteSpecification(t)?{content:[{type:"text",text:`\u{1F5D1}\uFE0F Deleted: ${t}.spec.md`}]}:{content:[{type:"text",text:`\u274C Specification "${e.name}" not found.`}]}});import{tool as M}from"@anthropic-ai/claude-agent-sdk";import{z as V}from"zod";var y=null;function Fe(e){y=e}var Ne=M("restart_services",`Rebuild and restart services. Call this after making backend changes (.cs files) to apply them.
|
|
19
19
|
|
|
20
20
|
Parameters:
|
|
21
21
|
- target: "backend" (default) | "frontend" | "both"
|
|
22
22
|
|
|
23
23
|
Use target="backend" after .cs file changes (faster, only restarts .NET).
|
|
24
24
|
Use target="frontend" if Vite is stuck (rare, HMR usually works).
|
|
25
|
-
Use target="both" if unsure or both need restart.`,{target:
|
|
25
|
+
Use target="both" if unsure or both need restart.`,{target:V.enum(["backend","frontend","both"]).default("backend").describe("Which service to restart")},async e=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=e.target||"backend";console.log(`[MCP] restart_services called (target: ${t})`);let n=await y.restartServices(t);if(!n.success){let s=[];return n.backendError&&s.push(`Backend: ${n.backendError}`),n.frontendError&&s.push(`Frontend: ${n.frontendError}`),{content:[{type:"text",text:`\u274C Restart failed:
|
|
26
26
|
${s.join(`
|
|
27
27
|
|
|
28
28
|
`)}`}]}}return{content:[{type:"text",text:`\u2705 ${t==="both"?"Backend and frontend":t==="backend"?"Backend":"Frontend"} restarted successfully.`}]}}),Ue=M("stop_services",`Stop running services. Call this BEFORE running scaffold to prevent port conflicts and ensure clean migrations.
|
|
@@ -30,12 +30,12 @@ ${s.join(`
|
|
|
30
30
|
Parameters:
|
|
31
31
|
- target: "backend" (default) | "frontend" | "both"
|
|
32
32
|
|
|
33
|
-
Use target="backend" before running scaffold (required for migrations).`,{target:
|
|
33
|
+
Use target="backend" before running scaffold (required for migrations).`,{target:V.enum(["backend","frontend","both"]).default("backend").describe("Which service to stop")},async e=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=e.target||"backend";console.log(`[MCP] stop_services called (target: ${t})`);let n=t==="backend"||t==="both",a=t==="frontend"||t==="both";n&&await y.stopBackend(),a&&await y.stopFrontend();let s=5e3,o=Date.now(),r=!n,g=!a;for(;Date.now()-o<s;){let u=await y.checkHealth();if(n&&!u.api&&(r=!0),a&&!u.web&&(g=!0),r&&g)break;await new Promise(C=>setTimeout(C,500))}let _=t==="both"?"Backend and frontend":t==="backend"?"Backend":"Frontend";return{content:[{type:"text",text:r&&g?`\u2705 ${_} stopped successfully.`:`\u26A0\uFE0F ${_} stop requested but health check still shows running. Proceeding anyway.`}]}}),Le=M("start_services",`Start services. Call this AFTER running scaffold to bring services back up.
|
|
34
34
|
|
|
35
35
|
Parameters:
|
|
36
36
|
- target: "backend" (default) | "frontend" | "both"
|
|
37
37
|
|
|
38
|
-
Use target="backend" after running scaffold.`,{target:
|
|
38
|
+
Use target="backend" after running scaffold.`,{target:V.enum(["backend","frontend","both"]).default("backend").describe("Which service to start")},async e=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=e.target||"backend";console.log(`[MCP] start_services called (target: ${t})`);let n=t==="backend"||t==="both",a=t==="frontend"||t==="both";n&&await y.startBackend(),a&&await y.startFrontend();let s=await y.waitForHealth(15e3),o=!n||s.api,r=!a||s.web,g=o&&r,_=t==="both"?"Backend and frontend":t==="backend"?"Backend":"Frontend";if(!g){let f=[];return n&&!s.api&&f.push("Backend failed to start"),a&&!s.web&&f.push("Frontend failed to start"),{content:[{type:"text",text:`\u274C ${_} failed to start: ${f.join(", ")}`}]}}return{content:[{type:"text",text:`\u2705 ${_} started successfully.`}]}}),Me=M("check_service_health",`Check if services are running and healthy. Returns current status of backend API and frontend.
|
|
39
39
|
Note: This only checks if ports are responding, NOT build/type errors. Use check_backend_build or check_frontend_types for that.`,{},async()=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let e=await y.checkHealth();return{content:[{type:"text",text:`Service Health:
|
|
40
40
|
\u{1F5A5}\uFE0F Backend API: ${e.api?"\u2705 running":"\u274C not running"}
|
|
41
41
|
\u{1F310} Frontend: ${e.web?"\u2705 running":"\u274C not running"}`}]}}),je=M("check_backend_build","Run dotnet build to check for C# compilation errors. Use this after fixing backend code to verify the fix works.",{},async()=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};console.log("[MCP] check_backend_build called");let e=await y.checkBackendBuild();return e.success?{content:[{type:"text",text:"\u2705 Backend build succeeded - no errors"}]}:{content:[{type:"text",text:`\u274C Backend build failed:
|
|
@@ -46,18 +46,18 @@ ${e.errors}`}]}}),Mt=M("tail_service_log",`Get the last N lines of service logs.
|
|
|
46
46
|
|
|
47
47
|
Parameters:
|
|
48
48
|
- target: "backend" | "frontend"
|
|
49
|
-
- lines: number (default 50)`,{target:
|
|
49
|
+
- lines: number (default 50)`,{target:V.enum(["backend","frontend"]).describe("Which log to read"),lines:V.number().default(50).describe("Number of lines to read")},async e=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=await y.tailLogs(e.target,e.lines);return{content:[{type:"text",text:`Last ${e.lines} lines of ${e.target} log:
|
|
50
50
|
|
|
51
51
|
${t.join(`
|
|
52
52
|
`)}`}]}}),jt=M("check_swagger_endpoints",`Search for endpoints in the generated swagger.json. Use this to verify that new backend endpoints are correctly exposed.
|
|
53
53
|
|
|
54
54
|
Parameters:
|
|
55
|
-
- pattern: string (e.g., "users", "api/reports")`,{pattern:
|
|
55
|
+
- pattern: string (e.g., "users", "api/reports")`,{pattern:V.string().describe("Text to search for in endpoint paths")},async e=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=await y.checkSwaggerEndpoints(e.pattern);return t.foundEndpoints.length===0?{content:[{type:"text",text:`\u274C No endpoints found matching "${e.pattern}" (Total endpoints: ${t.totalEndpoints})`}]}:{content:[{type:"text",text:`\u2705 Found ${t.foundEndpoints.length} matching endpoints:
|
|
56
56
|
|
|
57
57
|
${t.foundEndpoints.join(`
|
|
58
|
-
`)}`}]}});import*as P from"fs";import*as
|
|
59
|
-
`,"utf-8")},async reportProgress(n,a,s){let o=
|
|
60
|
-
`,"utf-8")}}}function ye(e){let t=
|
|
58
|
+
`)}`}]}});import*as P from"fs";import*as Q from"path";var qe="/tmp";function he(e){let t=e?Q.join(e,".sdd","insights"):Q.join(process.cwd(),".sdd","insights");return{async reportInsight(n,a){P.mkdirSync(t,{recursive:!0});let s=a.type==="learning"?"learnings.jsonl":"bugs.jsonl",o=Q.join(t,s);P.appendFileSync(o,JSON.stringify(a)+`
|
|
59
|
+
`,"utf-8")},async reportProgress(n,a,s){let o=Q.join(qe,`agent-progress-${n}.json`),r={timestamp:new Date().toISOString(),event:a,data:s};P.appendFileSync(o,JSON.stringify(r)+`
|
|
60
|
+
`,"utf-8")}}}function ye(e){let t=Q.join(qe,`agent-questions-${e}.json`);if(!P.existsSync(t))return null;try{let n=P.readFileSync(t,"utf-8");return P.unlinkSync(t),JSON.parse(n)}catch{return null}}import*as w from"fs";import*as ae from"path";function be(e){let t=ae.join(e,".sdd","specifications");function n(){w.existsSync(t)||w.mkdirSync(t,{recursive:!0})}function a(s){return ae.join(t,`${s}.spec.md`)}return{async saveSpecification(s,o){n(),w.writeFileSync(a(s),o,"utf-8")},async getSpecification(s){n();let o=a(s);return w.existsSync(o)?w.readFileSync(o,"utf-8"):null},async listSpecifications(){return n(),w.readdirSync(t).filter(o=>o.endsWith(".spec.md")).map(o=>{let r=w.readFileSync(ae.join(t,o),"utf-8"),g=r.match(/name: "([^"]+)"/),_=r.match(/status: (\w+)/),f=r.match(/version: "([^"]+)"/);return{slug:o.replace(".spec.md",""),name:g?.[1]||o,status:_?.[1]||"unknown",version:f?.[1]||"1.0.0"}})},async deleteSpecification(s){n();let o=a(s);return w.existsSync(o)?(w.unlinkSync(o),!0):!1},async specificationExists(s){return n(),w.existsSync(a(s))}}}function _e(){return pt({name:"agent-insights",version:"1.0.0",tools:[Ne,Ue,Le,Me,je,Be]})}import{query as Tt}from"@anthropic-ai/claude-agent-sdk";var ke={description:"Delegate to this agent when scaffolding new entities or creating CRUD features. This agent handles entity generation using the scaffold CLI with multi-entity support.",model:"inherit",prompt:`You are a scaffolding specialist. Your ONLY job is:
|
|
61
61
|
1. Read the spec if referenced in the card description
|
|
62
62
|
2. Check existing features (quick ls)
|
|
63
63
|
3. Write ALL entities to ONE /tmp/scaffold.json (big bang, no phasing!)
|
|
@@ -834,7 +834,7 @@ Before saving, verify your context:
|
|
|
834
834
|
5. **Keep it scannable** - Use bullets, clear sections
|
|
835
835
|
6. **Update, don't replace** - If context exists, improve it rather than starting fresh`};import*as z from"fs";import*as $e from"path";var de=class{buffer=[];flushInterval=null;sessionId=null;outputDir;flushIntervalMs;constructor(t){this.outputDir=t.outputDir||"/tmp/agent-debug",this.flushIntervalMs=t.flushIntervalMs||3e3,t.enabled&&(this.ensureOutputDir(),this.startFlushInterval())}ensureOutputDir(){z.existsSync(this.outputDir)||z.mkdirSync(this.outputDir,{recursive:!0})}startFlushInterval(){this.flushInterval=setInterval(()=>{this.flush()},this.flushIntervalMs)}setSessionId(t){this.sessionId=t}log(t){let a={timestamp:new Date().toISOString(),message:t};this.buffer.push(JSON.stringify(a,null,2)+`
|
|
836
836
|
---
|
|
837
|
-
`)}getLogFilePath(){let n=(this.sessionId||"unknown").replace(/[^a-zA-Z0-9-]/g,"_").slice(0,50),a=new Date().toISOString().split("T")[0];return $e.join(this.outputDir,`session-${a}-${n}.log`)}flush(){if(this.buffer.length===0)return;let t=this.buffer.join("");this.buffer=[];let n=this.getLogFilePath();z.appendFileSync(n,t)}stop(){this.flushInterval&&(clearInterval(this.flushInterval),this.flushInterval=null),this.flush()}};function We(e){return e?typeof e=="boolean"?e?new de({enabled:!0}):null:e.enabled?new de(e):null:null}import{z as c}from"zod";import*as J from"fs/promises";import*as E from"path";var Ge=c.object({port:c.number(),startCommand:c.string(),buildCommand:c.string().optional(),typecheckCommand:c.string().optional(),logFile:c.string().optional(),healthEndpoint:c.string().optional(),extensions:c.array(c.string())}),ut=c.object({port:c.number(),type:c.enum(["postgres","mysql","sqlite","mongodb"])}),ft=c.object({command:c.string(),schemaPath:c.string().optional()}),gt=c.object({email:c.string().optional(),name:c.string().optional(),commitPrefix:c.string().optional()}),mt=c.object({enabled:c.boolean(),port:c.number().optional()}),ee=c.object({version:c.literal("1.0"),paths:c.object({workspace:c.string(),backend:c.string().optional(),frontend:c.string().optional(),features:c.object({backend:c.string().optional(),frontend:c.string().optional()}).optional()}),services:c.object({backend:Ge.optional(),frontend:Ge.optional(),database:ut.optional()}).optional(),scaffold:ft.optional(),git:gt.optional(),techStack:c.object({backend:c.array(c.string()).optional(),frontend:c.array(c.string()).optional(),patterns:c.array(c.string()).optional()}).optional(),tunnel:mt.optional()});function O(e){return{version:"1.0",paths:{workspace:e||process.env.WORKSPACE_DIR||"/workspace",backend:"packages/dotnet-api",frontend:"packages/backoffice-web",features:{backend:"Source/Features",frontend:"src/applications/super-admin/features"}},services:{backend:{port:5338,startCommand:"dotnet run",buildCommand:"dotnet build",typecheckCommand:"dotnet build --no-restore",logFile:"/tmp/api.log",healthEndpoint:"http://localhost:5338",extensions:[".cs",".csproj"]},frontend:{port:5173,startCommand:"npm run dev",typecheckCommand:"npx tsc -p tsconfig.app.json --noEmit",logFile:"/tmp/web.log",healthEndpoint:"http://localhost:5173",extensions:[".ts",".tsx",".json"]},database:{port:43594,type:"postgres"}},scaffold:{command:"scaffold --schema /tmp/scaffold.json --output /workspace --force --full",schemaPath:"/tmp/scaffold.json"},git:{email:"agent@dotnetmentor.se",name:"Agent",commitPrefix:"[agent]"},techStack:{backend:[".NET 9","PostgreSQL","MediatR (CQRS)","EF Core"],frontend:["React 19","TypeScript","Vite","MUI","TanStack Query"],patterns:["Vertical slices","CQRS","Soft delete","Timestamps"]},tunnel:{enabled:!0,port:5173}}}var He="project-config.json",Ye=".sdd";async function Ke(e){let{workspaceDir:t,providedConfig:n,requireConfig:a}=e;if(n)return{config:ee.parse(n),source:"provided"};let s=E.join(t,Ye,He);try{let o=await J.readFile(s,"utf-8"),r=JSON.parse(o);return{config:ee.parse(r),source:"file",configPath:s}}catch(o){o instanceof Error&&"code"in o&&o.code!=="ENOENT"&&console.warn(`[Config] Warning: Invalid project-config.json at ${s}:`,o instanceof c.ZodError?o.errors:o.message)}if(a)throw new Error(`No project configuration found at ${s} and requireConfig is true`);return{config:O(t),source:"default"}}async function
|
|
837
|
+
`)}getLogFilePath(){let n=(this.sessionId||"unknown").replace(/[^a-zA-Z0-9-]/g,"_").slice(0,50),a=new Date().toISOString().split("T")[0];return $e.join(this.outputDir,`session-${a}-${n}.log`)}flush(){if(this.buffer.length===0)return;let t=this.buffer.join("");this.buffer=[];let n=this.getLogFilePath();z.appendFileSync(n,t)}stop(){this.flushInterval&&(clearInterval(this.flushInterval),this.flushInterval=null),this.flush()}};function We(e){return e?typeof e=="boolean"?e?new de({enabled:!0}):null:e.enabled?new de(e):null:null}import{z as c}from"zod";import*as J from"fs/promises";import*as E from"path";var Ge=c.object({port:c.number(),startCommand:c.string(),buildCommand:c.string().optional(),typecheckCommand:c.string().optional(),logFile:c.string().optional(),healthEndpoint:c.string().optional(),extensions:c.array(c.string())}),ut=c.object({port:c.number(),type:c.enum(["postgres","mysql","sqlite","mongodb"])}),ft=c.object({command:c.string(),schemaPath:c.string().optional()}),gt=c.object({email:c.string().optional(),name:c.string().optional(),commitPrefix:c.string().optional()}),mt=c.object({enabled:c.boolean(),port:c.number().optional()}),ee=c.object({version:c.literal("1.0"),paths:c.object({workspace:c.string(),backend:c.string().optional(),frontend:c.string().optional(),features:c.object({backend:c.string().optional(),frontend:c.string().optional()}).optional()}),services:c.object({backend:Ge.optional(),frontend:Ge.optional(),database:ut.optional()}).optional(),scaffold:ft.optional(),git:gt.optional(),techStack:c.object({backend:c.array(c.string()).optional(),frontend:c.array(c.string()).optional(),patterns:c.array(c.string()).optional()}).optional(),tunnel:mt.optional()});function O(e){return{version:"1.0",paths:{workspace:e||process.env.WORKSPACE_DIR||"/workspace",backend:"packages/dotnet-api",frontend:"packages/backoffice-web",features:{backend:"Source/Features",frontend:"src/applications/super-admin/features"}},services:{backend:{port:5338,startCommand:"dotnet run",buildCommand:"dotnet build",typecheckCommand:"dotnet build --no-restore",logFile:"/tmp/api.log",healthEndpoint:"http://localhost:5338",extensions:[".cs",".csproj"]},frontend:{port:5173,startCommand:"npm run dev",typecheckCommand:"npx tsc -p tsconfig.app.json --noEmit",logFile:"/tmp/web.log",healthEndpoint:"http://localhost:5173",extensions:[".ts",".tsx",".json"]},database:{port:43594,type:"postgres"}},scaffold:{command:"scaffold --schema /tmp/scaffold.json --output /workspace --force --full",schemaPath:"/tmp/scaffold.json"},git:{email:"agent@dotnetmentor.se",name:"Agent",commitPrefix:"[agent]"},techStack:{backend:[".NET 9","PostgreSQL","MediatR (CQRS)","EF Core"],frontend:["React 19","TypeScript","Vite","MUI","TanStack Query"],patterns:["Vertical slices","CQRS","Soft delete","Timestamps"]},tunnel:{enabled:!0,port:5173}}}var He="project-config.json",Ye=".sdd";async function Ke(e){let{workspaceDir:t,providedConfig:n,requireConfig:a}=e;if(n)return{config:ee.parse(n),source:"provided"};let s=E.join(t,Ye,He);try{let o=await J.readFile(s,"utf-8"),r=JSON.parse(o);return{config:ee.parse(r),source:"file",configPath:s}}catch(o){o instanceof Error&&"code"in o&&o.code!=="ENOENT"&&console.warn(`[Config] Warning: Invalid project-config.json at ${s}:`,o instanceof c.ZodError?o.errors:o.message)}if(a)throw new Error(`No project configuration found at ${s} and requireConfig is true`);return{config:O(t),source:"default"}}async function Ve(e,t){let n=E.join(e,Ye),a=E.join(n,He);await J.mkdir(n,{recursive:!0});let s=ee.parse(t);return await J.writeFile(a,JSON.stringify(s,null,2),"utf-8"),a}function pe(e,t){if(t)return E.isAbsolute(t)?t:E.join(e.paths.workspace,t)}function F(e){return pe(e,e.paths.backend)}function N(e){return pe(e,e.paths.frontend)}function te(e){let t=F(e);if(!(!t||!e.paths.features?.backend))return E.join(t,e.paths.features.backend)}function X(e){let t=N(e);if(!(!t||!e.paths.features?.frontend))return E.join(t,e.paths.features.frontend)}function Qe(e,t){if(!e.services?.backend?.extensions)return!1;let n=E.extname(t).toLowerCase();return e.services.backend.extensions.includes(n)}function ze(e,t){if(!e.services?.frontend?.extensions)return!1;let n=E.extname(t).toLowerCase();return e.services.frontend.extensions.includes(n)}function ue(e){let t=[];return e.techStack?.backend?.length&&t.push(`**Backend:** ${e.techStack.backend.join(", ")}`),e.techStack?.frontend?.length&&t.push(`**Frontend:** ${e.techStack.frontend.join(", ")}`),e.techStack?.patterns?.length&&t.push(`**Patterns:** ${e.techStack.patterns.join(", ")}`),t.join(`
|
|
838
838
|
`)}function Je(e){let t=[];return e.paths.backend&&t.push(`- Backend: ${F(e)}`),e.paths.frontend&&t.push(`- Frontend: ${N(e)}`),e.paths.features?.backend&&t.push(`- Backend Features: ${te(e)}`),e.paths.features?.frontend&&t.push(`- Frontend Features: ${X(e)}`),t.join(`
|
|
839
839
|
`)}function fe(e){let t=[];return e.services?.backend&&t.push(`| Backend | ${F(e)} | ${e.services.backend.port} | ${e.services.backend.logFile||"N/A"} |`),e.services?.frontend&&t.push(`| Frontend | ${N(e)} | ${e.services.frontend.port} | ${e.services.frontend.logFile||"N/A"} |`),e.services?.database&&t.push(`| Database (${e.services.database.type}) | localhost | ${e.services.database.port} | - |`),t.length===0?"":`| Service | Location | Port | Logs |
|
|
840
840
|
|---------|----------|------|------|
|
|
@@ -1017,6 +1017,19 @@ You know that overengineering is the enemy of good software. You are pragmatic a
|
|
|
1017
1017
|
|
|
1018
1018
|
**Your users may be non-technical.** They have ideas, not code. You are their bridge to magic.
|
|
1019
1019
|
|
|
1020
|
+
## \u{1F30D} Localization
|
|
1021
|
+
|
|
1022
|
+
**Match the user's language in generated UI text.** If the user prompts in Swedish, German, Spanish, etc., generate UI labels, placeholders, error messages, and button text in that language.
|
|
1023
|
+
|
|
1024
|
+
**Swedish special characters:** When generating Swedish text, ALWAYS use proper characters:
|
|
1025
|
+
- \xE5 (not a)
|
|
1026
|
+
- \xE4 (not a)
|
|
1027
|
+
- \xF6 (not o)
|
|
1028
|
+
|
|
1029
|
+
Examples: "L\xE4gg till", "\xC4ndra", "V\xE4lj anv\xE4ndare", "F\xF6rnamn", "Efternamn", "Spara \xE4ndringar"
|
|
1030
|
+
|
|
1031
|
+
**Code stays in English:** Variable names, function names, and code comments should remain in English for maintainability.
|
|
1032
|
+
|
|
1020
1033
|
${_}
|
|
1021
1034
|
|
|
1022
1035
|
${f}
|
|
@@ -1337,7 +1350,7 @@ You have access to **Playwright MCP** tools to verify the UI works correctly aft
|
|
|
1337
1350
|
- **Multi-tab apps**: Use \`browser_tabs\` to manage multiple windows/tabs in legacy applications
|
|
1338
1351
|
- **Large snapshots**: For pages with huge dropdowns (e.g., country lists), use \`browser_evaluate\` to collapse them first
|
|
1339
1352
|
- ${t?"In local mode, the browser window will be visible to you":"In container mode, browser runs headless"}
|
|
1340
|
-
</ui-verification>`}function Ze(e){return e&&e.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,"\uFFFD")}var vt=null;function et(){return Ze(vt)}import*as W from"path";import*as Te from"fs";import{fileURLToPath as Ct}from"url";var Et=_e(),ve=W.dirname(Ct(import.meta.url)),tt=[W.resolve(ve,"../mcps/stdio-server.js"),W.resolve(ve,"./adapters/mcps/stdio-server.js")],ne=tt.find(e=>Te.existsSync(e))||tt[0];console.log("[MCP] Stdio server path resolved:",ne);console.log("[MCP] __dirname:",ve);console.log("[MCP] File exists:",Te.existsSync(ne));async function nt(e,t={}){let{model:n,sessionId:a=null,projectId:s=null,apiUrl:o=null,callbacks:r={},debugLog:g,abortController:_,debugMode:f=!1,isLocal:u=process.env.LOCAL_MODE==="true",projectConfig:C=O(process.env.WORKSPACE_DIR||process.cwd()),useDefaultStack:R=!0}=t,B=We(g),q=new Set,Y=a||"",K,$,T,U=!1,Z=!1,I=process.env.WORKSPACE_DIR||process.cwd();console.log("[MCP] Configuring agent-planning stdio server:"),console.log("[MCP] Command: node"),console.log("[MCP] Args:",[ne]),console.log("[MCP] Env: API_URL=",process.env.API_URL||"http://localhost:5338",", PROJECT_ID=",s||process.env.PROJECT_ID||"",", PROJECT_KEY=",process.env.PROJECT_KEY?"***set***":"(not set)",", WORKSPACE_DIR=",I),console.log("[MCP] File exists check:",ne);try{for await(let d of Tt({prompt:e,options:{abortController:_,cwd:I,resume:a??void 0,allowedTools:["Bash","Read","Write","Edit","MultiEdit","Glob","Grep","LS","WebFetch","NotebookRead","NotebookEdit","Skill","mcp__agent-insights__check_backend_build","mcp__agent-insights__check_frontend_types","mcp__agent-insights__check_service_health",...u?[]:["mcp__agent-insights__stop_services","mcp__agent-insights__start_services","mcp__agent-insights__restart_services"],"mcp__agent-planning__start_question_session","mcp__agent-planning__ask_question","mcp__agent-planning__end_question_session","mcp__agent-planning__save_specification","mcp__agent-planning__list_specifications","mcp__agent-planning__read_specification","mcp__agent-planning__delete_specification","mcp__agent-planning__report_learning","mcp__agent-planning__report_bug","mcp__agent-planning__report_debug_insight","mcp__agent-planning__get_kanban_board","mcp__agent-planning__get_column_cards","mcp__agent-planning__get_card_details","mcp__agent-planning__create_kanban_card","mcp__agent-planning__update_kanban_card","mcp__agent-planning__move_kanban_card","mcp__agent-planning__delete_kanban_card","mcp__agent-planning__create_subtask","mcp__agent-planning__toggle_subtask","mcp__agent-planning__delete_subtask","mcp__agent-planning__get_project_prompt","mcp__agent-planning__update_project_prompt","mcp__agent-planning__create_command","mcp__agent-planning__get_test_suites","mcp__agent-planning__get_test_suite_details","mcp__agent-planning__get_test_details","mcp__agent-planning__run_test","mcp__agent-planning__run_test_suite","mcp__agent-planning__report_test_status","mcp__agent-planning__create_test_suite","mcp__agent-planning__create_test","mcp__agent-planning__update_test","mcp__agent-planning__reference_projects_list","mcp__agent-planning__reference_project_tree","mcp__agent-planning__reference_project_read_file","mcp__agent-planning__reference_project_download","mcp__agent-planning__reference_project_update_status","mcp__playwright__browser_navigate","mcp__playwright__browser_snapshot","mcp__playwright__browser_take_screenshot","mcp__playwright__browser_click","mcp__playwright__browser_type","mcp__playwright__browser_scroll_down","mcp__playwright__browser_scroll_up","mcp__playwright__browser_go_back","mcp__playwright__browser_go_forward","mcp__playwright__browser_wait","mcp__playwright__browser_fill_form","mcp__playwright__browser_select_option","mcp__playwright__browser_press_key","mcp__playwright__browser_hover","mcp__playwright__browser_drag","mcp__playwright__browser_file_upload","mcp__playwright__browser_handle_dialog","mcp__playwright__browser_close","mcp__playwright__browser_resize","mcp__playwright__browser_console_messages","mcp__playwright__browser_network_requests","mcp__playwright__browser_screen_capture","mcp__playwright__browser_screen_move_mouse","mcp__playwright__browser_screen_click","mcp__playwright__browser_screen_drag","mcp__playwright__browser_screen_type","mcp__playwright__browser_evaluate","mcp__playwright__browser_run_code","mcp__playwright__browser_tabs","mcp__playwright__browser_tab_list","mcp__playwright__browser_tab_new","mcp__playwright__browser_tab_select","mcp__playwright__browser_tab_close"],model:n,mcpServers:{"agent-insights":Et,"agent-planning":{type:"stdio",command:"node",args:[ne],env:{WORKSPACE_DIR:I,API_URL:o||process.env.API_URL||process.env.MASTER_URL||"http://localhost:5338",PROJECT_ID:s||process.env.PROJECT_ID||"",PROJECT_KEY:process.env.PROJECT_KEY||""}},playwright:{type:"stdio",command:"npx",args:["@playwright/mcp@latest","--caps=vision",`--user-data-dir=${W.join(I,".playwright-data")}`,...u?[]:["--headless"]]}},settingSources:["project"],disallowedTools:["AskUserQuestion","TodoWrite","EnterPlanMode"],agents:R?{scaffolding:ke,debugger:ie,planning:ce,backend:we,frontend:Se,"project-context":le}:{debugger:ie,planning:ce,"project-context":le},systemPrompt:Xe({debugMode:f,projectPrompt:et(),isLocal:u,projectConfig:C,useDefaultStack:R})}}))if(!(!d.uuid||q.has(d.uuid))){switch(q.add(d.uuid),B?.log(d),r.onRawMessage?.(d),d.type){case"assistant":if(d.message?.content)for(let S of d.message.content)S.type==="text"?r.onAssistantText?.(S.text):S.type==="tool_use"?r.onAssistantToolUse?.(S.name,S.input):S.type==="tool_result"&&r.onAssistantToolResult?.(S.tool_use_id,S.content);break;case"user":r.onUserMessage?.(d);break;case"result":if(d.subtype==="success")K=d.result,$=d.total_cost_usd,r.onResult?.(d.result,d.total_cost_usd);else{let S=`Agent error: ${d.subtype}`;T=S,r.onError?.(S)}Z=!0;break;case"system":switch(d.subtype){case"init":Y=d.session_id,B?.setSessionId(d.session_id),r.onSystemInit?.(d.session_id);break;case"status":r.onSystemStatus?.(JSON.stringify(d));break;case"compact_boundary":break;case"hook_response":break}break;case"stream_event":r.onStreamEvent?.(d);break;case"tool_progress":r.onToolProgress?.(d);break;case"auth_status":r.onAuthStatus?.(d);break}if(Z)break}}catch(d){d instanceof Error&&d.name==="AbortError"||d instanceof Error&&d.message.includes("aborted by user")?(U=!0,r.onAborted?.()):(T=d instanceof Error?d.message:String(d),r.onError?.(T))}finally{B?.stop()}return console.log("after try"),{sessionId:Y,result:K,cost:$,error:T,aborted:U}}function st(e){return`User answered the questions:
|
|
1353
|
+
</ui-verification>`}function Ze(e){return e&&e.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,"\uFFFD")}var vt=null;function et(){return Ze(vt)}import*as W from"path";import*as Te from"fs";import{fileURLToPath as Ct}from"url";var Et=_e(),ve=W.dirname(Ct(import.meta.url)),tt=[W.resolve(ve,"../mcps/stdio-server.js"),W.resolve(ve,"./adapters/mcps/stdio-server.js")],ne=tt.find(e=>Te.existsSync(e))||tt[0];console.log("[MCP] Stdio server path resolved:",ne);console.log("[MCP] __dirname:",ve);console.log("[MCP] File exists:",Te.existsSync(ne));async function nt(e,t={}){let{model:n,sessionId:a=null,projectId:s=null,apiUrl:o=null,callbacks:r={},debugLog:g,abortController:_,debugMode:f=!1,isLocal:u=process.env.LOCAL_MODE==="true",projectConfig:C=O(process.env.WORKSPACE_DIR||process.cwd()),useDefaultStack:R=!0}=t,B=We(g),q=new Set,Y=a||"",K,$,T,U=!1,Z=!1,I=process.env.WORKSPACE_DIR||process.cwd();console.log("[MCP] Configuring agent-planning stdio server:"),console.log("[MCP] Command: node"),console.log("[MCP] Args:",[ne]),console.log("[MCP] Env: API_URL=",process.env.API_URL||"http://localhost:5338",", PROJECT_ID=",s||process.env.PROJECT_ID||"",", PROJECT_KEY=",process.env.PROJECT_KEY?"***set***":"(not set)",", WORKSPACE_DIR=",I),console.log("[MCP] File exists check:",ne);try{for await(let d of Tt({prompt:e,options:{abortController:_,cwd:I,resume:a??void 0,allowedTools:["Bash","Read","Write","Edit","MultiEdit","Glob","Grep","LS","WebFetch","NotebookRead","NotebookEdit","Skill","mcp__agent-insights__check_backend_build","mcp__agent-insights__check_frontend_types","mcp__agent-insights__check_service_health",...u?[]:["mcp__agent-insights__stop_services","mcp__agent-insights__start_services","mcp__agent-insights__restart_services"],"mcp__agent-planning__start_question_session","mcp__agent-planning__ask_question","mcp__agent-planning__end_question_session","mcp__agent-planning__save_specification","mcp__agent-planning__list_specifications","mcp__agent-planning__read_specification","mcp__agent-planning__delete_specification","mcp__agent-planning__report_learning","mcp__agent-planning__report_bug","mcp__agent-planning__report_debug_insight","mcp__agent-planning__get_kanban_board","mcp__agent-planning__get_column_cards","mcp__agent-planning__get_card_details","mcp__agent-planning__create_kanban_card","mcp__agent-planning__update_kanban_card","mcp__agent-planning__move_kanban_card","mcp__agent-planning__delete_kanban_card","mcp__agent-planning__create_subtask","mcp__agent-planning__toggle_subtask","mcp__agent-planning__delete_subtask","mcp__agent-planning__get_project_prompt","mcp__agent-planning__update_project_prompt","mcp__agent-planning__create_command","mcp__agent-planning__get_test_suites","mcp__agent-planning__get_test_suite_details","mcp__agent-planning__get_test_details","mcp__agent-planning__run_test","mcp__agent-planning__run_test_suite","mcp__agent-planning__report_test_status","mcp__agent-planning__create_test_suite","mcp__agent-planning__create_test","mcp__agent-planning__update_test","mcp__agent-planning__reference_projects_list","mcp__agent-planning__reference_project_tree","mcp__agent-planning__reference_project_read_file","mcp__agent-planning__reference_project_download","mcp__agent-planning__reference_project_update_status","mcp__playwright__browser_navigate","mcp__playwright__browser_snapshot","mcp__playwright__browser_take_screenshot","mcp__playwright__browser_click","mcp__playwright__browser_type","mcp__playwright__browser_scroll_down","mcp__playwright__browser_scroll_up","mcp__playwright__browser_go_back","mcp__playwright__browser_go_forward","mcp__playwright__browser_wait","mcp__playwright__browser_fill_form","mcp__playwright__browser_select_option","mcp__playwright__browser_press_key","mcp__playwright__browser_hover","mcp__playwright__browser_drag","mcp__playwright__browser_file_upload","mcp__playwright__browser_handle_dialog","mcp__playwright__browser_close","mcp__playwright__browser_resize","mcp__playwright__browser_console_messages","mcp__playwright__browser_network_requests","mcp__playwright__browser_screen_capture","mcp__playwright__browser_screen_move_mouse","mcp__playwright__browser_screen_click","mcp__playwright__browser_screen_drag","mcp__playwright__browser_screen_type","mcp__playwright__browser_evaluate","mcp__playwright__browser_run_code","mcp__playwright__browser_tabs","mcp__playwright__browser_tab_list","mcp__playwright__browser_tab_new","mcp__playwright__browser_tab_select","mcp__playwright__browser_tab_close"],model:n,mcpServers:{"agent-insights":Et,"agent-planning":{type:"stdio",command:"node",args:[ne],env:{WORKSPACE_DIR:I,API_URL:o||process.env.API_URL||process.env.MASTER_URL||"http://localhost:5338",PROJECT_ID:s||process.env.PROJECT_ID||"",PROJECT_KEY:process.env.PROJECT_KEY||""}},playwright:{type:"stdio",command:"npx",args:["@playwright/mcp@latest",...u?[]:["--browser=firefox"],"--caps=vision",`--user-data-dir=${W.join(I,".playwright-data")}`,...u?[]:["--headless"]]}},settingSources:["project"],disallowedTools:["AskUserQuestion","TodoWrite","EnterPlanMode"],agents:R?{scaffolding:ke,debugger:ie,planning:ce,backend:we,frontend:Se,"project-context":le}:{debugger:ie,planning:ce,"project-context":le},systemPrompt:Xe({debugMode:f,projectPrompt:et(),isLocal:u,projectConfig:C,useDefaultStack:R})}}))if(!(!d.uuid||q.has(d.uuid))){switch(q.add(d.uuid),B?.log(d),r.onRawMessage?.(d),d.type){case"assistant":if(d.message?.content)for(let S of d.message.content)S.type==="text"?r.onAssistantText?.(S.text):S.type==="tool_use"?r.onAssistantToolUse?.(S.name,S.input):S.type==="tool_result"&&r.onAssistantToolResult?.(S.tool_use_id,S.content);break;case"user":r.onUserMessage?.(d);break;case"result":if(d.subtype==="success")K=d.result,$=d.total_cost_usd,r.onResult?.(d.result,d.total_cost_usd);else{let S=`Agent error: ${d.subtype}`;T=S,r.onError?.(S)}Z=!0;break;case"system":switch(d.subtype){case"init":Y=d.session_id,B?.setSessionId(d.session_id),r.onSystemInit?.(d.session_id);break;case"status":r.onSystemStatus?.(JSON.stringify(d));break;case"compact_boundary":break;case"hook_response":break}break;case"stream_event":r.onStreamEvent?.(d);break;case"tool_progress":r.onToolProgress?.(d);break;case"auth_status":r.onAuthStatus?.(d);break}if(Z)break}}catch(d){d instanceof Error&&d.name==="AbortError"||d instanceof Error&&d.message.includes("aborted by user")?(U=!0,r.onAborted?.()):(T=d instanceof Error?d.message:String(d),r.onError?.(T))}finally{B?.stop()}return console.log("after try"),{sessionId:Y,result:K,cost:$,error:T,aborted:U}}function st(e){return`User answered the questions:
|
|
1341
1354
|
|
|
1342
1355
|
${Object.entries(e).map(([n,a])=>{let s=Array.isArray(a)?a.join(", "):a;return`${n}: ${s}`}).join(`
|
|
1343
1356
|
`)}`}import{spawn as rt,execSync as G}from"child_process";import*as b from"fs";import*as ot from"http";import*as j from"path";function at(e){let{workspaceDir:t,apiPort:n,webPort:a,onBackendError:s,onFrontendError:o,projectConfig:r=O(t)}=e,g=n??r.services?.backend?.port??5338,_=a??r.services?.frontend?.port??5173,f=F(r),u=N(r),C=r.services?.backend?.startCommand??"dotnet run",R=r.services?.backend?.buildCommand??"dotnet build",B=r.services?.backend?.typecheckCommand??"dotnet build --no-restore",q=r.services?.frontend?.startCommand??"npm run dev",Y=r.services?.frontend?.typecheckCommand??"npx tsc -p tsconfig.app.json --noEmit",K=r.services?.backend?.logFile??"/tmp/api.log",$=r.services?.frontend?.logFile??"/tmp/web.log",T=null,U=null,Z=l=>new Promise(i=>{let p=setTimeout(()=>i(!1),3e3);ot.get(`http://localhost:${l}`,v=>{clearTimeout(p),i(v.statusCode!==void 0&&v.statusCode<500)}).on("error",()=>{clearTimeout(p),i(!1)})}),I=l=>{try{let i=G(`lsof -ti:${l} 2>/dev/null || true`,{encoding:"utf-8"}).trim();if(i){let p=i.split(`
|
|
@@ -1346,4 +1359,4 @@ ${Object.entries(e).map(([n,a])=>{let s=Array.isArray(a)?a.join(", "):a;return`$
|
|
|
1346
1359
|
Recent logs:
|
|
1347
1360
|
${k.join(`
|
|
1348
1361
|
`)}`:"";i.backendError||(i.backendError=`Backend failed to start.${h}`)}return m&&!v.web&&(i.success=!1,i.frontendError||(i.frontendError="Frontend failed to start")),i},xe=async()=>{if(!f||!b.existsSync(f))return{success:!0};try{return G(B,{cwd:f,stdio:"pipe",timeout:12e4,encoding:"utf-8"}),{success:!0}}catch(l){let i=l;return{success:!1,errors:i.stdout||i.stderr||"Build failed"}}},Re=async()=>{if(!u||!b.existsSync(j.join(u,"package.json")))return{success:!0};try{return G(Y,{cwd:u,stdio:"pipe",timeout:6e4,encoding:"utf-8"}),{success:!0}}catch(l){let i=l;return{success:!1,errors:i.stdout||i.stderr||"Type check failed"}}},lt=async()=>{let[l,i]=await Promise.all([xe(),Re()]);return{backend:l,frontend:i}},Ie=async(l,i)=>{let p=l==="backend"?K:$;if(!b.existsSync(p))return[`Log file not found: ${p}`];try{return G(`tail -n ${i} ${p}`,{encoding:"utf-8"}).split(`
|
|
1349
|
-
`).filter(Boolean)}catch(m){return[`Error reading logs: ${m instanceof Error?m.message:String(m)}`]}};return{startBackend:S,startFrontend:Ce,stopBackend:Ee,stopFrontend:Pe,restartServices:ct,checkHealth:ge,waitForHealth:Ae,getProcesses:()=>({backend:T,frontend:U}),checkBackendBuild:xe,checkFrontendTypes:Re,runTypeChecks:lt,tailLogs:Ie,checkSwaggerEndpoints:async l=>{let i=j.join(t,"swagger.json");if(!b.existsSync(i))return{foundEndpoints:["Error: swagger.json not found"],totalEndpoints:0};try{let p=JSON.parse(b.readFileSync(i,"utf-8")),m=Object.keys(p.paths||{}),v=[];for(let k of m)if(k.toLowerCase().includes(l.toLowerCase())){let h=Object.keys(p.paths[k]);for(let L of h)v.push(`${L.toUpperCase()} ${k}`)}return{foundEndpoints:v,totalEndpoints:m.length}}catch(p){return{foundEndpoints:[`Error parsing swagger.json: ${p instanceof Error?p.message:String(p)}`],totalEndpoints:0}}}}}function Pt(e){let t=e.toLowerCase();return t.includes("exception")||t.includes("fail:")||t.includes("[err]")||t.includes("[error]")}import{execSync as H}from"child_process";function it(e){let t=async()=>{try{return H("git status --porcelain",{cwd:e,encoding:"utf-8"}).trim().length>0}catch{return!1}},n=async()=>{try{return H("git branch --show-current",{cwd:e,encoding:"utf-8"}).trim()}catch{return"unknown"}};return{hasChanges:t,commitAndPush:async(s,o)=>{try{if(!await t())return{success:!0,noChanges:!0};let r=s.length>60?s.substring(0,60)+"...":s,_=`${o?"[agent] (partial)":"[agent]"} ${r}`;H("git add -A",{cwd:e}),H(`git commit -m "${_.replace(/"/g,'\\"')}"`,{cwd:e,encoding:"utf-8"});let f=H("git rev-parse --short HEAD",{cwd:e,encoding:"utf-8"}).trim();try{H("git push",{cwd:e,stdio:"pipe"})}catch(u){let C=u instanceof Error?u.message:String(u);if(C.includes("no upstream branch")){let R=await n();H(`git push --set-upstream origin ${R}`,{cwd:e,stdio:"pipe"})}else return console.error("[Git] Push failed:",C),{success:!
|
|
1362
|
+
`).filter(Boolean)}catch(m){return[`Error reading logs: ${m instanceof Error?m.message:String(m)}`]}};return{startBackend:S,startFrontend:Ce,stopBackend:Ee,stopFrontend:Pe,restartServices:ct,checkHealth:ge,waitForHealth:Ae,getProcesses:()=>({backend:T,frontend:U}),checkBackendBuild:xe,checkFrontendTypes:Re,runTypeChecks:lt,tailLogs:Ie,checkSwaggerEndpoints:async l=>{let i=j.join(t,"swagger.json");if(!b.existsSync(i))return{foundEndpoints:["Error: swagger.json not found"],totalEndpoints:0};try{let p=JSON.parse(b.readFileSync(i,"utf-8")),m=Object.keys(p.paths||{}),v=[];for(let k of m)if(k.toLowerCase().includes(l.toLowerCase())){let h=Object.keys(p.paths[k]);for(let L of h)v.push(`${L.toUpperCase()} ${k}`)}return{foundEndpoints:v,totalEndpoints:m.length}}catch(p){return{foundEndpoints:[`Error parsing swagger.json: ${p instanceof Error?p.message:String(p)}`],totalEndpoints:0}}}}}function Pt(e){let t=e.toLowerCase();return t.includes("exception")||t.includes("fail:")||t.includes("[err]")||t.includes("[error]")}import{execSync as H}from"child_process";function it(e){let t=async()=>{try{return H("git status --porcelain",{cwd:e,encoding:"utf-8"}).trim().length>0}catch{return!1}},n=async()=>{try{return H("git branch --show-current",{cwd:e,encoding:"utf-8"}).trim()}catch{return"unknown"}};return{hasChanges:t,commitAndPush:async(s,o)=>{try{if(!await t())return{success:!0,noChanges:!0};let r=s.length>60?s.substring(0,60)+"...":s,_=`${o?"[agent] (partial)":"[agent]"} ${r}`;H("git add -A",{cwd:e}),H(`git commit -m "${_.replace(/"/g,'\\"')}"`,{cwd:e,encoding:"utf-8"});let f=H("git rev-parse --short HEAD",{cwd:e,encoding:"utf-8"}).trim();try{H("git push",{cwd:e,stdio:"pipe"})}catch(u){let C=u instanceof Error?u.message:String(u);if(C.includes("no upstream branch")){let R=await n();H(`git push --set-upstream origin ${R}`,{cwd:e,stdio:"pipe"})}else return console.error("[Git] Push failed:",C),{success:!1,commitHash:f,commitSucceeded:!0,pushFailed:!0,error:C}}return{success:!0,commitHash:f}}catch(r){let g=r instanceof Error?r.message:String(r);return console.error("[Git] Error:",g),{success:!1,error:g}}},getCurrentBranch:n}}export{ee as ProjectConfigSchema,be as createFilePlanningTransport,he as createFileTransport,it as createLocalGitManager,at as createLocalServiceManager,_e as createMcpServer,st as formatAnswersAsPrompt,te as getBackendFeaturesPath,F as getBackendPath,O as getDefaultConfig,X as getFrontendFeaturesPath,N as getFrontendPath,Je as getPathsDescription,fe as getServicesDescription,ue as getTechStackDescription,Qe as isBackendFile,ze as isFrontendFile,Ke as loadProjectConfig,ye as readPendingQuestions,pe as resolveConfigPath,nt as runAgent,Ve as saveProjectConfig,Oe as setPlanningTransport,Fe as setServiceManager};
|
package/dist/index.js
CHANGED
|
@@ -15,14 +15,14 @@ Workflow:
|
|
|
15
15
|
2. save_specification(name: "User Authentication", filePath: "/tmp/user-auth-spec.md")`,{name:le.string().describe("Specification name (e.g., 'User Authentication')"),filePath:le.string().describe("Path to the file containing the specification content (written via Write tool)")},async e=>{if(!L)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let t=Ze.resolve(e.filePath);if(!de.existsSync(t))return{content:[{type:"text",text:`\u274C File not found: ${e.filePath}. Use Write tool first to create the file.`}]};let n=de.readFileSync(t,"utf-8"),s=Fe(e.name);return await L.saveSpecification(s,n),{content:[{type:"text",text:`\u2705 Saved specification: ${s}.spec.md`}]}}),On=pe("read_specification","Read the content of a specification.",{name:le.string().describe("Name of the specification to read")},async e=>{if(!L)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let t=Fe(e.name),n=await L.getSpecification(t);return n?{content:[{type:"text",text:n}]}:{content:[{type:"text",text:`\u274C Specification "${e.name}" not found.`}]}}),Dn=pe("list_specifications","List all specifications.",{},async()=>{if(!L)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let e=await L.listSpecifications();return e.length===0?{content:[{type:"text",text:"\u{1F4CB} No specifications found."}]}:{content:[{type:"text",text:`\u{1F4CB} Specifications:
|
|
16
16
|
|
|
17
17
|
${e.map(n=>`- ${n.name} (${n.slug}.spec.md)`).join(`
|
|
18
|
-
`)}`}]}}),Fn=pe("delete_specification","Delete a specification.",{name:le.string().describe("Name of the specification to delete")},async e=>{if(!L)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let t=Fe(e.name);return await L.deleteSpecification(t)?{content:[{type:"text",text:`\u{1F5D1}\uFE0F Deleted: ${t}.spec.md`}]}:{content:[{type:"text",text:`\u274C Specification "${e.name}" not found.`}]}});import{tool as q}from"@anthropic-ai/claude-agent-sdk";import{z as X}from"zod";var
|
|
18
|
+
`)}`}]}}),Fn=pe("delete_specification","Delete a specification.",{name:le.string().describe("Name of the specification to delete")},async e=>{if(!L)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let t=Fe(e.name);return await L.deleteSpecification(t)?{content:[{type:"text",text:`\u{1F5D1}\uFE0F Deleted: ${t}.spec.md`}]}:{content:[{type:"text",text:`\u274C Specification "${e.name}" not found.`}]}});import{tool as q}from"@anthropic-ai/claude-agent-sdk";import{z as X}from"zod";var k=null;function fe(e){k=e}var et=q("restart_services",`Rebuild and restart services. Call this after making backend changes (.cs files) to apply them.
|
|
19
19
|
|
|
20
20
|
Parameters:
|
|
21
21
|
- target: "backend" (default) | "frontend" | "both"
|
|
22
22
|
|
|
23
23
|
Use target="backend" after .cs file changes (faster, only restarts .NET).
|
|
24
24
|
Use target="frontend" if Vite is stuck (rare, HMR usually works).
|
|
25
|
-
Use target="both" if unsure or both need restart.`,{target:X.enum(["backend","frontend","both"]).default("backend").describe("Which service to restart")},async e=>{if(!
|
|
25
|
+
Use target="both" if unsure or both need restart.`,{target:X.enum(["backend","frontend","both"]).default("backend").describe("Which service to restart")},async e=>{if(!k)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=e.target||"backend";console.log(`[MCP] restart_services called (target: ${t})`);let n=await k.restartServices(t);if(!n.success){let o=[];return n.backendError&&o.push(`Backend: ${n.backendError}`),n.frontendError&&o.push(`Frontend: ${n.frontendError}`),{content:[{type:"text",text:`\u274C Restart failed:
|
|
26
26
|
${o.join(`
|
|
27
27
|
|
|
28
28
|
`)}`}]}}return{content:[{type:"text",text:`\u2705 ${t==="both"?"Backend and frontend":t==="backend"?"Backend":"Frontend"} restarted successfully.`}]}}),tt=q("stop_services",`Stop running services. Call this BEFORE running scaffold to prevent port conflicts and ensure clean migrations.
|
|
@@ -30,32 +30,32 @@ ${o.join(`
|
|
|
30
30
|
Parameters:
|
|
31
31
|
- target: "backend" (default) | "frontend" | "both"
|
|
32
32
|
|
|
33
|
-
Use target="backend" before running scaffold (required for migrations).`,{target:X.enum(["backend","frontend","both"]).default("backend").describe("Which service to stop")},async e=>{if(!
|
|
33
|
+
Use target="backend" before running scaffold (required for migrations).`,{target:X.enum(["backend","frontend","both"]).default("backend").describe("Which service to stop")},async e=>{if(!k)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=e.target||"backend";console.log(`[MCP] stop_services called (target: ${t})`);let n=t==="backend"||t==="both",s=t==="frontend"||t==="both";n&&await k.stopBackend(),s&&await k.stopFrontend();let o=5e3,r=Date.now(),i=!n,l=!s;for(;Date.now()-r<o;){let f=await k.checkHealth();if(n&&!f.api&&(i=!0),s&&!f.web&&(l=!0),i&&l)break;await new Promise(v=>setTimeout(v,500))}let h=t==="both"?"Backend and frontend":t==="backend"?"Backend":"Frontend";return{content:[{type:"text",text:i&&l?`\u2705 ${h} stopped successfully.`:`\u26A0\uFE0F ${h} stop requested but health check still shows running. Proceeding anyway.`}]}}),nt=q("start_services",`Start services. Call this AFTER running scaffold to bring services back up.
|
|
34
34
|
|
|
35
35
|
Parameters:
|
|
36
36
|
- target: "backend" (default) | "frontend" | "both"
|
|
37
37
|
|
|
38
|
-
Use target="backend" after running scaffold.`,{target:X.enum(["backend","frontend","both"]).default("backend").describe("Which service to start")},async e=>{if(!
|
|
39
|
-
Note: This only checks if ports are responding, NOT build/type errors. Use check_backend_build or check_frontend_types for that.`,{},async()=>{if(!
|
|
38
|
+
Use target="backend" after running scaffold.`,{target:X.enum(["backend","frontend","both"]).default("backend").describe("Which service to start")},async e=>{if(!k)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=e.target||"backend";console.log(`[MCP] start_services called (target: ${t})`);let n=t==="backend"||t==="both",s=t==="frontend"||t==="both";n&&await k.startBackend(),s&&await k.startFrontend();let o=await k.waitForHealth(15e3),r=!n||o.api,i=!s||o.web,l=r&&i,h=t==="both"?"Backend and frontend":t==="backend"?"Backend":"Frontend";if(!l){let m=[];return n&&!o.api&&m.push("Backend failed to start"),s&&!o.web&&m.push("Frontend failed to start"),{content:[{type:"text",text:`\u274C ${h} failed to start: ${m.join(", ")}`}]}}return{content:[{type:"text",text:`\u2705 ${h} started successfully.`}]}}),ot=q("check_service_health",`Check if services are running and healthy. Returns current status of backend API and frontend.
|
|
39
|
+
Note: This only checks if ports are responding, NOT build/type errors. Use check_backend_build or check_frontend_types for that.`,{},async()=>{if(!k)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let e=await k.checkHealth();return{content:[{type:"text",text:`Service Health:
|
|
40
40
|
\u{1F5A5}\uFE0F Backend API: ${e.api?"\u2705 running":"\u274C not running"}
|
|
41
|
-
\u{1F310} Frontend: ${e.web?"\u2705 running":"\u274C not running"}`}]}}),st=q("check_backend_build","Run dotnet build to check for C# compilation errors. Use this after fixing backend code to verify the fix works.",{},async()=>{if(!
|
|
41
|
+
\u{1F310} Frontend: ${e.web?"\u2705 running":"\u274C not running"}`}]}}),st=q("check_backend_build","Run dotnet build to check for C# compilation errors. Use this after fixing backend code to verify the fix works.",{},async()=>{if(!k)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};console.log("[MCP] check_backend_build called");let e=await k.checkBackendBuild();return e.success?{content:[{type:"text",text:"\u2705 Backend build succeeded - no errors"}]}:{content:[{type:"text",text:`\u274C Backend build failed:
|
|
42
42
|
|
|
43
|
-
${e.errors}`}]}}),rt=q("check_frontend_types","Run TypeScript type check (tsc) on frontend code. Use this after fixing frontend code to verify the fix works.",{},async()=>{if(!
|
|
43
|
+
${e.errors}`}]}}),rt=q("check_frontend_types","Run TypeScript type check (tsc) on frontend code. Use this after fixing frontend code to verify the fix works.",{},async()=>{if(!k)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};console.log("[MCP] check_frontend_types called");let e=await k.checkFrontendTypes();return e.success?{content:[{type:"text",text:"\u2705 Frontend type check passed - no errors"}]}:{content:[{type:"text",text:`\u274C Frontend type errors:
|
|
44
44
|
|
|
45
45
|
${e.errors}`}]}}),Mn=q("tail_service_log",`Get the last N lines of service logs. Use this to debug startup failures or verify that the application is running correctly.
|
|
46
46
|
|
|
47
47
|
Parameters:
|
|
48
48
|
- target: "backend" | "frontend"
|
|
49
|
-
- lines: number (default 50)`,{target:X.enum(["backend","frontend"]).describe("Which log to read"),lines:X.number().default(50).describe("Number of lines to read")},async e=>{if(!
|
|
49
|
+
- lines: number (default 50)`,{target:X.enum(["backend","frontend"]).describe("Which log to read"),lines:X.number().default(50).describe("Number of lines to read")},async e=>{if(!k)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=await k.tailLogs(e.target,e.lines);return{content:[{type:"text",text:`Last ${e.lines} lines of ${e.target} log:
|
|
50
50
|
|
|
51
51
|
${t.join(`
|
|
52
52
|
`)}`}]}}),jn=q("check_swagger_endpoints",`Search for endpoints in the generated swagger.json. Use this to verify that new backend endpoints are correctly exposed.
|
|
53
53
|
|
|
54
54
|
Parameters:
|
|
55
|
-
- pattern: string (e.g., "users", "api/reports")`,{pattern:X.string().describe("Text to search for in endpoint paths")},async e=>{if(!
|
|
55
|
+
- pattern: string (e.g., "users", "api/reports")`,{pattern:X.string().describe("Text to search for in endpoint paths")},async e=>{if(!k)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=await k.checkSwaggerEndpoints(e.pattern);return t.foundEndpoints.length===0?{content:[{type:"text",text:`\u274C No endpoints found matching "${e.pattern}" (Total endpoints: ${t.totalEndpoints})`}]}:{content:[{type:"text",text:`\u2705 Found ${t.foundEndpoints.length} matching endpoints:
|
|
56
56
|
|
|
57
57
|
${t.foundEndpoints.join(`
|
|
58
|
-
`)}`}]}});import*as W from"fs";import*as it from"path";var $t="/tmp";function te(e){let t=it.join($t,`agent-questions-${e}.json`);if(!W.existsSync(t))return null;try{let n=W.readFileSync(t,"utf-8");return W.unlinkSync(t),JSON.parse(n)}catch{return null}}import*as E from"fs";import*as ge from"path";function ne(e){let t=ge.join(e,".sdd","specifications");function n(){E.existsSync(t)||E.mkdirSync(t,{recursive:!0})}function s(o){return ge.join(t,`${o}.spec.md`)}return{async saveSpecification(o,r){n(),E.writeFileSync(s(o),r,"utf-8")},async getSpecification(o){n();let r=s(o);return E.existsSync(r)?E.readFileSync(r,"utf-8"):null},async listSpecifications(){return n(),E.readdirSync(t).filter(r=>r.endsWith(".spec.md")).map(r=>{let i=E.readFileSync(ge.join(t,r),"utf-8"),l=i.match(/name: "([^"]+)"/),h=i.match(/status: (\w+)/),
|
|
58
|
+
`)}`}]}});import*as W from"fs";import*as it from"path";var $t="/tmp";function te(e){let t=it.join($t,`agent-questions-${e}.json`);if(!W.existsSync(t))return null;try{let n=W.readFileSync(t,"utf-8");return W.unlinkSync(t),JSON.parse(n)}catch{return null}}import*as E from"fs";import*as ge from"path";function ne(e){let t=ge.join(e,".sdd","specifications");function n(){E.existsSync(t)||E.mkdirSync(t,{recursive:!0})}function s(o){return ge.join(t,`${o}.spec.md`)}return{async saveSpecification(o,r){n(),E.writeFileSync(s(o),r,"utf-8")},async getSpecification(o){n();let r=s(o);return E.existsSync(r)?E.readFileSync(r,"utf-8"):null},async listSpecifications(){return n(),E.readdirSync(t).filter(r=>r.endsWith(".spec.md")).map(r=>{let i=E.readFileSync(ge.join(t,r),"utf-8"),l=i.match(/name: "([^"]+)"/),h=i.match(/status: (\w+)/),m=i.match(/version: "([^"]+)"/);return{slug:r.replace(".spec.md",""),name:l?.[1]||r,status:h?.[1]||"unknown",version:m?.[1]||"1.0.0"}})},async deleteSpecification(o){n();let r=s(o);return E.existsSync(r)?(E.unlinkSync(r),!0):!1},async specificationExists(o){return n(),E.existsSync(s(o))}}}function Ue(){return Bt({name:"agent-insights",version:"1.0.0",tools:[et,tt,nt,ot,st,rt]})}import{query as rn}from"@anthropic-ai/claude-agent-sdk";var Ne={description:"Delegate to this agent when scaffolding new entities or creating CRUD features. This agent handles entity generation using the scaffold CLI with multi-entity support.",model:"inherit",prompt:`You are a scaffolding specialist. Your ONLY job is:
|
|
59
59
|
1. Read the spec if referenced in the card description
|
|
60
60
|
2. Check existing features (quick ls)
|
|
61
61
|
3. Write ALL entities to ONE /tmp/scaffold.json (big bang, no phasing!)
|
|
@@ -832,8 +832,8 @@ Before saving, verify your context:
|
|
|
832
832
|
5. **Keep it scannable** - Use bullets, clear sections
|
|
833
833
|
6. **Update, don't replace** - If context exists, improve it rather than starting fresh`};import*as Z from"fs";import*as ct from"path";var be=class{buffer=[];flushInterval=null;sessionId=null;outputDir;flushIntervalMs;constructor(t){this.outputDir=t.outputDir||"/tmp/agent-debug",this.flushIntervalMs=t.flushIntervalMs||3e3,t.enabled&&(this.ensureOutputDir(),this.startFlushInterval())}ensureOutputDir(){Z.existsSync(this.outputDir)||Z.mkdirSync(this.outputDir,{recursive:!0})}startFlushInterval(){this.flushInterval=setInterval(()=>{this.flush()},this.flushIntervalMs)}setSessionId(t){this.sessionId=t}log(t){let s={timestamp:new Date().toISOString(),message:t};this.buffer.push(JSON.stringify(s,null,2)+`
|
|
834
834
|
---
|
|
835
|
-
`)}getLogFilePath(){let n=(this.sessionId||"unknown").replace(/[^a-zA-Z0-9-]/g,"_").slice(0,50),s=new Date().toISOString().split("T")[0];return ct.join(this.outputDir,`session-${s}-${n}.log`)}flush(){if(this.buffer.length===0)return;let t=this.buffer.join("");this.buffer=[];let n=this.getLogFilePath();Z.appendFileSync(n,t)}stop(){this.flushInterval&&(clearInterval(this.flushInterval),this.flushInterval=null),this.flush()}};function lt(e){return e?typeof e=="boolean"?e?new be({enabled:!0}):null:e.enabled?new be(e):null:null}import{z as d}from"zod";import*as je from"fs/promises";import*as K from"path";var pt=d.object({port:d.number(),startCommand:d.string(),buildCommand:d.string().optional(),typecheckCommand:d.string().optional(),logFile:d.string().optional(),healthEndpoint:d.string().optional(),extensions:d.array(d.string())}),qt=d.object({port:d.number(),type:d.enum(["postgres","mysql","sqlite","mongodb"])}),Wt=d.object({command:d.string(),schemaPath:d.string().optional()}),Gt=d.object({email:d.string().optional(),name:d.string().optional(),commitPrefix:d.string().optional()}),Ht=d.object({enabled:d.boolean(),port:d.number().optional()}),dt=d.object({version:d.literal("1.0"),paths:d.object({workspace:d.string(),backend:d.string().optional(),frontend:d.string().optional(),features:d.object({backend:d.string().optional(),frontend:d.string().optional()}).optional()}),services:d.object({backend:pt.optional(),frontend:pt.optional(),database:qt.optional()}).optional(),scaffold:Wt.optional(),git:Gt.optional(),techStack:d.object({backend:d.array(d.string()).optional(),frontend:d.array(d.string()).optional(),patterns:d.array(d.string()).optional()}).optional(),tunnel:Ht.optional()});function G(e){return{version:"1.0",paths:{workspace:e||process.env.WORKSPACE_DIR||"/workspace",backend:"packages/dotnet-api",frontend:"packages/backoffice-web",features:{backend:"Source/Features",frontend:"src/applications/super-admin/features"}},services:{backend:{port:5338,startCommand:"dotnet run",buildCommand:"dotnet build",typecheckCommand:"dotnet build --no-restore",logFile:"/tmp/api.log",healthEndpoint:"http://localhost:5338",extensions:[".cs",".csproj"]},frontend:{port:5173,startCommand:"npm run dev",typecheckCommand:"npx tsc -p tsconfig.app.json --noEmit",logFile:"/tmp/web.log",healthEndpoint:"http://localhost:5173",extensions:[".ts",".tsx",".json"]},database:{port:43594,type:"postgres"}},scaffold:{command:"scaffold --schema /tmp/scaffold.json --output /workspace --force --full",schemaPath:"/tmp/scaffold.json"},git:{email:"agent@dotnetmentor.se",name:"Agent",commitPrefix:"[agent]"},techStack:{backend:[".NET 9","PostgreSQL","MediatR (CQRS)","EF Core"],frontend:["React 19","TypeScript","Vite","MUI","TanStack Query"],patterns:["Vertical slices","CQRS","Soft delete","Timestamps"]},tunnel:{enabled:!0,port:5173}}}function we(e,t){if(t)return K.isAbsolute(t)?t:K.join(e.paths.workspace,t)}function H(e){return we(e,e.paths.backend)}function Y(e){return we(e,e.paths.frontend)}function
|
|
836
|
-
`)}function
|
|
835
|
+
`)}getLogFilePath(){let n=(this.sessionId||"unknown").replace(/[^a-zA-Z0-9-]/g,"_").slice(0,50),s=new Date().toISOString().split("T")[0];return ct.join(this.outputDir,`session-${s}-${n}.log`)}flush(){if(this.buffer.length===0)return;let t=this.buffer.join("");this.buffer=[];let n=this.getLogFilePath();Z.appendFileSync(n,t)}stop(){this.flushInterval&&(clearInterval(this.flushInterval),this.flushInterval=null),this.flush()}};function lt(e){return e?typeof e=="boolean"?e?new be({enabled:!0}):null:e.enabled?new be(e):null:null}import{z as d}from"zod";import*as je from"fs/promises";import*as K from"path";var pt=d.object({port:d.number(),startCommand:d.string(),buildCommand:d.string().optional(),typecheckCommand:d.string().optional(),logFile:d.string().optional(),healthEndpoint:d.string().optional(),extensions:d.array(d.string())}),qt=d.object({port:d.number(),type:d.enum(["postgres","mysql","sqlite","mongodb"])}),Wt=d.object({command:d.string(),schemaPath:d.string().optional()}),Gt=d.object({email:d.string().optional(),name:d.string().optional(),commitPrefix:d.string().optional()}),Ht=d.object({enabled:d.boolean(),port:d.number().optional()}),dt=d.object({version:d.literal("1.0"),paths:d.object({workspace:d.string(),backend:d.string().optional(),frontend:d.string().optional(),features:d.object({backend:d.string().optional(),frontend:d.string().optional()}).optional()}),services:d.object({backend:pt.optional(),frontend:pt.optional(),database:qt.optional()}).optional(),scaffold:Wt.optional(),git:Gt.optional(),techStack:d.object({backend:d.array(d.string()).optional(),frontend:d.array(d.string()).optional(),patterns:d.array(d.string()).optional()}).optional(),tunnel:Ht.optional()});function G(e){return{version:"1.0",paths:{workspace:e||process.env.WORKSPACE_DIR||"/workspace",backend:"packages/dotnet-api",frontend:"packages/backoffice-web",features:{backend:"Source/Features",frontend:"src/applications/super-admin/features"}},services:{backend:{port:5338,startCommand:"dotnet run",buildCommand:"dotnet build",typecheckCommand:"dotnet build --no-restore",logFile:"/tmp/api.log",healthEndpoint:"http://localhost:5338",extensions:[".cs",".csproj"]},frontend:{port:5173,startCommand:"npm run dev",typecheckCommand:"npx tsc -p tsconfig.app.json --noEmit",logFile:"/tmp/web.log",healthEndpoint:"http://localhost:5173",extensions:[".ts",".tsx",".json"]},database:{port:43594,type:"postgres"}},scaffold:{command:"scaffold --schema /tmp/scaffold.json --output /workspace --force --full",schemaPath:"/tmp/scaffold.json"},git:{email:"agent@dotnetmentor.se",name:"Agent",commitPrefix:"[agent]"},techStack:{backend:[".NET 9","PostgreSQL","MediatR (CQRS)","EF Core"],frontend:["React 19","TypeScript","Vite","MUI","TanStack Query"],patterns:["Vertical slices","CQRS","Soft delete","Timestamps"]},tunnel:{enabled:!0,port:5173}}}function we(e,t){if(t)return K.isAbsolute(t)?t:K.join(e.paths.workspace,t)}function H(e){return we(e,e.paths.backend)}function Y(e){return we(e,e.paths.frontend)}function ke(e){let t=H(e);if(!(!t||!e.paths.features?.backend))return K.join(t,e.paths.features.backend)}function oe(e){let t=Y(e);if(!(!t||!e.paths.features?.frontend))return K.join(t,e.paths.features.frontend)}function Se(e){let t=[];return e.techStack?.backend?.length&&t.push(`**Backend:** ${e.techStack.backend.join(", ")}`),e.techStack?.frontend?.length&&t.push(`**Frontend:** ${e.techStack.frontend.join(", ")}`),e.techStack?.patterns?.length&&t.push(`**Patterns:** ${e.techStack.patterns.join(", ")}`),t.join(`
|
|
836
|
+
`)}function _e(e){let t=[];return e.services?.backend&&t.push(`| Backend | ${H(e)} | ${e.services.backend.port} | ${e.services.backend.logFile||"N/A"} |`),e.services?.frontend&&t.push(`| Frontend | ${Y(e)} | ${e.services.frontend.port} | ${e.services.frontend.logFile||"N/A"} |`),e.services?.database&&t.push(`| Database (${e.services.database.type}) | localhost | ${e.services.database.port} | - |`),t.length===0?"":`| Service | Location | Port | Logs |
|
|
837
837
|
|---------|----------|------|------|
|
|
838
838
|
${t.join(`
|
|
839
839
|
`)}`}function ut(e={}){let{debugMode:t=!1,projectPrompt:n=null,isLocal:s=!1,projectConfig:o=G(),useDefaultStack:r=!0}=e,i=n?`
|
|
@@ -881,7 +881,7 @@ Then STOP. Do not continue. Do not try to fix it. Wait for user.
|
|
|
881
881
|
|
|
882
882
|
---
|
|
883
883
|
|
|
884
|
-
`:"",h=Jt(o),
|
|
884
|
+
`:"",h=Jt(o),m=Xt(o,s),f=tn(o,r),v=Zt(r),T=r?nn(o):"",_=r?on(o,s):"",O=en(r);return`${i}${l}# Agent Instructions
|
|
885
885
|
|
|
886
886
|
## \u26D4 CRITICAL: EVERYTHING IS A KANBAN TASK
|
|
887
887
|
|
|
@@ -1014,21 +1014,34 @@ You know that overengineering is the enemy of good software. You are pragmatic a
|
|
|
1014
1014
|
|
|
1015
1015
|
**Your users may be non-technical.** They have ideas, not code. You are their bridge to magic.
|
|
1016
1016
|
|
|
1017
|
-
|
|
1017
|
+
## \u{1F30D} Localization
|
|
1018
|
+
|
|
1019
|
+
**Match the user's language in generated UI text.** If the user prompts in Swedish, German, Spanish, etc., generate UI labels, placeholders, error messages, and button text in that language.
|
|
1020
|
+
|
|
1021
|
+
**Swedish special characters:** When generating Swedish text, ALWAYS use proper characters:
|
|
1022
|
+
- \xE5 (not a)
|
|
1023
|
+
- \xE4 (not a)
|
|
1024
|
+
- \xF6 (not o)
|
|
1025
|
+
|
|
1026
|
+
Examples: "L\xE4gg till", "\xC4ndra", "V\xE4lj anv\xE4ndare", "F\xF6rnamn", "Efternamn", "Spara \xE4ndringar"
|
|
1018
1027
|
|
|
1019
|
-
|
|
1028
|
+
**Code stays in English:** Variable names, function names, and code comments should remain in English for maintainability.
|
|
1029
|
+
|
|
1030
|
+
${h}
|
|
1020
1031
|
|
|
1021
1032
|
${m}
|
|
1022
1033
|
|
|
1034
|
+
${f}
|
|
1035
|
+
|
|
1023
1036
|
${O}
|
|
1024
1037
|
|
|
1025
1038
|
${T}
|
|
1026
1039
|
|
|
1027
|
-
${
|
|
1028
|
-
`}function Jt(e){let t=
|
|
1040
|
+
${_}
|
|
1041
|
+
`}function Jt(e){let t=Se(e),n=H(e),s=Y(e),o=ke(e),r=oe(e),i=[];o&&i.push(`- Backend: "${o}/{Entity}/"`),r&&i.push(`- Frontend: "${r}/{entity}/"`);let l=e.techStack?.patterns?.length?`
|
|
1029
1042
|
**Key patterns:**
|
|
1030
|
-
${e.techStack.patterns.map(
|
|
1031
|
-
`)}`:"",
|
|
1043
|
+
${e.techStack.patterns.map(f=>`- ${f}`).join(`
|
|
1044
|
+
`)}`:"",m=e.techStack?.backend?.some(f=>f.includes(".NET")||f.includes("C#"))??!1?`
|
|
1032
1045
|
|
|
1033
1046
|
**\u26A0\uFE0F User entity exists!** ASP.NET Identity "ApplicationUser" at "Features/Users/" - don't scaffold User, just relate to it.`:"";return`<tech-stack>
|
|
1034
1047
|
${t}
|
|
@@ -1036,7 +1049,7 @@ ${i.length>0?`
|
|
|
1036
1049
|
**Architecture:** Feature folders
|
|
1037
1050
|
${i.join(`
|
|
1038
1051
|
`)}`:""}
|
|
1039
|
-
${l}${
|
|
1052
|
+
${l}${m}
|
|
1040
1053
|
</tech-stack>`}function Xt(e,t){return t?`<environment>
|
|
1041
1054
|
**LOCAL MODE - User manages their own services.**
|
|
1042
1055
|
|
|
@@ -1058,7 +1071,7 @@ The user is responsible for:
|
|
|
1058
1071
|
</environment>`:`<environment>
|
|
1059
1072
|
Docker container with pre-started services:
|
|
1060
1073
|
|
|
1061
|
-
${
|
|
1074
|
+
${_e(e)}
|
|
1062
1075
|
${e.tunnel?.enabled?"| Tunnel | - | - | Exposes frontend to user |":""}
|
|
1063
1076
|
|
|
1064
1077
|
User sees app through **Cloudflare tunnel** (live preview in browser).
|
|
@@ -1334,21 +1347,21 @@ You have access to **Playwright MCP** tools to verify the UI works correctly aft
|
|
|
1334
1347
|
- **Multi-tab apps**: Use \`browser_tabs\` to manage multiple windows/tabs in legacy applications
|
|
1335
1348
|
- **Large snapshots**: For pages with huge dropdowns (e.g., country lists), use \`browser_evaluate\` to collapse them first
|
|
1336
1349
|
- ${t?"In local mode, the browser window will be visible to you":"In container mode, browser runs headless"}
|
|
1337
|
-
</ui-verification>`}function ft(e){return e&&e.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,"\uFFFD")}var sn=null;function gt(){return ft(sn)}import*as V from"path";import*as Be from"fs";import{fileURLToPath as an}from"url";var cn=Ue(),$e=V.dirname(an(import.meta.url)),mt=[V.resolve($e,"../mcps/stdio-server.js"),V.resolve($e,"./adapters/mcps/stdio-server.js")],se=mt.find(e=>Be.existsSync(e))||mt[0];console.log("[MCP] Stdio server path resolved:",se);console.log("[MCP] __dirname:",$e);console.log("[MCP] File exists:",Be.existsSync(se));async function ve(e,t={}){let{model:n,sessionId:s=null,projectId:o=null,apiUrl:r=null,callbacks:i={},debugLog:l,abortController:h,debugMode:
|
|
1350
|
+
</ui-verification>`}function ft(e){return e&&e.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,"\uFFFD")}var sn=null;function gt(){return ft(sn)}import*as V from"path";import*as Be from"fs";import{fileURLToPath as an}from"url";var cn=Ue(),$e=V.dirname(an(import.meta.url)),mt=[V.resolve($e,"../mcps/stdio-server.js"),V.resolve($e,"./adapters/mcps/stdio-server.js")],se=mt.find(e=>Be.existsSync(e))||mt[0];console.log("[MCP] Stdio server path resolved:",se);console.log("[MCP] __dirname:",$e);console.log("[MCP] File exists:",Be.existsSync(se));async function ve(e,t={}){let{model:n,sessionId:s=null,projectId:o=null,apiUrl:r=null,callbacks:i={},debugLog:l,abortController:h,debugMode:m=!1,isLocal:f=process.env.LOCAL_MODE==="true",projectConfig:v=G(process.env.WORKSPACE_DIR||process.cwd()),useDefaultStack:T=!0}=t,_=lt(l),O=new Set,y=s||"",I,U,x,$=!1,ee=!1,N=process.env.WORKSPACE_DIR||process.cwd();console.log("[MCP] Configuring agent-planning stdio server:"),console.log("[MCP] Command: node"),console.log("[MCP] Args:",[se]),console.log("[MCP] Env: API_URL=",process.env.API_URL||"http://localhost:5338",", PROJECT_ID=",o||process.env.PROJECT_ID||"",", PROJECT_KEY=",process.env.PROJECT_KEY?"***set***":"(not set)",", WORKSPACE_DIR=",N),console.log("[MCP] File exists check:",se);try{for await(let u of rn({prompt:e,options:{abortController:h,cwd:N,resume:s??void 0,allowedTools:["Bash","Read","Write","Edit","MultiEdit","Glob","Grep","LS","WebFetch","NotebookRead","NotebookEdit","Skill","mcp__agent-insights__check_backend_build","mcp__agent-insights__check_frontend_types","mcp__agent-insights__check_service_health",...f?[]:["mcp__agent-insights__stop_services","mcp__agent-insights__start_services","mcp__agent-insights__restart_services"],"mcp__agent-planning__start_question_session","mcp__agent-planning__ask_question","mcp__agent-planning__end_question_session","mcp__agent-planning__save_specification","mcp__agent-planning__list_specifications","mcp__agent-planning__read_specification","mcp__agent-planning__delete_specification","mcp__agent-planning__report_learning","mcp__agent-planning__report_bug","mcp__agent-planning__report_debug_insight","mcp__agent-planning__get_kanban_board","mcp__agent-planning__get_column_cards","mcp__agent-planning__get_card_details","mcp__agent-planning__create_kanban_card","mcp__agent-planning__update_kanban_card","mcp__agent-planning__move_kanban_card","mcp__agent-planning__delete_kanban_card","mcp__agent-planning__create_subtask","mcp__agent-planning__toggle_subtask","mcp__agent-planning__delete_subtask","mcp__agent-planning__get_project_prompt","mcp__agent-planning__update_project_prompt","mcp__agent-planning__create_command","mcp__agent-planning__get_test_suites","mcp__agent-planning__get_test_suite_details","mcp__agent-planning__get_test_details","mcp__agent-planning__run_test","mcp__agent-planning__run_test_suite","mcp__agent-planning__report_test_status","mcp__agent-planning__create_test_suite","mcp__agent-planning__create_test","mcp__agent-planning__update_test","mcp__agent-planning__reference_projects_list","mcp__agent-planning__reference_project_tree","mcp__agent-planning__reference_project_read_file","mcp__agent-planning__reference_project_download","mcp__agent-planning__reference_project_update_status","mcp__playwright__browser_navigate","mcp__playwright__browser_snapshot","mcp__playwright__browser_take_screenshot","mcp__playwright__browser_click","mcp__playwright__browser_type","mcp__playwright__browser_scroll_down","mcp__playwright__browser_scroll_up","mcp__playwright__browser_go_back","mcp__playwright__browser_go_forward","mcp__playwright__browser_wait","mcp__playwright__browser_fill_form","mcp__playwright__browser_select_option","mcp__playwright__browser_press_key","mcp__playwright__browser_hover","mcp__playwright__browser_drag","mcp__playwright__browser_file_upload","mcp__playwright__browser_handle_dialog","mcp__playwright__browser_close","mcp__playwright__browser_resize","mcp__playwright__browser_console_messages","mcp__playwright__browser_network_requests","mcp__playwright__browser_screen_capture","mcp__playwright__browser_screen_move_mouse","mcp__playwright__browser_screen_click","mcp__playwright__browser_screen_drag","mcp__playwright__browser_screen_type","mcp__playwright__browser_evaluate","mcp__playwright__browser_run_code","mcp__playwright__browser_tabs","mcp__playwright__browser_tab_list","mcp__playwright__browser_tab_new","mcp__playwright__browser_tab_select","mcp__playwright__browser_tab_close"],model:n,mcpServers:{"agent-insights":cn,"agent-planning":{type:"stdio",command:"node",args:[se],env:{WORKSPACE_DIR:N,API_URL:r||process.env.API_URL||process.env.MASTER_URL||"http://localhost:5338",PROJECT_ID:o||process.env.PROJECT_ID||"",PROJECT_KEY:process.env.PROJECT_KEY||""}},playwright:{type:"stdio",command:"npx",args:["@playwright/mcp@latest",...f?[]:["--browser=firefox"],"--caps=vision",`--user-data-dir=${V.join(N,".playwright-data")}`,...f?[]:["--headless"]]}},settingSources:["project"],disallowedTools:["AskUserQuestion","TodoWrite","EnterPlanMode"],agents:T?{scaffolding:Ne,debugger:me,planning:he,backend:Le,frontend:Me,"project-context":ye}:{debugger:me,planning:he,"project-context":ye},systemPrompt:ut({debugMode:m,projectPrompt:gt(),isLocal:f,projectConfig:v,useDefaultStack:T})}}))if(!(!u.uuid||O.has(u.uuid))){switch(O.add(u.uuid),_?.log(u),i.onRawMessage?.(u),u.type){case"assistant":if(u.message?.content)for(let A of u.message.content)A.type==="text"?i.onAssistantText?.(A.text):A.type==="tool_use"?i.onAssistantToolUse?.(A.name,A.input):A.type==="tool_result"&&i.onAssistantToolResult?.(A.tool_use_id,A.content);break;case"user":i.onUserMessage?.(u);break;case"result":if(u.subtype==="success")I=u.result,U=u.total_cost_usd,i.onResult?.(u.result,u.total_cost_usd);else{let A=`Agent error: ${u.subtype}`;x=A,i.onError?.(A)}ee=!0;break;case"system":switch(u.subtype){case"init":y=u.session_id,_?.setSessionId(u.session_id),i.onSystemInit?.(u.session_id);break;case"status":i.onSystemStatus?.(JSON.stringify(u));break;case"compact_boundary":break;case"hook_response":break}break;case"stream_event":i.onStreamEvent?.(u);break;case"tool_progress":i.onToolProgress?.(u);break;case"auth_status":i.onAuthStatus?.(u);break}if(ee)break}}catch(u){u instanceof Error&&u.name==="AbortError"||u instanceof Error&&u.message.includes("aborted by user")?($=!0,i.onAborted?.()):(x=u instanceof Error?u.message:String(u),i.onError?.(x))}finally{_?.stop()}return console.log("after try"),{sessionId:y,result:I,cost:U,error:x,aborted:$}}function Ce(e){return`User answered the questions:
|
|
1338
1351
|
|
|
1339
1352
|
${Object.entries(e).map(([n,s])=>{let o=Array.isArray(s)?s.join(", "):s;return`${n}: ${o}`}).join(`
|
|
1340
|
-
`)}`}import{spawn as ht,execSync as z}from"child_process";import*as
|
|
1341
|
-
`).filter(Boolean);console.log(`[Services] Killing ${
|
|
1342
|
-
`);P=D.pop()||"";for(let F of D)F&&ln(F)&&o&&o(F)};x.stdout?.on("data",C),x.stderr?.on("data",C),x.on("exit",w=>{c.end(),w!==0&&w!==null&&o&&o(`Process exited with code ${w}`)}),x.unref()},Ye=async()=>{if(!
|
|
1353
|
+
`)}`}import{spawn as ht,execSync as z}from"child_process";import*as S from"fs";import*as yt from"http";import*as Q from"path";function Te(e){let{workspaceDir:t,apiPort:n,webPort:s,onBackendError:o,onFrontendError:r,projectConfig:i=G(t)}=e,l=n??i.services?.backend?.port??5338,h=s??i.services?.frontend?.port??5173,m=H(i),f=Y(i),v=i.services?.backend?.startCommand??"dotnet run",T=i.services?.backend?.buildCommand??"dotnet build",_=i.services?.backend?.typecheckCommand??"dotnet build --no-restore",O=i.services?.frontend?.startCommand??"npm run dev",y=i.services?.frontend?.typecheckCommand??"npx tsc -p tsconfig.app.json --noEmit",I=i.services?.backend?.logFile??"/tmp/api.log",U=i.services?.frontend?.logFile??"/tmp/web.log",x=null,$=null,ee=p=>new Promise(c=>{let g=setTimeout(()=>c(!1),3e3);yt.get(`http://localhost:${p}`,P=>{clearTimeout(g),c(P.statusCode!==void 0&&P.statusCode<500)}).on("error",()=>{clearTimeout(g),c(!1)})}),N=p=>{try{let c=z(`lsof -ti:${p} 2>/dev/null || true`,{encoding:"utf-8"}).trim();if(c){let g=c.split(`
|
|
1354
|
+
`).filter(Boolean);console.log(`[Services] Killing ${g.length} process(es) on port ${p}: ${g.join(", ")}`);for(let b of g)try{process.kill(parseInt(b,10),"SIGKILL")}catch{}}}catch{try{z(`fuser -k ${p}/tcp 2>/dev/null || true`,{encoding:"utf-8"})}catch{}}},u=(p,c)=>new Promise(g=>{if(!p||!p.pid){g();return}console.log(`[Services] Stopping ${c} (PID: ${p.pid})...`);try{process.kill(-p.pid,"SIGTERM")}catch{try{p.kill("SIGTERM")}catch{}}setTimeout(g,500)}),A=async()=>{if(!m||!S.existsSync(m)){console.log("[Services] No backend found, skipping backend start");return}N(l),console.log(`[Services] Starting backend with: ${v}...`);let p=Q.dirname(I);S.existsSync(p)||S.mkdirSync(p,{recursive:!0});let c=S.createWriteStream(I,{flags:"a"}),[g,...b]=v.split(" ");x=ht(g,b,{cwd:m,stdio:["ignore","pipe","pipe"],detached:!0,shell:!0});let P="",C=w=>{let B=w.toString();c.write(B);let D=(P+B).split(`
|
|
1355
|
+
`);P=D.pop()||"";for(let F of D)F&&ln(F)&&o&&o(F)};x.stdout?.on("data",C),x.stderr?.on("data",C),x.on("exit",w=>{c.end(),w!==0&&w!==null&&o&&o(`Process exited with code ${w}`)}),x.unref()},Ye=async()=>{if(!f||!S.existsSync(Q.join(f,"package.json"))){console.log("[Services] No frontend found, skipping frontend start");return}N(h),console.log(`[Services] Starting frontend with: ${O}...`);let p=Q.dirname(U);S.existsSync(p)||S.mkdirSync(p,{recursive:!0});let[c,...g]=O.split(" ");$=ht(c,g,{cwd:f,stdio:["ignore",S.openSync(U,"w"),S.openSync(U,"w")],detached:!0,shell:!0}),$.unref()},Qe=async()=>{await u(x,"backend"),x=null,N(l)},Ke=async()=>{await u($,"frontend"),$=null,N(h)},De=async()=>{let[p,c]=await Promise.all([ee(l),ee(h)]);return{api:p,web:c}},Ve=async(p=15e3)=>{let c=Date.now(),g=1e3,b=0,P=null,C=null;for(console.log(`[Services] Waiting for health (timeout: ${p}ms)...`);Date.now()-c<p;){b++;let D=await De(),F=Date.now()-c;if(D.web&&C===null&&(C=F,console.log(`[Services] \u2713 Frontend (Vite) ready after ${F}ms`)),D.api&&P===null&&(P=F,console.log(`[Services] \u2713 Backend (dotnet) ready after ${F}ms`)),console.log(`[Services] Health poll #${b} (${F}ms): api=${D.api}, web=${D.web}`),D.api&&D.web)return console.log(`[Services] \u2713 Both services healthy after ${F}ms (${b} polls)`),D;await new Promise(jt=>setTimeout(jt,g))}let w=await De(),B=Date.now()-c;return w.web&&C===null&&console.log(`[Services] \u2713 Frontend (Vite) ready after ${B}ms`),w.api&&P===null&&console.log(`[Services] \u2713 Backend (dotnet) ready after ${B}ms`),console.log(`[Services] \u26A0 Health timeout after ${B}ms: api=${w.api}, web=${w.web}`),w},Lt=async p=>{console.log(`[Services] Restarting ${p}...`);let c={success:!0},g=p==="backend"||p==="both",b=p==="frontend"||p==="both";if(g&&await Qe(),b&&await Ke(),g&&m&&S.existsSync(m)){console.log(`[Services] Building backend with: ${T}...`);try{z(T,{cwd:m,stdio:"pipe",timeout:12e4,encoding:"utf-8"}),console.log("[Services] \u2713 Backend build succeeded")}catch(C){let w=C;c.success=!1,c.backendError=w.stderr||w.stdout||"Build failed",console.error("[Services] \u2717 Backend build failed")}}if(b&&f&&S.existsSync(Q.join(f,"package.json"))){console.log(`[Services] Type-checking frontend with: ${y}...`);try{z(y,{cwd:f,stdio:"pipe",timeout:6e4,encoding:"utf-8"}),console.log("[Services] \u2713 Frontend types OK")}catch(C){let w=C;c.success=!1,c.frontendError=w.stderr||w.stdout||"Type check failed",console.error("[Services] \u2717 Frontend type check failed")}}console.log(`[Services] Starting ${p}...`),g&&await A(),b&&await Ye();let P=await Ve(1e4);if(g&&!P.api){c.success=!1;let C=await Xe("backend",20),w=C.length>0?`
|
|
1343
1356
|
Recent logs:
|
|
1344
1357
|
${C.join(`
|
|
1345
|
-
`)}`:"";c.backendError||(c.backendError=`Backend failed to start.${w}`)}return b&&!P.web&&(c.success=!1,c.frontendError||(c.frontendError="Frontend failed to start")),c},ze=async()=>{if(!
|
|
1346
|
-
`).filter(Boolean)}catch(b){return[`Error reading logs: ${b instanceof Error?b.message:String(b)}`]}};return{startBackend:A,startFrontend:Ye,stopBackend:Qe,stopFrontend:Ke,restartServices:Lt,checkHealth:De,waitForHealth:Ve,getProcesses:()=>({backend:x,frontend:$}),checkBackendBuild:ze,checkFrontendTypes:Je,runTypeChecks:Mt,tailLogs:Xe,checkSwaggerEndpoints:async p=>{let c=Q.join(t,"swagger.json");if(!
|
|
1358
|
+
`)}`:"";c.backendError||(c.backendError=`Backend failed to start.${w}`)}return b&&!P.web&&(c.success=!1,c.frontendError||(c.frontendError="Frontend failed to start")),c},ze=async()=>{if(!m||!S.existsSync(m))return{success:!0};try{return z(_,{cwd:m,stdio:"pipe",timeout:12e4,encoding:"utf-8"}),{success:!0}}catch(p){let c=p;return{success:!1,errors:c.stdout||c.stderr||"Build failed"}}},Je=async()=>{if(!f||!S.existsSync(Q.join(f,"package.json")))return{success:!0};try{return z(y,{cwd:f,stdio:"pipe",timeout:6e4,encoding:"utf-8"}),{success:!0}}catch(p){let c=p;return{success:!1,errors:c.stdout||c.stderr||"Type check failed"}}},Mt=async()=>{let[p,c]=await Promise.all([ze(),Je()]);return{backend:p,frontend:c}},Xe=async(p,c)=>{let g=p==="backend"?I:U;if(!S.existsSync(g))return[`Log file not found: ${g}`];try{return z(`tail -n ${c} ${g}`,{encoding:"utf-8"}).split(`
|
|
1359
|
+
`).filter(Boolean)}catch(b){return[`Error reading logs: ${b instanceof Error?b.message:String(b)}`]}};return{startBackend:A,startFrontend:Ye,stopBackend:Qe,stopFrontend:Ke,restartServices:Lt,checkHealth:De,waitForHealth:Ve,getProcesses:()=>({backend:x,frontend:$}),checkBackendBuild:ze,checkFrontendTypes:Je,runTypeChecks:Mt,tailLogs:Xe,checkSwaggerEndpoints:async p=>{let c=Q.join(t,"swagger.json");if(!S.existsSync(c))return{foundEndpoints:["Error: swagger.json not found"],totalEndpoints:0};try{let g=JSON.parse(S.readFileSync(c,"utf-8")),b=Object.keys(g.paths||{}),P=[];for(let C of b)if(C.toLowerCase().includes(p.toLowerCase())){let w=Object.keys(g.paths[C]);for(let B of w)P.push(`${B.toUpperCase()} ${C}`)}return{foundEndpoints:P,totalEndpoints:b.length}}catch(g){return{foundEndpoints:[`Error parsing swagger.json: ${g instanceof Error?g.message:String(g)}`],totalEndpoints:0}}}}}function ln(e){let t=e.toLowerCase();return t.includes("exception")||t.includes("fail:")||t.includes("[err]")||t.includes("[error]")}import{execSync as J}from"child_process";function Ee(e){let t=async()=>{try{return J("git status --porcelain",{cwd:e,encoding:"utf-8"}).trim().length>0}catch{return!1}},n=async()=>{try{return J("git branch --show-current",{cwd:e,encoding:"utf-8"}).trim()}catch{return"unknown"}};return{hasChanges:t,commitAndPush:async(o,r)=>{try{if(!await t())return{success:!0,noChanges:!0};let i=o.length>60?o.substring(0,60)+"...":o,h=`${r?"[agent] (partial)":"[agent]"} ${i}`;J("git add -A",{cwd:e}),J(`git commit -m "${h.replace(/"/g,'\\"')}"`,{cwd:e,encoding:"utf-8"});let m=J("git rev-parse --short HEAD",{cwd:e,encoding:"utf-8"}).trim();try{J("git push",{cwd:e,stdio:"pipe"})}catch(f){let v=f instanceof Error?f.message:String(f);if(v.includes("no upstream branch")){let T=await n();J(`git push --set-upstream origin ${T}`,{cwd:e,stdio:"pipe"})}else return console.error("[Git] Push failed:",v),{success:!1,commitHash:m,commitSucceeded:!0,pushFailed:!0,error:v}}return{success:!0,commitHash:m}}catch(i){let l=i instanceof Error?i.message:String(i);return console.error("[Git] Error:",l),{success:!1,error:l}}},getCurrentBranch:n}}import ie from"inquirer";var re="\u2192 Other (type custom answer)";async function Ae(e){let t={};for(let n of e){let s=await pn(n);t[n.id]=s}return t}async function pn(e){return e.multiSelect&&e.options.length>0?dn(e):e.options.length>0?un(e):fn(e)}async function dn(e){let t=[...e.options];e.allowFreeText&&t.push(re);let{selection:n}=await ie.prompt([{type:"checkbox",name:"selection",message:e.question,choices:t}]);if(n.includes(re)){let{customAnswer:s}=await ie.prompt([{type:"input",name:"customAnswer",message:"Enter your custom answer:"}]);return[...n.filter(r=>r!==re),s]}return n}async function un(e){let t=[...e.options];e.allowFreeText&&t.push(re);let{selection:n}=await ie.prompt([{type:"select",name:"selection",message:e.question,choices:t}]);if(n===re){let{customAnswer:s}=await ie.prompt([{type:"input",name:"customAnswer",message:"Enter your custom answer:"}]);return s}return n}async function fn(e){let{answer:t}=await ie.prompt([{type:"input",name:"answer",message:e.question}]);return t}import*as M from"fs";import*as Pe from"path";var gn=".sdd/cli-sessions.json";function qe(){return Pe.join(process.cwd(),gn)}function mn(){let e=Pe.dirname(qe());M.existsSync(e)||M.mkdirSync(e,{recursive:!0})}function We(){try{let e=qe();if(!M.existsSync(e))return[];let t=M.readFileSync(e,"utf-8");return JSON.parse(t).sort((s,o)=>new Date(o.lastUsedAt).getTime()-new Date(s.lastUsedAt).getTime())}catch{return[]}}function bt(e){mn();let t=We(),n=t.findIndex(o=>o.sessionId===e.sessionId);n>=0?t[n]={...t[n],lastUsedAt:e.lastUsedAt,turnCount:e.turnCount}:t.unshift(e);let s=t.slice(0,20);M.writeFileSync(qe(),JSON.stringify(s,null,2))}function wt(e){let t=e.firstPrompt.length>50?e.firstPrompt.slice(0,50)+"...":e.firstPrompt,n=new Date(e.lastUsedAt),s=hn(n);return`${t} (${s}, ${e.turnCount} turns)`}function hn(e){let n=new Date().getTime()-e.getTime(),s=Math.floor(n/6e4),o=Math.floor(n/36e5),r=Math.floor(n/864e5);return s<1?"just now":s<60?`${s}m ago`:o<24?`${o}h ago`:r<7?`${r}d ago`:e.toLocaleDateString()}var kt={reset:"\x1B[0m",dim:"\x1B[2m",bold:"\x1B[1m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",red:"\x1B[31m",gray:"\x1B[90m"};function a(e,t){return`${kt[e]}${t}${kt.reset}`}var yn={Bash:"\u26A1",Read:"\u{1F4D6}",Write:"\u270F\uFE0F",Edit:"\u{1F527}",MultiEdit:"\u{1F527}",Glob:"\u{1F50D}",Grep:"\u{1F50E}",LS:"\u{1F4C1}",WebFetch:"\u{1F310}",NotebookRead:"\u{1F4D3}",NotebookEdit:"\u{1F4DD}",Task:"\u{1F4CB}",ask_questions:"\u2753",save_specification:"\u{1F4C4}",list_specifications:"\u{1F4CB}",read_specification:"\u{1F4D6}",delete_specification:"\u{1F5D1}\uFE0F",scaffold_entity:"\u{1F3D7}\uFE0F"};function bn(e){return yn[e]||"\u{1F527}"}function ae(e,t){return e.length<=t?e:e.slice(0,t-3)+"..."}function St(e){let t=e.split("/");return t.length<=3?e:".../"+t.slice(-2).join("/")}function Ge(e){console.log(`
|
|
1347
1360
|
${a("bold",a("cyan","\u2550".repeat(60)))}`),console.log(a("bold",` ${e}`)),console.log(`${a("bold",a("cyan","\u2550".repeat(60)))}
|
|
1348
|
-
`)}function
|
|
1361
|
+
`)}function _t(e){let t=e.split(`
|
|
1349
1362
|
`);for(let n of t)n.trim()?console.log(` ${n}`):console.log()}function vt(e,t){if(t&&typeof t=="object"){let s=t;if(e==="Task"&&s.subagent_type){let o=String(s.subagent_type);if(console.log(`
|
|
1350
1363
|
\u{1F916} ${a("bold",a("magenta",`Delegating to @${o}`))}`),s.prompt){let r=ae(String(s.prompt),100);console.log(` ${a("dim","\u2192")} ${a("gray",r)}`)}return}}let n=bn(e);if(console.log(`
|
|
1351
|
-
${n} ${a("bold",a("blue",e))}`),t&&typeof t=="object"){let s=t;if(e==="Bash"&&s.command)console.log(` ${a("dim","$")} ${a("yellow",ae(String(s.command),80))}`);else if((e==="Read"||e==="Write"||e==="Edit")&&s.file_path)console.log(` ${a("dim","\u2192")} ${a("cyan",
|
|
1364
|
+
${n} ${a("bold",a("blue",e))}`),t&&typeof t=="object"){let s=t;if(e==="Bash"&&s.command)console.log(` ${a("dim","$")} ${a("yellow",ae(String(s.command),80))}`);else if((e==="Read"||e==="Write"||e==="Edit")&&s.file_path)console.log(` ${a("dim","\u2192")} ${a("cyan",St(String(s.file_path)))}`);else if(e==="Grep"&&s.pattern)console.log(` ${a("dim","/")}${a("magenta",String(s.pattern))}${a("dim","/")}`);else if(e==="LS"&&s.path)console.log(` ${a("dim","\u2192")} ${a("cyan",St(String(s.path)))}`);else if(e==="scaffold_entity"&&s.name)console.log(` ${a("dim","\u2192")} Creating entity: ${a("green",String(s.name))}`);else if(e==="ask_questions"&&s.questions){let o=s.questions;console.log(` ${a("dim","\u2192")} ${o.length} question(s)`)}else e==="save_specification"&&s.name&&console.log(` ${a("dim","\u2192")} ${a("green",String(s.name))}`)}}function Ct(e,t){console.log(`
|
|
1352
1365
|
${a("bold",a("green","\u2713 Complete"))}`),console.log(` ${a("dim","Result:")} ${ae(e,100)}`),console.log(` ${a("dim","Cost:")} ${a("yellow",`$${t.toFixed(4)}`)}`)}function Tt(e){console.log(`
|
|
1353
1366
|
${a("bold",a("red","\u2717 Error"))}`),console.log(` ${e}`)}function Et(){console.log(`
|
|
1354
1367
|
${a("bold",a("yellow","\u23F9\uFE0F Aborted"))}`),console.log(` ${a("dim","Session stopped by user. Progress has been preserved.")}`)}function At(){console.log(`
|
|
@@ -1358,8 +1371,8 @@ ${a("dim","\u2192 Answers sent, agent continuing...")}
|
|
|
1358
1371
|
`)}function xe(){console.log(a("dim",`
|
|
1359
1372
|
`+"\u2500".repeat(50)+`
|
|
1360
1373
|
`))}function xt(e,t){let n="";if(typeof t=="string")n=ae(t,100);else if(Array.isArray(t)){let s=t.find(o=>typeof o=="object"&&o!==null&&o.type==="text");if(s&&typeof s=="object"){let o=s.text;if(typeof o=="string"){let r=o.split(`
|
|
1361
|
-
`).filter(i=>i.trim());r.length>3?n=r.slice(0,3).join(" \u21B5 ")+` (+${r.length-3} more lines)`:n=r.join(" \u21B5 "),n=ae(n,100)}}}console.log(n?` ${a("dim","\u2190")} ${a("gray",n)}`:` ${a("dim","\u2190")} ${a("green","done")}`)}function Rt(e){return{onAssistantText:
|
|
1362
|
-
\u{1F4C2} Resuming session: ${s.firstPrompt.slice(0,50)}...`);let{prompt:o}=await R.prompt([{type:"input",name:"prompt",message:"Continue with:"}]);return{sessionId:s.sessionId,firstPrompt:s.firstPrompt,isResume:!0,existingTurnCount:s.turnCount||0}}async function Ie(){let{action:e}=await R.prompt([{type:"select",name:"action",message:"What would you like to do?",choices:[{name:"\u{1F4DD} Send follow-up message",value:"followup"},{name:"\u{1F6AA} Exit",value:"exit"}]}]);if(e==="exit")return null;let{prompt:t}=await R.prompt([{type:"input",name:"prompt",message:"Your message:"}]);return t}async function Dt(){let{action:e}=await R.prompt([{type:"select",name:"action",message:"What would you like to do?",choices:[{name:"Continue with a new prompt",value:"continue"},{name:"Exit (session saved)",value:"exit"}]}]);return e}async function Ft(){let e=process.env.API_URL||process.env.MASTER_URL,t=process.env.PROJECT_ID,n=
|
|
1374
|
+
`).filter(i=>i.trim());r.length>3?n=r.slice(0,3).join(" \u21B5 ")+` (+${r.length-3} more lines)`:n=r.join(" \u21B5 "),n=ae(n,100)}}}console.log(n?` ${a("dim","\u2190")} ${a("gray",n)}`:` ${a("dim","\u2190")} ${a("green","done")}`)}function Rt(e){return{onAssistantText:_t,onAssistantToolUse:(t,n)=>{if(vt(t,n),["Write","Edit","MultiEdit"].includes(t)){let s=n,o=s.file_path||s.target_file;o&&e.onFileModified(o)}},onAssistantToolResult:xt,onResult:(t,n)=>{e.onCostUpdate(n),Ct(t,n)},onError:Tt,onAborted:Et,onSystemInit:t=>{e.sessionId||(e.onSessionInit(t),console.log(` Session: ${t.slice(0,12)}...`))}}}import R from"inquirer";import*as j from"fs";import*as Re from"path";var{Separator:wn}=R,kn=".sdd/api-config.json";function He(){return Re.join(process.cwd(),kn)}function Sn(){let e=Re.dirname(He());j.existsSync(e)||j.mkdirSync(e,{recursive:!0})}function _n(){try{let e=He();if(!j.existsSync(e))return null;let t=j.readFileSync(e,"utf-8");return JSON.parse(t)}catch{return null}}function vn(e){Sn();let t={...e,lastUsedAt:new Date().toISOString()};j.writeFileSync(He(),JSON.stringify(t,null,2))}async function It(){let{options:e}=await R.prompt([{type:"checkbox",name:"options",message:"Session options:",choices:[{name:"\u{1F50D} Auto-typecheck (self-correcting mode)",value:"typecheck"},{name:"\u{1F4E6} Auto-commit & push",value:"autoCommit"}]}]),{model:t}=await R.prompt([{type:"list",name:"model",message:"Select Claude model:",choices:[{name:"\u{1F916} Auto (let Claude choose)",value:"auto"},{name:"\u{1F9E0} Opus 4.5 (most capable)",value:"claude-opus-4-5-20251101"},{name:"\u26A1 Sonnet 4.5 (fast & capable)",value:"claude-sonnet-4-5-20250929"},{name:"\u{1F4A8} Haiku 4.5 (fastest)",value:"claude-haiku-4-5-20251001"}],default:"auto"}]);return{typecheck:e.includes("typecheck"),autoCommit:e.includes("autoCommit"),model:t}}async function Ot(){let e=We();if(e.length===0){let{prompt:r}=await R.prompt([{type:"input",name:"prompt",message:"What would you like to build?"}]);return{sessionId:null,firstPrompt:r,isResume:!1,existingTurnCount:0}}let t=[{name:"\u{1F195} Start new session",value:"new"},new wn("\u2500\u2500\u2500 Previous Sessions \u2500\u2500\u2500"),...e.map(r=>({name:wt(r),value:r.sessionId}))],{selection:n}=await R.prompt([{type:"select",name:"selection",message:"Select a session:",choices:t}]);if(n==="new"){let{prompt:r}=await R.prompt([{type:"input",name:"prompt",message:"What would you like to build?"}]);return{sessionId:null,firstPrompt:r,isResume:!1,existingTurnCount:0}}let s=e.find(r=>r.sessionId===n);console.log(`
|
|
1375
|
+
\u{1F4C2} Resuming session: ${s.firstPrompt.slice(0,50)}...`);let{prompt:o}=await R.prompt([{type:"input",name:"prompt",message:"Continue with:"}]);return{sessionId:s.sessionId,firstPrompt:s.firstPrompt,isResume:!0,existingTurnCount:s.turnCount||0}}async function Ie(){let{action:e}=await R.prompt([{type:"select",name:"action",message:"What would you like to do?",choices:[{name:"\u{1F4DD} Send follow-up message",value:"followup"},{name:"\u{1F6AA} Exit",value:"exit"}]}]);if(e==="exit")return null;let{prompt:t}=await R.prompt([{type:"input",name:"prompt",message:"Your message:"}]);return t}async function Dt(){let{action:e}=await R.prompt([{type:"select",name:"action",message:"What would you like to do?",choices:[{name:"Continue with a new prompt",value:"continue"},{name:"Exit (session saved)",value:"exit"}]}]);return e}async function Ft(){let e=process.env.API_URL||process.env.MASTER_URL,t=process.env.PROJECT_ID,n=_n();if(n&&!e&&!t){console.log(`
|
|
1363
1376
|
\u{1F4CB} Previous API Configuration Found
|
|
1364
1377
|
`),console.log(` \u{1F517} API URL: ${n.apiUrl}`),console.log(` \u{1F194} Project ID: ${n.projectId}`);let{action:r}=await R.prompt([{type:"select",name:"action",message:"What would you like to do?",choices:[{name:"\u2705 Proceed with saved configuration",value:"proceed"},{name:"\u{1F504} Override with new configuration",value:"override"}]}]);if(r==="proceed")return{apiUrl:n.apiUrl,projectId:n.projectId}}let s=await R.prompt([{type:"input",name:"apiUrl",message:"API URL (backend endpoint):",default:e||n?.apiUrl||"http://localhost:5338",validate:r=>{if(!r.trim())return"API URL is required";try{return new URL(r),!0}catch{return"Please enter a valid URL"}}},{type:"input",name:"projectId",message:"Project ID:",default:t||n?.projectId,validate:r=>r.trim()?!0:"Project ID is required"}]),o={apiUrl:s.apiUrl,projectId:s.projectId};return vn(o),o}async function Ut(e,t){if(t.size===0)return console.log(`
|
|
1365
1378
|
${a("dim","\u23ED\uFE0F No code changes, skipping type checks")}`),{passed:!0};let n=[...t].some(l=>l.includes("dotnet-api")||l.endsWith(".cs")),s=[...t].some(l=>l.includes("backoffice-web")||l.endsWith(".ts")||l.endsWith(".tsx"));if(!n&&!s)return console.log(`
|
|
@@ -1384,9 +1397,9 @@ File paths like "src/..." are relative to the package directory shown in parenth
|
|
|
1384
1397
|
|
|
1385
1398
|
\u23F9\uFE0F Stopping agent...
|
|
1386
1399
|
`),ce.abort()):(console.log(`
|
|
1387
|
-
`),process.exit(0))});async function Oe(e,t={}){let{serviceManager:n,gitManager:s}=t,o=null,r,i,l=0,h=0,
|
|
1388
|
-
Turn ${l}`),xe();let T=new Set;ce=new AbortController;let
|
|
1389
|
-
`),await Dt()==="exit")break;let I=await Ie();if(!I)break;i=I;continue}if(console.log("after abort"),o){let y=te(o);if(y&&y.length>0){At();let I=await Ae(y);i=Ce(I),Pt();continue}}if(console.log("after pending questions"),
|
|
1400
|
+
`),process.exit(0))});async function Oe(e,t={}){let{serviceManager:n,gitManager:s}=t,o=null,r,i,l=0,h=0,m=t.typecheck??!1,f=t.autoCommit??!1,v=t.model;if(e)r=e,i=e;else{let T=await It();n&&(m=T.typecheck),s&&(f=T.autoCommit),t.skipModelPrompt||(v=T.model);let _=await Ot();o=_.sessionId,r=_.firstPrompt,l=_.existingTurnCount,i=_.isResume&&await Ie()||r}for(Cn(m,f,!!n,!!s,v);;){l++,console.log(`
|
|
1401
|
+
Turn ${l}`),xe();let T=new Set;ce=new AbortController;let _=await ve(i,{model:v==="auto"?void 0:v,sessionId:o,projectId:process.env.PROJECT_ID||null,debugLog:!0,abortController:ce,callbacks:Rt({sessionId:o,onSessionInit:y=>{o=y},onCostUpdate:y=>{h+=y},onFileModified:y=>{T.add(y)}})});if(console.log("after runAgent"),ce=null,_.sessionId&&(o=_.sessionId),_.cost&&(h=_.cost),console.log("after result"),En(o,r,l),console.log("after saveSessionIfNeeded"),_.aborted){if(await new Promise(U=>setTimeout(U,100)),xe(),h>0&&console.log(` \u{1F4B0} Session cost so far: $${h.toFixed(4)}
|
|
1402
|
+
`),await Dt()==="exit")break;let I=await Ie();if(!I)break;i=I;continue}if(console.log("after abort"),o){let y=te(o);if(y&&y.length>0){At();let I=await Ae(y);i=Ce(I),Pt();continue}}if(console.log("after pending questions"),m&&n){let y=await Ut(n,T);if(!y.passed&&y.errorPrompt){i=y.errorPrompt;continue}}if(console.log("after typecheck"),f&&s){let y=_.aborted||!!_.error;await Nt(s,r,y)}console.log("after auto-commit"),xe();let O=await Ie();if(!O)break;i=O}Tn(o,h)}function Cn(e,t,n,s,o){Ge("\u{1F680} Agent Started"),console.log(" \u{1F4DD} Debug logs \u2192 /tmp/agent-debug/"),o&&console.log(` \u{1F916} Model: ${o==="auto"?"Auto":o==="claude-opus-4-5-20251101"?"Opus 4.5":o==="claude-sonnet-4-5-20250929"?"Sonnet 4.5":o==="claude-haiku-4-5-20251001"?"Haiku 4.5":"Unknown"}`),e&&n&&console.log(" \u{1F50D} Auto-typecheck enabled (self-correcting mode)"),t&&s&&console.log(" \u{1F4E6} Auto-commit enabled"),console.log(` \u{1F4A1} Press Ctrl+C to stop the agent
|
|
1390
1403
|
`)}function Tn(e,t){Ge("\u2705 Session Complete"),e&&console.log(" \u{1F4BE} Session saved - you can resume it next time"),t>0&&console.log(` \u{1F4B0} Total cost: $${t.toFixed(4)}`),console.log()}function En(e,t,n){e&&bt({sessionId:e,firstPrompt:t,createdAt:new Date().toISOString(),lastUsedAt:new Date().toISOString(),turnCount:n})}process.env.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT=process.env.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT||"60000";async function An(){let e=process.env.API_URL||process.env.MASTER_URL,t=process.env.PROJECT_ID,n=e,s=t;if(!e||!t){console.log(`
|
|
1391
1404
|
\u{1F4CB} API Configuration Required
|
|
1392
|
-
`);let
|
|
1405
|
+
`);let m=await Ft();n=m.apiUrl,s=m.projectId}process.env.API_URL=n,process.env.PROJECT_ID=s;let o=process.env.WORKSPACE_DIR||process.cwd(),r=Te({workspaceDir:o}),i=Ee(o),l=ne(o);fe(r),ue(l),await Oe(void 0,{serviceManager:r,gitManager:i})}An().catch(e=>{console.error("Fatal error:",e),process.exit(1)});
|