glenn-code 1.0.17 → 1.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Glenn Code - Bundled and minified
2
2
  // (c) DNM Lab - All rights reserved
3
3
 
4
- import{createSdkMcpServer as Bt}from"@anthropic-ai/claude-agent-sdk";import{tool as pe}from"@anthropic-ai/claude-agent-sdk";import{z as le}from"zod";import*as de from"fs";import*as Ze from"path";var M=null;function ue(e){M=e}function Fe(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}var Rn=pe("save_specification",`Save a specification to the database.
4
+ import{createSdkMcpServer as Bt}from"@anthropic-ai/claude-agent-sdk";import{tool as pe}from"@anthropic-ai/claude-agent-sdk";import{z as le}from"zod";import*as de from"fs";import*as Ze from"path";var L=null;function ue(e){L=e}function Fe(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}var In=pe("save_specification",`Save a specification to the database.
5
5
 
6
6
  IMPORTANT: First write the specification content to a file using the Write tool, then call this with the file path.
7
7
 
@@ -12,17 +12,17 @@ Workflow:
12
12
 
13
13
  Example:
14
14
  1. Write tool \u2192 save to /tmp/user-auth-spec.md
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(!M)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 M.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(!M)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let t=Fe(e.name),n=await M.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(!M)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let e=await M.listSpecifications();return e.length===0?{content:[{type:"text",text:"\u{1F4CB} No specifications found."}]}:{content:[{type:"text",text:`\u{1F4CB} Specifications:
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(!M)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let t=Fe(e.name);return await M.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 w=null;function ge(e){w=e}var et=q("restart_services",`Rebuild and restart services. Call this after making backend changes (.cs files) to apply them.
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 _=null;function fe(e){_=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(!w)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 w.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:
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(!_)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 _.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,61 +30,84 @@ ${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(!w)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 w.stopBackend(),s&&await w.stopFrontend();let o=5e3,r=Date.now(),i=!n,l=!s;for(;Date.now()-r<o;){let m=await w.checkHealth();if(n&&!m.api&&(i=!0),s&&!m.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.
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(!_)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 _.stopBackend(),s&&await _.stopFrontend();let o=5e3,r=Date.now(),i=!n,l=!s;for(;Date.now()-r<o;){let m=await _.checkHealth();if(n&&!m.api&&(i=!0),s&&!m.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(!w)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 w.startBackend(),s&&await w.startFrontend();let o=await w.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 f=[];return n&&!o.api&&f.push("Backend failed to start"),s&&!o.web&&f.push("Frontend failed to start"),{content:[{type:"text",text:`\u274C ${h} failed to start: ${f.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(!w)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let e=await w.checkHealth();return{content:[{type:"text",text:`Service Health:
38
+ Use target="backend" after running scaffold.`,{target:X.enum(["backend","frontend","both"]).default("backend").describe("Which service to start")},async e=>{if(!_)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 _.startBackend(),s&&await _.startFrontend();let o=await _.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 g=[];return n&&!o.api&&g.push("Backend failed to start"),s&&!o.web&&g.push("Frontend failed to start"),{content:[{type:"text",text:`\u274C ${h} failed to start: ${g.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(!_)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let e=await _.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(!w)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};console.log("[MCP] check_backend_build called");let e=await w.checkBackendBuild();return e.success?{content:[{type:"text",text:"\u2705 Backend build succeeded - no errors"}]}:{content:[{type:"text",text:`\u274C Backend build failed:
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(!_)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};console.log("[MCP] check_backend_build called");let e=await _.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(!w)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};console.log("[MCP] check_frontend_types called");let e=await w.checkFrontendTypes();return e.success?{content:[{type:"text",text:"\u2705 Frontend type check passed - no errors"}]}:{content:[{type:"text",text:`\u274C Frontend type errors:
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(!_)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};console.log("[MCP] check_frontend_types called");let e=await _.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
- ${e.errors}`}]}}),Ln=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.
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(!w)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=await w.tailLogs(e.target,e.lines);return{content:[{type:"text",text:`Last ${e.lines} lines of ${e.target} log:
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(!_)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=await _.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(!w)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=await w.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:
55
+ - pattern: string (e.g., "users", "api/reports")`,{pattern:X.string().describe("Text to search for in endpoint paths")},async e=>{if(!_)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=await _.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 P from"fs";import*as fe from"path";function ne(e){let t=fe.join(e,".sdd","specifications");function n(){P.existsSync(t)||P.mkdirSync(t,{recursive:!0})}function s(o){return fe.join(t,`${o}.spec.md`)}return{async saveSpecification(o,r){n(),P.writeFileSync(s(o),r,"utf-8")},async getSpecification(o){n();let r=s(o);return P.existsSync(r)?P.readFileSync(r,"utf-8"):null},async listSpecifications(){return n(),P.readdirSync(t).filter(r=>r.endsWith(".spec.md")).map(r=>{let i=P.readFileSync(fe.join(t,r),"utf-8"),l=i.match(/name: "([^"]+)"/),h=i.match(/status: (\w+)/),f=i.match(/version: "([^"]+)"/);return{slug:r.replace(".spec.md",""),name:l?.[1]||r,status:h?.[1]||"unknown",version:f?.[1]||"1.0.0"}})},async deleteSpecification(o){n();let r=s(o);return P.existsSync(r)?(P.unlinkSync(r),!0):!1},async specificationExists(o){return n(),P.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
- 1. Check existing features (quick ls)
60
- 2. Write ALL entities to ONE /tmp/scaffold.json (big bang, no phasing!)
61
- 3. Run scaffold CLI ONCE
62
- 4. Restart backend
63
- 5. STOP IMMEDIATELY
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+)/),g=i.match(/version: "([^"]+)"/);return{slug:r.replace(".spec.md",""),name:l?.[1]||r,status:h?.[1]||"unknown",version:g?.[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
+ 1. Read the spec if referenced in the card description
60
+ 2. Check existing features (quick ls)
61
+ 3. Write ALL entities to ONE /tmp/scaffold.json (big bang, no phasing!)
62
+ 4. Run scaffold CLI ONCE
63
+ 5. Restart backend
64
+ 6. STOP IMMEDIATELY
64
65
 
65
66
  **\u26A1 ONE-SHOT RULE: Put ALL entities in ONE JSON. The CLI handles dependencies.**
66
67
 
68
+ ## \u26A0\uFE0F SPEC AWARENESS (READ THIS FIRST!)
69
+
70
+ **If the card description contains "\u{1F4CB} Spec:" or mentions a specification:**
71
+ 1. Extract the spec name/slug from the card description
72
+ 2. Call \`read_specification("{spec-slug}")\` to get the FULL specification
73
+ 3. Use BOTH the card description AND the spec to understand what to build
74
+ 4. The spec contains the complete context - don't miss any fields or relations!
75
+
76
+ **Example:** If card says "\u{1F4CB} Spec: time-off-management", run:
77
+ \`\`\`
78
+ read_specification("time-off-management")
79
+ \`\`\`
80
+
67
81
  ## WORKFLOW
68
82
 
69
- ### Step 0: Check Existing Features (ALWAYS DO THIS FIRST)
83
+ ### Step 0: Read Spec (IF REFERENCED)
84
+ If the card mentions a spec, read it first to get full context.
85
+
86
+ ### Step 1: Check Existing Features (ALWAYS DO THIS)
70
87
  \`\`\`bash
71
88
  ls /workspace/packages/dotnet-api/Source/Features/
72
89
  \`\`\`
73
90
  This shows what already exists. **DO NOT scaffold entities that already exist!**
74
91
  Common existing features: Users (auth), Authentication, KanbanBoard, Document, etc.
75
92
 
76
- ### Step 1: Write Schema
93
+ ### Step 2: Write Schema
77
94
  \`\`\`bash
78
95
  # Use Write tool to create /tmp/scaffold.json
79
96
  \`\`\`
80
97
 
81
- ### Step 2: Run Scaffold
98
+ ### Step 3: Run Scaffold
82
99
  \`\`\`bash
83
100
  scaffold --schema /tmp/scaffold.json --output /workspace --force --full
84
101
  \`\`\`
85
102
 
86
- ### Step 3: Report & STOP
87
- If CLI shows "success": true, respond with:
103
+ ### Step 4: Restart Backend
104
+ \`\`\`bash
105
+ mcp__agent-insights__restart_services
106
+ \`\`\`
107
+ This regenerates swagger and applies migrations.
108
+
109
+ ### Step 5: Report & STOP
110
+ If everything succeeded, respond with:
88
111
  - Entities created: [names from output]
89
112
  - Routes: /super-admin/[plural] for each
90
113
  - DONE
@@ -239,7 +262,7 @@ DO NOT phase/split into multiple runs - that's slower and error-prone.
239
262
  - Enrollment depends on Student + CourseOffering - CLI handles it
240
263
  - ONE scaffold run creates ALL 6 entities with correct FK relationships
241
264
 
242
- REMEMBER: ls \u2192 Write \u2192 Run \u2192 Restart \u2192 STOP. No verification needed.`};var me={description:"Delegate to this agent to fix errors, type issues, or bugs in the code",model:"inherit",prompt:`You are a bug-fixing specialist. Your job is to FIX errors, not just report them.
265
+ REMEMBER: Read spec (if referenced) \u2192 ls \u2192 Write \u2192 Run \u2192 Restart \u2192 STOP. No verification needed.`};var me={description:"Delegate to this agent to fix errors, type issues, or bugs in the code",model:"inherit",prompt:`You are a bug-fixing specialist. Your job is to FIX errors, not just report them.
243
266
 
244
267
  ## Process
245
268
  1. Read the error message carefully - understand WHAT and WHERE
@@ -428,7 +451,7 @@ Save your spec. The spec will appear in the user's UI.
428
451
  - Ask ONE question at a time (not all at once!)
429
452
  - STOP asking if user says "start implementing"
430
453
  - NEVER output the spec as text - use save_specification
431
- - Maximum 7 questions - don't exhaust the user!`};var Me={description:"Delegate to this agent for writing new backend features (non-scaffold). Custom endpoints, queries, commands, business logic.",model:"inherit",prompt:`You are a backend specialist for .NET 9 / ASP.NET Core.
454
+ - Maximum 7 questions - don't exhaust the user!`};var Le={description:"Delegate to this agent for writing new backend features (non-scaffold). Custom endpoints, queries, commands, business logic.",model:"inherit",prompt:`You are a backend specialist for .NET 9 / ASP.NET Core.
432
455
 
433
456
  ## YOUR ROLE
434
457
  Write custom backend code that goes BEYOND what scaffold generates:
@@ -437,7 +460,27 @@ Write custom backend code that goes BEYOND what scaffold generates:
437
460
  - Business logic
438
461
  - Lookup queries for dropdowns
439
462
 
463
+ ## \u26A0\uFE0F SPEC AWARENESS (READ THIS FIRST!)
464
+
465
+ **If the card description contains "\u{1F4CB} Spec:" or mentions a specification:**
466
+ 1. Extract the spec name/slug from the card description
467
+ 2. Call \`read_specification("{spec-slug}")\` to get the FULL specification
468
+ 3. Use BOTH the card description AND the spec to understand:
469
+ - Business logic rules
470
+ - Validation requirements
471
+ - Edge cases and special considerations
472
+ - How this feature fits into the bigger picture
473
+
474
+ **Example:** If card says "\u{1F4CB} Spec: time-off-management", run:
475
+ \`\`\`
476
+ read_specification("time-off-management")
477
+ \`\`\`
478
+
479
+ The spec often contains crucial details about business rules that may not fit in the card description!
480
+
440
481
  ## BEFORE YOU START
482
+ 1. **Read the spec** (if referenced in card)
483
+ 2. **Check existing features:**
441
484
  \`\`\`bash
442
485
  ls /workspace/packages/dotnet-api/Source/Features/
443
486
  \`\`\`
@@ -500,7 +543,7 @@ public class Get{EntityB}LookupHandler : IQueryHandler<Get{EntityB}LookupQuery,
500
543
  2. If build succeeds, generate swagger: \`mcp__agent-insights__restart_services\` (this regenerates swagger)
501
544
  3. Report what you created and STOP
502
545
 
503
- **\u26A0\uFE0F CRITICAL:** Always generate swagger before frontend work begins!`};var Le={description:"Delegate to this agent for writing frontend features. Components, hooks, pages using generated API hooks.",model:"inherit",prompt:`You are a frontend specialist for React 19 + TypeScript + MUI.
546
+ **\u26A0\uFE0F CRITICAL:** Always generate swagger before frontend work begins!`};var Me={description:"Delegate to this agent for writing frontend features. Components, hooks, pages using generated API hooks.",model:"inherit",prompt:`You are a frontend specialist for React 19 + TypeScript + MUI.
504
547
 
505
548
  ## YOUR ROLE
506
549
  Write frontend code using GENERATED API hooks (never manual fetch):
@@ -508,28 +551,46 @@ Write frontend code using GENERATED API hooks (never manual fetch):
508
551
  - Custom hooks for state management
509
552
  - Forms with validation
510
553
 
554
+ ## \u26A0\uFE0F SPEC AWARENESS (READ THIS FIRST!)
555
+
556
+ **If the card description contains "\u{1F4CB} Spec:" or mentions a specification:**
557
+ 1. Extract the spec name/slug from the card description
558
+ 2. Call \`read_specification("{spec-slug}")\` to get the FULL specification
559
+ 3. Use BOTH the card description AND the spec to understand:
560
+ - User workflows and interactions
561
+ - UI/UX requirements
562
+ - What data needs to be displayed
563
+ - How components should behave
564
+
565
+ **Example:** If card says "\u{1F4CB} Spec: time-off-management", run:
566
+ \`\`\`
567
+ read_specification("time-off-management")
568
+ \`\`\`
569
+
570
+ The spec contains the full user story and UX requirements that help you build the right UI!
571
+
511
572
  ## BEFORE YOU START
512
- 1. Check existing features for patterns:
573
+ 1. **Read the spec** (if referenced in card)
574
+
575
+ 2. Check existing features for patterns:
513
576
  \`\`\`bash
514
577
  ls /workspace/packages/backoffice-web/src/applications/super-admin/features/
515
578
  \`\`\`
516
579
 
517
- 2. Check generated API hooks exist:
580
+ 3. Check generated API hooks exist:
518
581
  \`\`\`bash
519
582
  grep -l "{EntityName}" /workspace/packages/backoffice-web/src/generated/queries-commands.ts
520
583
  \`\`\`
521
584
 
522
- 3. MAKE SURE FUNCTIONALITY YOU ARE ABOUT TO BUILD DOES NOT ALREADY EXIST!
523
- Scaffold output includes, from another agent includes:
585
+ 4. MAKE SURE FUNCTIONALITY YOU ARE ABOUT TO BUILD DOES NOT ALREADY EXIST!
586
+ Scaffold output includes:
524
587
  - {Entity}Page (full CRUD list view)
525
588
  - {Entity}Table (table component)
526
589
  - {Entity}CreateDialog (add form)
527
590
  - {Entity}DetailsDialog (edit/detail view)
528
591
  - use{Entity}Management hook (custom logic)
529
592
  \u2192 These are COMPLETE and reusable. Never rebuild
530
- 1. Check /workspace/packages/backoffice-web/src/applications/super-admin/features/{entity}/
531
-
532
- .
593
+ Check /workspace/packages/backoffice-web/src/applications/super-admin/features/{entity}/
533
594
 
534
595
  **\u26A0\uFE0F If hooks don't exist, STOP and tell the main agent to run backend first!**
535
596
 
@@ -771,8 +832,8 @@ Before saving, verify your context:
771
832
  5. **Keep it scannable** - Use bullets, clear sections
772
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)+`
773
834
  ---
774
- `)}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()}),Yt=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:Yt.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 _e(e,t){if(t)return K.isAbsolute(t)?t:K.join(e.paths.workspace,t)}function Y(e){return _e(e,e.paths.backend)}function H(e){return _e(e,e.paths.frontend)}function we(e){let t=Y(e);if(!(!t||!e.paths.features?.backend))return K.join(t,e.paths.features.backend)}function oe(e){let t=H(e);if(!(!t||!e.paths.features?.frontend))return K.join(t,e.paths.features.frontend)}function ke(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(`
775
- `)}function Se(e){let t=[];return e.services?.backend&&t.push(`| Backend | ${Y(e)} | ${e.services.backend.port} | ${e.services.backend.logFile||"N/A"} |`),e.services?.frontend&&t.push(`| Frontend | ${H(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 |
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 _e(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 ke(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 Se(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 |
776
837
  |---------|----------|------|------|
777
838
  ${t.join(`
778
839
  `)}`}function ut(e={}){let{debugMode:t=!1,projectPrompt:n=null,isLocal:s=!1,projectConfig:o=G(),useDefaultStack:r=!0}=e,i=n?`
@@ -820,7 +881,7 @@ Then STOP. Do not continue. Do not try to fix it. Wait for user.
820
881
 
821
882
  ---
822
883
 
823
- `:"",h=Jt(o),f=Xt(o,s),m=tn(o,r),v=Zt(r),T=r?nn(o):"",S=r?on(o,s):"",O=en(r);return`${i}${l}# Agent Instructions
884
+ `:"",h=Jt(o),g=Xt(o,s),m=tn(o,r),v=Zt(r),T=r?nn(o):"",S=r?on(o,s):"",O=en(r);return`${i}${l}# Agent Instructions
824
885
 
825
886
  ## \u26D4 CRITICAL: EVERYTHING IS A KANBAN TASK
826
887
 
@@ -955,7 +1016,7 @@ You know that overengineering is the enemy of good software. You are pragmatic a
955
1016
 
956
1017
  ${h}
957
1018
 
958
- ${f}
1019
+ ${g}
959
1020
 
960
1021
  ${m}
961
1022
 
@@ -964,10 +1025,10 @@ ${O}
964
1025
  ${T}
965
1026
 
966
1027
  ${S}
967
- `}function Jt(e){let t=ke(e),n=Y(e),s=H(e),o=we(e),r=oe(e),i=[];o&&i.push(`- Backend: "${o}/{Entity}/"`),r&&i.push(`- Frontend: "${r}/{entity}/"`);let l=e.techStack?.patterns?.length?`
1028
+ `}function Jt(e){let t=ke(e),n=H(e),s=Y(e),o=_e(e),r=oe(e),i=[];o&&i.push(`- Backend: "${o}/{Entity}/"`),r&&i.push(`- Frontend: "${r}/{entity}/"`);let l=e.techStack?.patterns?.length?`
968
1029
  **Key patterns:**
969
1030
  ${e.techStack.patterns.map(m=>`- ${m}`).join(`
970
- `)}`:"",f=e.techStack?.backend?.some(m=>m.includes(".NET")||m.includes("C#"))??!1?`
1031
+ `)}`:"",g=e.techStack?.backend?.some(m=>m.includes(".NET")||m.includes("C#"))??!1?`
971
1032
 
972
1033
  **\u26A0\uFE0F User entity exists!** ASP.NET Identity "ApplicationUser" at "Features/Users/" - don't scaffold User, just relate to it.`:"";return`<tech-stack>
973
1034
  ${t}
@@ -975,7 +1036,7 @@ ${i.length>0?`
975
1036
  **Architecture:** Feature folders
976
1037
  ${i.join(`
977
1038
  `)}`:""}
978
- ${l}${f}
1039
+ ${l}${g}
979
1040
  </tech-stack>`}function Xt(e,t){return t?`<environment>
980
1041
  **LOCAL MODE - User manages their own services.**
981
1042
 
@@ -1006,19 +1067,21 @@ User sees app through **Cloudflare tunnel** (live preview in browser).
1006
1067
  | Trigger | Subagent | Example |
1007
1068
  |---------|----------|---------|
1008
1069
  | Any new feature request | @planning | "build a golf app", "add tasks" |
1070
+ | Create tasks from spec | @kanban | "create tasks for time-off spec" |
1009
1071
  | Create entity, CRUD, scaffold | @scaffolding | "add task management" |
1010
1072
  | Custom backend code | @backend | "add lookup query" |
1011
1073
  | Frontend UI code | @frontend | "create the UI" |
1012
1074
  | Build error, bug, fix | @debugger | "error CS0246" |
1013
1075
 
1014
- **Flow:** @planning \u2192 **[Create Kanban Cards]** \u2192 @scaffolding \u2192 @backend \u2192 @frontend \u2192 @debugger`:`**\u2705 ALWAYS delegate to these subagents:**
1076
+ **Flow:** @planning \u2192 @kanban (creates detailed tasks) \u2192 @scaffolding \u2192 @backend \u2192 @frontend \u2192 @debugger`:`**\u2705 ALWAYS delegate to these subagents:**
1015
1077
 
1016
1078
  | Trigger | Subagent | Example |
1017
1079
  |---------|----------|---------|
1018
1080
  | Any new feature request | @planning | "build a feature", "add tasks" |
1081
+ | Create tasks from spec | @kanban | "create tasks for the spec" |
1019
1082
  | Build error, bug, fix | @debugger | "fix this error" |
1020
1083
 
1021
- **Flow:** @planning \u2192 **[Create Kanban Cards]** \u2192 Execute work \u2192 @debugger (if issues)
1084
+ **Flow:** @planning \u2192 @kanban (creates detailed tasks) \u2192 Execute work \u2192 @debugger (if issues)
1022
1085
 
1023
1086
  Note: For this project, you implement the code directly after planning (no stack-specific subagents).
1024
1087
  Check \`.claude/skills/\` for project-specific patterns and conventions.`}function en(e){return e?`<workflow>
@@ -1026,42 +1089,42 @@ Check \`.claude/skills/\` for project-specific patterns and conventions.`}functi
1026
1089
  1. **@planning** - Gather requirements, design spec, saves it.
1027
1090
  2. **\u23F8\uFE0F STOP & WAIT** - Tell user: "Spec is ready! Review it and tell me when to implement."
1028
1091
  3. **User approves** - User says "looks good", "let's build".
1029
- 4. **KANBAN CREATION** - YOU (Main Agent) read the spec and create Kanban cards:
1092
+ 4. **KANBAN CREATION** - Delegate to @kanban agent OR create cards yourself:
1030
1093
 
1031
- **\u26A0\uFE0F CRITICAL: Cards MUST include spec details, not generic descriptions!**
1094
+ **Option A: Delegate to @kanban** (recommended)
1095
+ - "@kanban, create tasks for the {spec-name} specification"
1096
+ - The kanban agent reads the spec and creates detailed cards automatically
1032
1097
 
1033
- **WHY THIS MATTERS:** Subagents (@scaffolding, @backend, @frontend) receive ONLY the card title and description as context. They do NOT automatically see the original specification. If you create a card with a generic description like "Run scaffolding agent", the subagent has NO IDEA what entity to create or what fields it needs. The card description IS the subagent's instruction manual.
1098
+ **Option B: Create cards yourself** (if @kanban unavailable)
1099
+ - Read the spec with \`read_specification("{spec-slug}")\`
1100
+ - Create detailed cards following the format below
1034
1101
 
1035
- For each card, extract the relevant details from the specification:
1102
+ **\u26A0\uFE0F CRITICAL: Cards MUST include spec details AND a spec reference!**
1036
1103
 
1037
- - **"Scaffold [Entity]"** \u2192 Description MUST include:
1038
- - Entity name and all its fields/properties from the spec
1039
- - Relationships to other entities (e.g., "belongs to Project", "has many Tasks")
1040
- - Any enum types or special field types
1104
+ **WHY THIS MATTERS:** Subagents (@scaffolding, @backend, @frontend) receive ONLY the card title and description as context. They do NOT automatically see the original specification.
1041
1105
 
1042
- - **"Backend: [Feature]"** \u2192 Description MUST include:
1043
- - The specific custom logic requirements from the spec
1044
- - Which endpoints/queries need to be created
1045
- - Business rules or validation requirements
1046
- - Example: "Implement GetTasksByStatus query that filters by status enum and orders by dueDate"
1106
+ **CARD DESCRIPTION FORMAT:**
1107
+ \`\`\`
1108
+ \u{1F4CB} Spec: {spec-slug}
1047
1109
 
1048
- - **"Frontend: [Feature]"** \u2192 Description MUST include:
1049
- - Specific UI components needed from the spec
1050
- - User interactions and workflows
1051
- - Which data needs to be displayed and how
1052
- - Example: "Create TaskBoard component with drag-drop between status columns"
1110
+ {Specific requirements extracted from the spec}
1053
1111
 
1054
- **Use subtasks** for individual requirements within each card.
1112
+ ---
1113
+ \u{1F4A1} Read full spec: read_specification("{spec-slug}")
1114
+ \`\`\`
1055
1115
 
1056
- **BAD example (too generic):**
1057
- - Title: "Scaffold Task Feature"
1058
- - Description: "Run scaffolding agent to generate entity and CRUD"
1059
- - Problem: Subagent has no idea what fields Task needs!
1116
+ **This format ensures:**
1117
+ 1. Subagents have immediate context (extracted details)
1118
+ 2. Subagents can read the full spec if they need more detail
1119
+ 3. There's a single source of truth (the spec)
1060
1120
 
1061
- **GOOD example (includes spec details):**
1121
+ **For Scaffold cards** - include: entity name, ALL fields with types, ALL relations, features
1122
+ **For Backend cards** - include: endpoints, business rules, validation, inputs/outputs
1123
+ **For Frontend cards** - include: components, user workflows, data to display
1124
+
1125
+ **GOOD example:**
1062
1126
  - Title: "Scaffold Task Feature"
1063
- - Description: "Create Task entity with fields: title (string, required), description (text), status (enum: Todo/InProgress/Done), dueDate (datetime), priority (enum: Low/Medium/High). Belongs to Project (required). Has many Comments."
1064
- - Subtasks: "Add title field", "Add status enum", "Add Project relationship", etc.
1127
+ - Description: "\u{1F4CB} Spec: task-management\\n\\nCreate Task entity with fields: title (string, required), description (text), status (enum: Todo/InProgress/Done), dueDate (datetime), priority (enum: Low/Medium/High). Belongs to Project (required). Has many Comments.\\n\\n---\\n\u{1F4A1} Read full spec: read_specification(\\"task-management\\")"
1065
1128
 
1066
1129
  5. **EXECUTION LOOP**:
1067
1130
  - "get_kanban_board"
@@ -1082,18 +1145,26 @@ Check \`.claude/skills/\` for project-specific patterns and conventions.`}functi
1082
1145
  1. **@planning** - Gather requirements, design spec, saves it.
1083
1146
  2. **\u23F8\uFE0F STOP & WAIT** - Tell user: "Spec is ready! Review it and tell me when to implement."
1084
1147
  3. **User approves** - User says "looks good", "let's build".
1085
- 4. **KANBAN CREATION** - Create Kanban cards with detailed descriptions:
1148
+ 4. **KANBAN CREATION** - Delegate to @kanban OR create cards yourself:
1149
+
1150
+ **\u26A0\uFE0F CRITICAL: Cards MUST include spec reference AND extracted details!**
1086
1151
 
1087
- **\u26A0\uFE0F CRITICAL: Cards MUST include full implementation details!**
1152
+ **CARD DESCRIPTION FORMAT:**
1153
+ \`\`\`
1154
+ \u{1F4CB} Spec: {spec-slug}
1088
1155
 
1089
- Each card description should include:
1090
- - What needs to be built
1091
- - Specific requirements from the spec
1092
- - Any dependencies or ordering constraints
1156
+ {Specific requirements extracted from the spec}
1157
+
1158
+ ---
1159
+ \u{1F4A1} Read full spec: read_specification("{spec-slug}")
1160
+ \`\`\`
1161
+
1162
+ This ensures you can read the full spec for context when picking up the task.
1093
1163
 
1094
1164
  5. **EXECUTION LOOP**:
1095
1165
  - "get_kanban_board"
1096
1166
  - Pick top "Todo" card \u2192 Move to "In Progress"
1167
+ - **If card references a spec, read it first** for full context
1097
1168
  - Implement the feature (check \`.claude/skills/\` for project patterns)
1098
1169
  - Move card to "Done"
1099
1170
  - Repeat until board is empty OR if you need user input.
@@ -1102,6 +1173,7 @@ Check \`.claude/skills/\` for project-specific patterns and conventions.`}functi
1102
1173
 
1103
1174
  **Rules:**
1104
1175
  - After @planning \u2192 STOP and wait for user to approve the spec
1176
+ - Always read the referenced spec before starting work on a card
1105
1177
  - Check project's existing code for patterns before implementing
1106
1178
  - Use @debugger if you encounter build errors or bugs
1107
1179
  </workflow>`}function tn(e,t){let n=`**@planning** - Design before building:
@@ -1109,6 +1181,11 @@ Check \`.claude/skills/\` for project-specific patterns and conventions.`}functi
1109
1181
  - Asks questions, designs spec
1110
1182
  - **Saves spec using save_specification MCP tool**
1111
1183
  - **After @planning completes: STOP and wait for user approval!**
1184
+
1185
+ **@kanban** - Break down specs into tasks:
1186
+ - Reads the specification and creates detailed Kanban cards
1187
+ - Cards include spec reference + extracted details
1188
+ - Subagents can work independently with full context
1112
1189
  `;if(t){let s=!!e.scaffold?.command,o=!!e.services?.backend,r=!!e.services?.frontend;s&&(n+=`
1113
1190
  **@scaffolding** - Create CRUD entities, database, backend & frontend:
1114
1191
  - "Add X management" \u2192 one-shot all entities, everything from entity creation to frontend hooks and pages!
@@ -1257,34 +1334,34 @@ You have access to **Playwright MCP** tools to verify the UI works correctly aft
1257
1334
  - **Multi-tab apps**: Use \`browser_tabs\` to manage multiple windows/tabs in legacy applications
1258
1335
  - **Large snapshots**: For pages with huge dropdowns (e.g., country lists), use \`browser_evaluate\` to collapse them first
1259
1336
  - ${t?"In local mode, the browser window will be visible to you":"In container mode, browser runs headless"}
1260
- </ui-verification>`}function gt(e){return e&&e.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,"\uFFFD")}var sn=null;function ft(){return gt(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:f=!1,isLocal:m=process.env.LOCAL_MODE==="true",projectConfig:v=G(process.env.WORKSPACE_DIR||process.cwd()),useDefaultStack:T=!0}=t,S=lt(l),O=new Set,y=s||"",R,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",...m?[]:["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","--caps=vision",`--user-data-dir=${V.join(N,".playwright-data")}`,...m?[]:["--headless"]]}},settingSources:["project"],disallowedTools:["AskUserQuestion","TodoWrite","EnterPlanMode"],agents:T?{scaffolding:Ne,debugger:me,planning:he,backend:Me,frontend:Le,"project-context":ye}:{debugger:me,planning:he,"project-context":ye},systemPrompt:ut({debugMode:f,projectPrompt:ft(),isLocal:m,projectConfig:v,useDefaultStack:T})}}))if(!(!u.uuid||O.has(u.uuid))){switch(O.add(u.uuid),S?.log(u),i.onRawMessage?.(u),u.type){case"assistant":if(u.message?.content)for(let E of u.message.content)E.type==="text"?i.onAssistantText?.(E.text):E.type==="tool_use"?i.onAssistantToolUse?.(E.name,E.input):E.type==="tool_result"&&i.onAssistantToolResult?.(E.tool_use_id,E.content);break;case"user":i.onUserMessage?.(u);break;case"result":if(u.subtype==="success")R=u.result,U=u.total_cost_usd,i.onResult?.(u.result,u.total_cost_usd);else{let E=`Agent error: ${u.subtype}`;x=E,i.onError?.(E)}ee=!0;break;case"system":switch(u.subtype){case"init":y=u.session_id,S?.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{S?.stop()}return console.log("after try"),{sessionId:y,result:R,cost:U,error:x,aborted:$}}function Ce(e){return`User answered the questions:
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:g=!1,isLocal:m=process.env.LOCAL_MODE==="true",projectConfig:v=G(process.env.WORKSPACE_DIR||process.cwd()),useDefaultStack:T=!0}=t,S=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",...m?[]:["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","--caps=vision",`--user-data-dir=${V.join(N,".playwright-data")}`,...m?[]:["--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:g,projectPrompt:gt(),isLocal:m,projectConfig:v,useDefaultStack:T})}}))if(!(!u.uuid||O.has(u.uuid))){switch(O.add(u.uuid),S?.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,S?.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{S?.stop()}return console.log("after try"),{sessionId:y,result:I,cost:U,error:x,aborted:$}}function Ce(e){return`User answered the questions:
1261
1338
 
1262
1339
  ${Object.entries(e).map(([n,s])=>{let o=Array.isArray(s)?s.join(", "):s;return`${n}: ${o}`}).join(`
1263
- `)}`}import{spawn as ht,execSync as z}from"child_process";import*as k 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,f=Y(i),m=H(i),v=i.services?.backend?.startCommand??"dotnet run",T=i.services?.backend?.buildCommand??"dotnet build",S=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",R=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}`,A=>{clearTimeout(g),c(A.statusCode!==void 0&&A.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(`
1264
- `).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)}),E=async()=>{if(!f||!k.existsSync(f)){console.log("[Services] No backend found, skipping backend start");return}N(l),console.log(`[Services] Starting backend with: ${v}...`);let p=Q.dirname(R);k.existsSync(p)||k.mkdirSync(p,{recursive:!0});let c=k.createWriteStream(R,{flags:"a"}),[g,...b]=v.split(" ");x=ht(g,b,{cwd:f,stdio:["ignore","pipe","pipe"],detached:!0,shell:!0});let A="",C=_=>{let B=_.toString();c.write(B);let D=(A+B).split(`
1265
- `);A=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",_=>{c.end(),_!==0&&_!==null&&o&&o(`Process exited with code ${_}`)}),x.unref()},He=async()=>{if(!m||!k.existsSync(Q.join(m,"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);k.existsSync(p)||k.mkdirSync(p,{recursive:!0});let[c,...g]=O.split(" ");$=ht(c,g,{cwd:m,stdio:["ignore",k.openSync(U,"w"),k.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,A=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&&A===null&&(A=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 _=await De(),B=Date.now()-c;return _.web&&C===null&&console.log(`[Services] \u2713 Frontend (Vite) ready after ${B}ms`),_.api&&A===null&&console.log(`[Services] \u2713 Backend (dotnet) ready after ${B}ms`),console.log(`[Services] \u26A0 Health timeout after ${B}ms: api=${_.api}, web=${_.web}`),_},Mt=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&&f&&k.existsSync(f)){console.log(`[Services] Building backend with: ${T}...`);try{z(T,{cwd:f,stdio:"pipe",timeout:12e4,encoding:"utf-8"}),console.log("[Services] \u2713 Backend build succeeded")}catch(C){let _=C;c.success=!1,c.backendError=_.stderr||_.stdout||"Build failed",console.error("[Services] \u2717 Backend build failed")}}if(b&&m&&k.existsSync(Q.join(m,"package.json"))){console.log(`[Services] Type-checking frontend with: ${y}...`);try{z(y,{cwd:m,stdio:"pipe",timeout:6e4,encoding:"utf-8"}),console.log("[Services] \u2713 Frontend types OK")}catch(C){let _=C;c.success=!1,c.frontendError=_.stderr||_.stdout||"Type check failed",console.error("[Services] \u2717 Frontend type check failed")}}console.log(`[Services] Starting ${p}...`),g&&await E(),b&&await He();let A=await Ve(1e4);if(g&&!A.api){c.success=!1;let C=await Xe("backend",20),_=C.length>0?`
1340
+ `)}`}import{spawn as ht,execSync as z}from"child_process";import*as k 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,g=H(i),m=Y(i),v=i.services?.backend?.startCommand??"dotnet run",T=i.services?.backend?.buildCommand??"dotnet build",S=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 f=setTimeout(()=>c(!1),3e3);yt.get(`http://localhost:${p}`,P=>{clearTimeout(f),c(P.statusCode!==void 0&&P.statusCode<500)}).on("error",()=>{clearTimeout(f),c(!1)})}),N=p=>{try{let c=z(`lsof -ti:${p} 2>/dev/null || true`,{encoding:"utf-8"}).trim();if(c){let f=c.split(`
1341
+ `).filter(Boolean);console.log(`[Services] Killing ${f.length} process(es) on port ${p}: ${f.join(", ")}`);for(let b of f)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(f=>{if(!p||!p.pid){f();return}console.log(`[Services] Stopping ${c} (PID: ${p.pid})...`);try{process.kill(-p.pid,"SIGTERM")}catch{try{p.kill("SIGTERM")}catch{}}setTimeout(f,500)}),A=async()=>{if(!g||!k.existsSync(g)){console.log("[Services] No backend found, skipping backend start");return}N(l),console.log(`[Services] Starting backend with: ${v}...`);let p=Q.dirname(I);k.existsSync(p)||k.mkdirSync(p,{recursive:!0});let c=k.createWriteStream(I,{flags:"a"}),[f,...b]=v.split(" ");x=ht(f,b,{cwd:g,stdio:["ignore","pipe","pipe"],detached:!0,shell:!0});let P="",C=w=>{let B=w.toString();c.write(B);let D=(P+B).split(`
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(!m||!k.existsSync(Q.join(m,"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);k.existsSync(p)||k.mkdirSync(p,{recursive:!0});let[c,...f]=O.split(" ");$=ht(c,f,{cwd:m,stdio:["ignore",k.openSync(U,"w"),k.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(),f=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,f))}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},f=p==="backend"||p==="both",b=p==="frontend"||p==="both";if(f&&await Qe(),b&&await Ke(),f&&g&&k.existsSync(g)){console.log(`[Services] Building backend with: ${T}...`);try{z(T,{cwd:g,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&&m&&k.existsSync(Q.join(m,"package.json"))){console.log(`[Services] Type-checking frontend with: ${y}...`);try{z(y,{cwd:m,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}...`),f&&await A(),b&&await Ye();let P=await Ve(1e4);if(f&&!P.api){c.success=!1;let C=await Xe("backend",20),w=C.length>0?`
1266
1343
  Recent logs:
1267
1344
  ${C.join(`
1268
- `)}`:"";c.backendError||(c.backendError=`Backend failed to start.${_}`)}return b&&!A.web&&(c.success=!1,c.frontendError||(c.frontendError="Frontend failed to start")),c},ze=async()=>{if(!f||!k.existsSync(f))return{success:!0};try{return z(S,{cwd:f,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(!m||!k.existsSync(Q.join(m,"package.json")))return{success:!0};try{return z(y,{cwd:m,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"}}},Lt=async()=>{let[p,c]=await Promise.all([ze(),Je()]);return{backend:p,frontend:c}},Xe=async(p,c)=>{let g=p==="backend"?R:U;if(!k.existsSync(g))return[`Log file not found: ${g}`];try{return z(`tail -n ${c} ${g}`,{encoding:"utf-8"}).split(`
1269
- `).filter(Boolean)}catch(b){return[`Error reading logs: ${b instanceof Error?b.message:String(b)}`]}};return{startBackend:E,startFrontend:He,stopBackend:Qe,stopFrontend:Ke,restartServices:Mt,checkHealth:De,waitForHealth:Ve,getProcesses:()=>({backend:x,frontend:$}),checkBackendBuild:ze,checkFrontendTypes:Je,runTypeChecks:Lt,tailLogs:Xe,checkSwaggerEndpoints:async p=>{let c=Q.join(t,"swagger.json");if(!k.existsSync(c))return{foundEndpoints:["Error: swagger.json not found"],totalEndpoints:0};try{let g=JSON.parse(k.readFileSync(c,"utf-8")),b=Object.keys(g.paths||{}),A=[];for(let C of b)if(C.toLowerCase().includes(p.toLowerCase())){let _=Object.keys(g.paths[C]);for(let B of _)A.push(`${B.toUpperCase()} ${C}`)}return{foundEndpoints:A,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 Pe(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 f=J("git rev-parse --short HEAD",{cwd:e,encoding:"utf-8"}).trim();try{J("git push",{cwd:e,stdio:"pipe"})}catch(m){let v=m instanceof Error?m.message:String(m);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:!0,commitHash:f,error:`Committed but push failed: ${v}`}}return{success:!0,commitHash:f}}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 Ee(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):gn(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 gn(e){let{answer:t}=await ie.prompt([{type:"input",name:"answer",message:e.question}]);return t}import*as L from"fs";import*as Ae from"path";var fn=".sdd/cli-sessions.json";function qe(){return Ae.join(process.cwd(),fn)}function mn(){let e=Ae.dirname(qe());L.existsSync(e)||L.mkdirSync(e,{recursive:!0})}function We(){try{let e=qe();if(!L.existsSync(e))return[];let t=L.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);L.writeFileSync(qe(),JSON.stringify(s,null,2))}function _t(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 wt={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`${wt[e]}${t}${wt.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 kt(e){let t=e.split("/");return t.length<=3?e:".../"+t.slice(-2).join("/")}function Ge(e){console.log(`
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(!g||!k.existsSync(g))return{success:!0};try{return z(S,{cwd:g,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(!m||!k.existsSync(Q.join(m,"package.json")))return{success:!0};try{return z(y,{cwd:m,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 f=p==="backend"?I:U;if(!k.existsSync(f))return[`Log file not found: ${f}`];try{return z(`tail -n ${c} ${f}`,{encoding:"utf-8"}).split(`
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(!k.existsSync(c))return{foundEndpoints:["Error: swagger.json not found"],totalEndpoints:0};try{let f=JSON.parse(k.readFileSync(c,"utf-8")),b=Object.keys(f.paths||{}),P=[];for(let C of b)if(C.toLowerCase().includes(p.toLowerCase())){let w=Object.keys(f.paths[C]);for(let B of w)P.push(`${B.toUpperCase()} ${C}`)}return{foundEndpoints:P,totalEndpoints:b.length}}catch(f){return{foundEndpoints:[`Error parsing swagger.json: ${f instanceof Error?f.message:String(f)}`],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 g=J("git rev-parse --short HEAD",{cwd:e,encoding:"utf-8"}).trim();try{J("git push",{cwd:e,stdio:"pipe"})}catch(m){let v=m instanceof Error?m.message:String(m);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:!0,commitHash:g,error:`Committed but push failed: ${v}`}}return{success:!0,commitHash:g}}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 _t={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`${_t[e]}${t}${_t.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 kt(e){let t=e.split("/");return t.length<=3?e:".../"+t.slice(-2).join("/")}function Ge(e){console.log(`
1270
1347
  ${a("bold",a("cyan","\u2550".repeat(60)))}`),console.log(a("bold",` ${e}`)),console.log(`${a("bold",a("cyan","\u2550".repeat(60)))}
1271
1348
  `)}function St(e){let t=e.split(`
1272
1349
  `);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(`
1273
1350
  \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(`
1274
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",kt(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",kt(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(`
1275
1352
  ${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(`
1276
- ${a("bold",a("red","\u2717 Error"))}`),console.log(` ${e}`)}function Pt(){console.log(`
1277
- ${a("bold",a("yellow","\u23F9\uFE0F Aborted"))}`),console.log(` ${a("dim","Session stopped by user. Progress has been preserved.")}`)}function Et(){console.log(`
1353
+ ${a("bold",a("red","\u2717 Error"))}`),console.log(` ${e}`)}function Et(){console.log(`
1354
+ ${a("bold",a("yellow","\u23F9\uFE0F Aborted"))}`),console.log(` ${a("dim","Session stopped by user. Progress has been preserved.")}`)}function At(){console.log(`
1278
1355
  ${a("bold",a("yellow","\u2753 Agent needs your input"))}
1279
- `)}function At(){console.log(`
1356
+ `)}function Pt(){console.log(`
1280
1357
  ${a("dim","\u2192 Answers sent, agent continuing...")}
1281
1358
  `)}function xe(){console.log(a("dim",`
1282
1359
  `+"\u2500".repeat(50)+`
1283
1360
  `))}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(`
1284
- `).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 It(e){return{onAssistantText:St,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:Pt,onSystemInit:t=>{e.sessionId||(e.onSessionInit(t),console.log(` Session: ${t.slice(0,12)}...`))}}}import I from"inquirer";import*as j from"fs";import*as Ie from"path";var{Separator:_n}=I,wn=".sdd/api-config.json";function Ye(){return Ie.join(process.cwd(),wn)}function kn(){let e=Ie.dirname(Ye());j.existsSync(e)||j.mkdirSync(e,{recursive:!0})}function Sn(){try{let e=Ye();if(!j.existsSync(e))return null;let t=j.readFileSync(e,"utf-8");return JSON.parse(t)}catch{return null}}function vn(e){kn();let t={...e,lastUsedAt:new Date().toISOString()};j.writeFileSync(Ye(),JSON.stringify(t,null,2))}async function Rt(){let{options:e}=await I.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 I.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 I.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 _n("\u2500\u2500\u2500 Previous Sessions \u2500\u2500\u2500"),...e.map(r=>({name:_t(r),value:r.sessionId}))],{selection:n}=await I.prompt([{type:"select",name:"selection",message:"Select a session:",choices:t}]);if(n==="new"){let{prompt:r}=await I.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(`
1285
- \u{1F4C2} Resuming session: ${s.firstPrompt.slice(0,50)}...`);let{prompt:o}=await I.prompt([{type:"input",name:"prompt",message:"Continue with:"}]);return{sessionId:s.sessionId,firstPrompt:s.firstPrompt,isResume:!0,existingTurnCount:s.turnCount||0}}async function Re(){let{action:e}=await I.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 I.prompt([{type:"input",name:"prompt",message:"Your message:"}]);return t}async function Dt(){let{action:e}=await I.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=Sn();if(n&&!e&&!t){console.log(`
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:St,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,_n=".sdd/api-config.json";function He(){return Re.join(process.cwd(),_n)}function kn(){let e=Re.dirname(He());j.existsSync(e)||j.mkdirSync(e,{recursive:!0})}function Sn(){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){kn();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(`
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=Sn();if(n&&!e&&!t){console.log(`
1286
1363
  \u{1F4CB} Previous API Configuration Found
1287
- `),console.log(` \u{1F517} API URL: ${n.apiUrl}`),console.log(` \u{1F194} Project ID: ${n.projectId}`);let{action:r}=await I.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 I.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(`
1364
+ `),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(`
1288
1365
  ${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(`
1289
1366
  ${a("dim","\u23ED\uFE0F No code changes, skipping type checks")}`),{passed:!0};let o=[];n&&o.push("backend"),s&&o.push("frontend"),console.log(`
1290
1367
  ${a("dim",`\u{1F50D} Type checking ${o.join(" & ")}...`)}`);let r=process.env.WORKSPACE_DIR||process.cwd(),i=[];if(n){let l=await e.checkBackendBuild();!l.success&&l.errors&&i.push(`## Backend Build Errors (in ${r}/packages/dotnet-api)
@@ -1307,9 +1384,9 @@ File paths like "src/..." are relative to the package directory shown in parenth
1307
1384
 
1308
1385
  \u23F9\uFE0F Stopping agent...
1309
1386
  `),ce.abort()):(console.log(`
1310
- `),process.exit(0))});async function Oe(e,t={}){let{serviceManager:n,gitManager:s}=t,o=null,r,i,l=0,h=0,f=t.typecheck??!1,m=t.autoCommit??!1,v=t.model;if(e)r=e,i=e;else{let T=await Rt();n&&(f=T.typecheck),s&&(m=T.autoCommit),t.skipModelPrompt||(v=T.model);let S=await Ot();o=S.sessionId,r=S.firstPrompt,l=S.existingTurnCount,i=S.isResume&&await Re()||r}for(Cn(f,m,!!n,!!s,v);;){l++,console.log(`
1311
- Turn ${l}`),xe();let T=new Set;ce=new AbortController;let S=await ve(i,{model:v==="auto"?void 0:v,sessionId:o,projectId:process.env.PROJECT_ID||null,debugLog:!0,abortController:ce,callbacks:It({sessionId:o,onSessionInit:y=>{o=y},onCostUpdate:y=>{h+=y},onFileModified:y=>{T.add(y)}})});if(console.log("after runAgent"),ce=null,S.sessionId&&(o=S.sessionId),S.cost&&(h=S.cost),console.log("after result"),Pn(o,r,l),console.log("after saveSessionIfNeeded"),S.aborted){if(await new Promise(U=>setTimeout(U,100)),xe(),h>0&&console.log(` \u{1F4B0} Session cost so far: $${h.toFixed(4)}
1312
- `),await Dt()==="exit")break;let R=await Re();if(!R)break;i=R;continue}if(console.log("after abort"),o){let y=te(o);if(y&&y.length>0){Et();let R=await Ee(y);i=Ce(R),At();continue}}if(console.log("after pending questions"),f&&n){let y=await Ut(n,T);if(!y.passed&&y.errorPrompt){i=y.errorPrompt;continue}}if(console.log("after typecheck"),m&&s){let y=S.aborted||!!S.error;await Nt(s,r,y)}console.log("after auto-commit"),xe();let O=await Re();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
1313
- `)}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 Pn(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 En(){let e=process.env.API_URL||process.env.MASTER_URL,t=process.env.PROJECT_ID,n=e,s=t;if(!e||!t){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,g=t.typecheck??!1,m=t.autoCommit??!1,v=t.model;if(e)r=e,i=e;else{let T=await It();n&&(g=T.typecheck),s&&(m=T.autoCommit),t.skipModelPrompt||(v=T.model);let S=await Ot();o=S.sessionId,r=S.firstPrompt,l=S.existingTurnCount,i=S.isResume&&await Ie()||r}for(Cn(g,m,!!n,!!s,v);;){l++,console.log(`
1388
+ Turn ${l}`),xe();let T=new Set;ce=new AbortController;let S=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,S.sessionId&&(o=S.sessionId),S.cost&&(h=S.cost),console.log("after result"),En(o,r,l),console.log("after saveSessionIfNeeded"),S.aborted){if(await new Promise(U=>setTimeout(U,100)),xe(),h>0&&console.log(` \u{1F4B0} Session cost so far: $${h.toFixed(4)}
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"),g&&n){let y=await Ut(n,T);if(!y.passed&&y.errorPrompt){i=y.errorPrompt;continue}}if(console.log("after typecheck"),m&&s){let y=S.aborted||!!S.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
+ `)}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(`
1314
1391
  \u{1F4CB} API Configuration Required
1315
- `);let f=await Ft();n=f.apiUrl,s=f.projectId}process.env.API_URL=n,process.env.PROJECT_ID=s;let o=process.env.WORKSPACE_DIR||process.cwd(),r=Te({workspaceDir:o}),i=Pe(o),l=ne(o);ge(r),ue(l),await Oe(void 0,{serviceManager:r,gitManager:i})}En().catch(e=>{console.error("Fatal error:",e),process.exit(1)});
1392
+ `);let g=await Ft();n=g.apiUrl,s=g.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)});