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/core.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 pt}from"@anthropic-ai/claude-agent-sdk";import{tool as se}from"@anthropic-ai/claude-agent-sdk";import{z as oe}from"zod";import*as re from"fs";import*as De from"path";var D=null;function Oe(e){D=e}function me(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}var It=se("save_specification",`Save a specification to the database.
4
+ import{createSdkMcpServer as pt}from"@anthropic-ai/claude-agent-sdk";import{tool as re}from"@anthropic-ai/claude-agent-sdk";import{z as se}from"zod";import*as oe from"fs";import*as De from"path";var D=null;function Oe(e){D=e}function me(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}var It=re("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,30 +12,30 @@ 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:oe.string().describe("Specification name (e.g., 'User Authentication')"),filePath:oe.string().describe("Path to the file containing the specification content (written via Write tool)")},async e=>{if(!D)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let t=De.resolve(e.filePath);if(!re.existsSync(t))return{content:[{type:"text",text:`\u274C File not found: ${e.filePath}. Use Write tool first to create the file.`}]};let n=re.readFileSync(t,"utf-8"),a=me(e.name);return await D.saveSpecification(a,n),{content:[{type:"text",text:`\u2705 Saved specification: ${a}.spec.md`}]}}),Dt=se("read_specification","Read the content of a specification.",{name:oe.string().describe("Name of the specification to read")},async e=>{if(!D)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let t=me(e.name),n=await D.getSpecification(t);return n?{content:[{type:"text",text:n}]}:{content:[{type:"text",text:`\u274C Specification "${e.name}" not found.`}]}}),Ot=se("list_specifications","List all specifications.",{},async()=>{if(!D)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let e=await D.listSpecifications();return e.length===0?{content:[{type:"text",text:"\u{1F4CB} No specifications found."}]}:{content:[{type:"text",text:`\u{1F4CB} Specifications:
15
+ 2. save_specification(name: "User Authentication", filePath: "/tmp/user-auth-spec.md")`,{name:se.string().describe("Specification name (e.g., 'User Authentication')"),filePath:se.string().describe("Path to the file containing the specification content (written via Write tool)")},async e=>{if(!D)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let t=De.resolve(e.filePath);if(!oe.existsSync(t))return{content:[{type:"text",text:`\u274C File not found: ${e.filePath}. Use Write tool first to create the file.`}]};let n=oe.readFileSync(t,"utf-8"),a=me(e.name);return await D.saveSpecification(a,n),{content:[{type:"text",text:`\u2705 Saved specification: ${a}.spec.md`}]}}),Dt=re("read_specification","Read the content of a specification.",{name:se.string().describe("Name of the specification to read")},async e=>{if(!D)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let t=me(e.name),n=await D.getSpecification(t);return n?{content:[{type:"text",text:n}]}:{content:[{type:"text",text:`\u274C Specification "${e.name}" not found.`}]}}),Ot=re("list_specifications","List all specifications.",{},async()=>{if(!D)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let e=await D.listSpecifications();return e.length===0?{content:[{type:"text",text:"\u{1F4CB} No specifications found."}]}:{content:[{type:"text",text:`\u{1F4CB} Specifications:
16
16
 
17
17
  ${e.map(n=>`- ${n.name} (${n.slug}.spec.md)`).join(`
18
- `)}`}]}}),Ft=se("delete_specification","Delete a specification.",{name:oe.string().describe("Name of the specification to delete")},async e=>{if(!D)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let t=me(e.name);return await D.deleteSpecification(t)?{content:[{type:"text",text:`\u{1F5D1}\uFE0F Deleted: ${t}.spec.md`}]}:{content:[{type:"text",text:`\u274C Specification "${e.name}" not found.`}]}});import{tool as M}from"@anthropic-ai/claude-agent-sdk";import{z as Q}from"zod";var y=null;function Fe(e){y=e}var Ne=M("restart_services",`Rebuild and restart services. Call this after making backend changes (.cs files) to apply them.
18
+ `)}`}]}}),Ft=re("delete_specification","Delete a specification.",{name:se.string().describe("Name of the specification to delete")},async e=>{if(!D)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let t=me(e.name);return await D.deleteSpecification(t)?{content:[{type:"text",text:`\u{1F5D1}\uFE0F Deleted: ${t}.spec.md`}]}:{content:[{type:"text",text:`\u274C Specification "${e.name}" not found.`}]}});import{tool as M}from"@anthropic-ai/claude-agent-sdk";import{z as Q}from"zod";var y=null;function Fe(e){y=e}var Ne=M("restart_services",`Rebuild and restart services. Call this after making backend changes (.cs files) to apply them.
19
19
 
20
20
  Parameters:
21
21
  - target: "backend" (default) | "frontend" | "both"
22
22
 
23
23
  Use target="backend" after .cs file changes (faster, only restarts .NET).
24
24
  Use target="frontend" if Vite is stuck (rare, HMR usually works).
25
- Use target="both" if unsure or both need restart.`,{target:Q.enum(["backend","frontend","both"]).default("backend").describe("Which service to restart")},async e=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=e.target||"backend";console.log(`[MCP] restart_services called (target: ${t})`);let n=await y.restartServices(t);if(!n.success){let o=[];return n.backendError&&o.push(`Backend: ${n.backendError}`),n.frontendError&&o.push(`Frontend: ${n.frontendError}`),{content:[{type:"text",text:`\u274C Restart failed:
26
- ${o.join(`
25
+ Use target="both" if unsure or both need restart.`,{target:Q.enum(["backend","frontend","both"]).default("backend").describe("Which service to restart")},async e=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=e.target||"backend";console.log(`[MCP] restart_services called (target: ${t})`);let n=await y.restartServices(t);if(!n.success){let s=[];return n.backendError&&s.push(`Backend: ${n.backendError}`),n.frontendError&&s.push(`Frontend: ${n.frontendError}`),{content:[{type:"text",text:`\u274C Restart failed:
26
+ ${s.join(`
27
27
 
28
28
  `)}`}]}}return{content:[{type:"text",text:`\u2705 ${t==="both"?"Backend and frontend":t==="backend"?"Backend":"Frontend"} restarted successfully.`}]}}),Ue=M("stop_services",`Stop running services. Call this BEFORE running scaffold to prevent port conflicts and ensure clean migrations.
29
29
 
30
30
  Parameters:
31
31
  - target: "backend" (default) | "frontend" | "both"
32
32
 
33
- Use target="backend" before running scaffold (required for migrations).`,{target:Q.enum(["backend","frontend","both"]).default("backend").describe("Which service to stop")},async e=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=e.target||"backend";console.log(`[MCP] stop_services called (target: ${t})`);let n=t==="backend"||t==="both",a=t==="frontend"||t==="both";n&&await y.stopBackend(),a&&await y.stopFrontend();let o=5e3,r=Date.now(),s=!n,f=!a;for(;Date.now()-r<o;){let u=await y.checkHealth();if(n&&!u.api&&(s=!0),a&&!u.web&&(f=!0),s&&f)break;await new Promise(T=>setTimeout(T,500))}let _=t==="both"?"Backend and frontend":t==="backend"?"Backend":"Frontend";return{content:[{type:"text",text:s&&f?`\u2705 ${_} stopped successfully.`:`\u26A0\uFE0F ${_} stop requested but health check still shows running. Proceeding anyway.`}]}}),Le=M("start_services",`Start services. Call this AFTER running scaffold to bring services back up.
33
+ Use target="backend" before running scaffold (required for migrations).`,{target:Q.enum(["backend","frontend","both"]).default("backend").describe("Which service to stop")},async e=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=e.target||"backend";console.log(`[MCP] stop_services called (target: ${t})`);let n=t==="backend"||t==="both",a=t==="frontend"||t==="both";n&&await y.stopBackend(),a&&await y.stopFrontend();let s=5e3,o=Date.now(),r=!n,g=!a;for(;Date.now()-o<s;){let u=await y.checkHealth();if(n&&!u.api&&(r=!0),a&&!u.web&&(g=!0),r&&g)break;await new Promise(C=>setTimeout(C,500))}let _=t==="both"?"Backend and frontend":t==="backend"?"Backend":"Frontend";return{content:[{type:"text",text:r&&g?`\u2705 ${_} stopped successfully.`:`\u26A0\uFE0F ${_} stop requested but health check still shows running. Proceeding anyway.`}]}}),Le=M("start_services",`Start services. Call this AFTER running scaffold to bring services back up.
34
34
 
35
35
  Parameters:
36
36
  - target: "backend" (default) | "frontend" | "both"
37
37
 
38
- Use target="backend" after running scaffold.`,{target:Q.enum(["backend","frontend","both"]).default("backend").describe("Which service to start")},async e=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=e.target||"backend";console.log(`[MCP] start_services called (target: ${t})`);let n=t==="backend"||t==="both",a=t==="frontend"||t==="both";n&&await y.startBackend(),a&&await y.startFrontend();let o=await y.waitForHealth(15e3),r=!n||o.api,s=!a||o.web,f=r&&s,_=t==="both"?"Backend and frontend":t==="backend"?"Backend":"Frontend";if(!f){let g=[];return n&&!o.api&&g.push("Backend failed to start"),a&&!o.web&&g.push("Frontend failed to start"),{content:[{type:"text",text:`\u274C ${_} failed to start: ${g.join(", ")}`}]}}return{content:[{type:"text",text:`\u2705 ${_} started successfully.`}]}}),Me=M("check_service_health",`Check if services are running and healthy. Returns current status of backend API and frontend.
38
+ Use target="backend" after running scaffold.`,{target:Q.enum(["backend","frontend","both"]).default("backend").describe("Which service to start")},async e=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=e.target||"backend";console.log(`[MCP] start_services called (target: ${t})`);let n=t==="backend"||t==="both",a=t==="frontend"||t==="both";n&&await y.startBackend(),a&&await y.startFrontend();let s=await y.waitForHealth(15e3),o=!n||s.api,r=!a||s.web,g=o&&r,_=t==="both"?"Backend and frontend":t==="backend"?"Backend":"Frontend";if(!g){let f=[];return n&&!s.api&&f.push("Backend failed to start"),a&&!s.web&&f.push("Frontend failed to start"),{content:[{type:"text",text:`\u274C ${_} failed to start: ${f.join(", ")}`}]}}return{content:[{type:"text",text:`\u2705 ${_} started successfully.`}]}}),Me=M("check_service_health",`Check if services are running and healthy. Returns current status of backend API and frontend.
39
39
  Note: This only checks if ports are responding, NOT build/type errors. Use check_backend_build or check_frontend_types for that.`,{},async()=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let e=await y.checkHealth();return{content:[{type:"text",text:`Service Health:
40
40
  \u{1F5A5}\uFE0F Backend API: ${e.api?"\u2705 running":"\u274C not running"}
41
41
  \u{1F310} Frontend: ${e.web?"\u2705 running":"\u274C not running"}`}]}}),je=M("check_backend_build","Run dotnet build to check for C# compilation errors. Use this after fixing backend code to verify the fix works.",{},async()=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};console.log("[MCP] check_backend_build called");let e=await y.checkBackendBuild();return e.success?{content:[{type:"text",text:"\u2705 Backend build succeeded - no errors"}]}:{content:[{type:"text",text:`\u274C Backend build failed:
@@ -55,38 +55,61 @@ Parameters:
55
55
  - pattern: string (e.g., "users", "api/reports")`,{pattern:Q.string().describe("Text to search for in endpoint paths")},async e=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=await y.checkSwaggerEndpoints(e.pattern);return t.foundEndpoints.length===0?{content:[{type:"text",text:`\u274C No endpoints found matching "${e.pattern}" (Total endpoints: ${t.totalEndpoints})`}]}:{content:[{type:"text",text:`\u2705 Found ${t.foundEndpoints.length} matching endpoints:
56
56
 
57
57
  ${t.foundEndpoints.join(`
58
- `)}`}]}});import*as P from"fs";import*as V from"path";var $e="/tmp";function he(e){let t=e?V.join(e,".sdd","insights"):V.join(process.cwd(),".sdd","insights");return{async reportInsight(n,a){P.mkdirSync(t,{recursive:!0});let o=a.type==="learning"?"learnings.jsonl":"bugs.jsonl",r=V.join(t,o);P.appendFileSync(r,JSON.stringify(a)+`
59
- `,"utf-8")},async reportProgress(n,a,o){let r=V.join($e,`agent-progress-${n}.json`),s={timestamp:new Date().toISOString(),event:a,data:o};P.appendFileSync(r,JSON.stringify(s)+`
60
- `,"utf-8")}}}function ye(e){let t=V.join($e,`agent-questions-${e}.json`);if(!P.existsSync(t))return null;try{let n=P.readFileSync(t,"utf-8");return P.unlinkSync(t),JSON.parse(n)}catch{return null}}import*as w from"fs";import*as ae from"path";function be(e){let t=ae.join(e,".sdd","specifications");function n(){w.existsSync(t)||w.mkdirSync(t,{recursive:!0})}function a(o){return ae.join(t,`${o}.spec.md`)}return{async saveSpecification(o,r){n(),w.writeFileSync(a(o),r,"utf-8")},async getSpecification(o){n();let r=a(o);return w.existsSync(r)?w.readFileSync(r,"utf-8"):null},async listSpecifications(){return n(),w.readdirSync(t).filter(r=>r.endsWith(".spec.md")).map(r=>{let s=w.readFileSync(ae.join(t,r),"utf-8"),f=s.match(/name: "([^"]+)"/),_=s.match(/status: (\w+)/),g=s.match(/version: "([^"]+)"/);return{slug:r.replace(".spec.md",""),name:f?.[1]||r,status:_?.[1]||"unknown",version:g?.[1]||"1.0.0"}})},async deleteSpecification(o){n();let r=a(o);return w.existsSync(r)?(w.unlinkSync(r),!0):!1},async specificationExists(o){return n(),w.existsSync(a(o))}}}function _e(){return pt({name:"agent-insights",version:"1.0.0",tools:[Ne,Ue,Le,Me,je,Be]})}import{query as Ct}from"@anthropic-ai/claude-agent-sdk";var ke={description:"Delegate to this agent when scaffolding new entities or creating CRUD features. This agent handles entity generation using the scaffold CLI with multi-entity support.",model:"inherit",prompt:`You are a scaffolding specialist. Your ONLY job is:
61
- 1. Check existing features (quick ls)
62
- 2. Write ALL entities to ONE /tmp/scaffold.json (big bang, no phasing!)
63
- 3. Run scaffold CLI ONCE
64
- 4. Restart backend
65
- 5. STOP IMMEDIATELY
58
+ `)}`}]}});import*as P from"fs";import*as V from"path";var qe="/tmp";function he(e){let t=e?V.join(e,".sdd","insights"):V.join(process.cwd(),".sdd","insights");return{async reportInsight(n,a){P.mkdirSync(t,{recursive:!0});let s=a.type==="learning"?"learnings.jsonl":"bugs.jsonl",o=V.join(t,s);P.appendFileSync(o,JSON.stringify(a)+`
59
+ `,"utf-8")},async reportProgress(n,a,s){let o=V.join(qe,`agent-progress-${n}.json`),r={timestamp:new Date().toISOString(),event:a,data:s};P.appendFileSync(o,JSON.stringify(r)+`
60
+ `,"utf-8")}}}function ye(e){let t=V.join(qe,`agent-questions-${e}.json`);if(!P.existsSync(t))return null;try{let n=P.readFileSync(t,"utf-8");return P.unlinkSync(t),JSON.parse(n)}catch{return null}}import*as w from"fs";import*as ae from"path";function be(e){let t=ae.join(e,".sdd","specifications");function n(){w.existsSync(t)||w.mkdirSync(t,{recursive:!0})}function a(s){return ae.join(t,`${s}.spec.md`)}return{async saveSpecification(s,o){n(),w.writeFileSync(a(s),o,"utf-8")},async getSpecification(s){n();let o=a(s);return w.existsSync(o)?w.readFileSync(o,"utf-8"):null},async listSpecifications(){return n(),w.readdirSync(t).filter(o=>o.endsWith(".spec.md")).map(o=>{let r=w.readFileSync(ae.join(t,o),"utf-8"),g=r.match(/name: "([^"]+)"/),_=r.match(/status: (\w+)/),f=r.match(/version: "([^"]+)"/);return{slug:o.replace(".spec.md",""),name:g?.[1]||o,status:_?.[1]||"unknown",version:f?.[1]||"1.0.0"}})},async deleteSpecification(s){n();let o=a(s);return w.existsSync(o)?(w.unlinkSync(o),!0):!1},async specificationExists(s){return n(),w.existsSync(a(s))}}}function _e(){return pt({name:"agent-insights",version:"1.0.0",tools:[Ne,Ue,Le,Me,je,Be]})}import{query as Tt}from"@anthropic-ai/claude-agent-sdk";var ke={description:"Delegate to this agent when scaffolding new entities or creating CRUD features. This agent handles entity generation using the scaffold CLI with multi-entity support.",model:"inherit",prompt:`You are a scaffolding specialist. Your ONLY job is:
61
+ 1. Read the spec if referenced in the card description
62
+ 2. Check existing features (quick ls)
63
+ 3. Write ALL entities to ONE /tmp/scaffold.json (big bang, no phasing!)
64
+ 4. Run scaffold CLI ONCE
65
+ 5. Restart backend
66
+ 6. STOP IMMEDIATELY
66
67
 
67
68
  **\u26A1 ONE-SHOT RULE: Put ALL entities in ONE JSON. The CLI handles dependencies.**
68
69
 
70
+ ## \u26A0\uFE0F SPEC AWARENESS (READ THIS FIRST!)
71
+
72
+ **If the card description contains "\u{1F4CB} Spec:" or mentions a specification:**
73
+ 1. Extract the spec name/slug from the card description
74
+ 2. Call \`read_specification("{spec-slug}")\` to get the FULL specification
75
+ 3. Use BOTH the card description AND the spec to understand what to build
76
+ 4. The spec contains the complete context - don't miss any fields or relations!
77
+
78
+ **Example:** If card says "\u{1F4CB} Spec: time-off-management", run:
79
+ \`\`\`
80
+ read_specification("time-off-management")
81
+ \`\`\`
82
+
69
83
  ## WORKFLOW
70
84
 
71
- ### Step 0: Check Existing Features (ALWAYS DO THIS FIRST)
85
+ ### Step 0: Read Spec (IF REFERENCED)
86
+ If the card mentions a spec, read it first to get full context.
87
+
88
+ ### Step 1: Check Existing Features (ALWAYS DO THIS)
72
89
  \`\`\`bash
73
90
  ls /workspace/packages/dotnet-api/Source/Features/
74
91
  \`\`\`
75
92
  This shows what already exists. **DO NOT scaffold entities that already exist!**
76
93
  Common existing features: Users (auth), Authentication, KanbanBoard, Document, etc.
77
94
 
78
- ### Step 1: Write Schema
95
+ ### Step 2: Write Schema
79
96
  \`\`\`bash
80
97
  # Use Write tool to create /tmp/scaffold.json
81
98
  \`\`\`
82
99
 
83
- ### Step 2: Run Scaffold
100
+ ### Step 3: Run Scaffold
84
101
  \`\`\`bash
85
102
  scaffold --schema /tmp/scaffold.json --output /workspace --force --full
86
103
  \`\`\`
87
104
 
88
- ### Step 3: Report & STOP
89
- If CLI shows "success": true, respond with:
105
+ ### Step 4: Restart Backend
106
+ \`\`\`bash
107
+ mcp__agent-insights__restart_services
108
+ \`\`\`
109
+ This regenerates swagger and applies migrations.
110
+
111
+ ### Step 5: Report & STOP
112
+ If everything succeeded, respond with:
90
113
  - Entities created: [names from output]
91
114
  - Routes: /super-admin/[plural] for each
92
115
  - DONE
@@ -241,7 +264,7 @@ DO NOT phase/split into multiple runs - that's slower and error-prone.
241
264
  - Enrollment depends on Student + CourseOffering - CLI handles it
242
265
  - ONE scaffold run creates ALL 6 entities with correct FK relationships
243
266
 
244
- REMEMBER: ls \u2192 Write \u2192 Run \u2192 Restart \u2192 STOP. No verification needed.`};var ie={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.
267
+ REMEMBER: Read spec (if referenced) \u2192 ls \u2192 Write \u2192 Run \u2192 Restart \u2192 STOP. No verification needed.`};var ie={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.
245
268
 
246
269
  ## Process
247
270
  1. Read the error message carefully - understand WHAT and WHERE
@@ -439,7 +462,27 @@ Write custom backend code that goes BEYOND what scaffold generates:
439
462
  - Business logic
440
463
  - Lookup queries for dropdowns
441
464
 
465
+ ## \u26A0\uFE0F SPEC AWARENESS (READ THIS FIRST!)
466
+
467
+ **If the card description contains "\u{1F4CB} Spec:" or mentions a specification:**
468
+ 1. Extract the spec name/slug from the card description
469
+ 2. Call \`read_specification("{spec-slug}")\` to get the FULL specification
470
+ 3. Use BOTH the card description AND the spec to understand:
471
+ - Business logic rules
472
+ - Validation requirements
473
+ - Edge cases and special considerations
474
+ - How this feature fits into the bigger picture
475
+
476
+ **Example:** If card says "\u{1F4CB} Spec: time-off-management", run:
477
+ \`\`\`
478
+ read_specification("time-off-management")
479
+ \`\`\`
480
+
481
+ The spec often contains crucial details about business rules that may not fit in the card description!
482
+
442
483
  ## BEFORE YOU START
484
+ 1. **Read the spec** (if referenced in card)
485
+ 2. **Check existing features:**
443
486
  \`\`\`bash
444
487
  ls /workspace/packages/dotnet-api/Source/Features/
445
488
  \`\`\`
@@ -510,28 +553,46 @@ Write frontend code using GENERATED API hooks (never manual fetch):
510
553
  - Custom hooks for state management
511
554
  - Forms with validation
512
555
 
556
+ ## \u26A0\uFE0F SPEC AWARENESS (READ THIS FIRST!)
557
+
558
+ **If the card description contains "\u{1F4CB} Spec:" or mentions a specification:**
559
+ 1. Extract the spec name/slug from the card description
560
+ 2. Call \`read_specification("{spec-slug}")\` to get the FULL specification
561
+ 3. Use BOTH the card description AND the spec to understand:
562
+ - User workflows and interactions
563
+ - UI/UX requirements
564
+ - What data needs to be displayed
565
+ - How components should behave
566
+
567
+ **Example:** If card says "\u{1F4CB} Spec: time-off-management", run:
568
+ \`\`\`
569
+ read_specification("time-off-management")
570
+ \`\`\`
571
+
572
+ The spec contains the full user story and UX requirements that help you build the right UI!
573
+
513
574
  ## BEFORE YOU START
514
- 1. Check existing features for patterns:
575
+ 1. **Read the spec** (if referenced in card)
576
+
577
+ 2. Check existing features for patterns:
515
578
  \`\`\`bash
516
579
  ls /workspace/packages/backoffice-web/src/applications/super-admin/features/
517
580
  \`\`\`
518
581
 
519
- 2. Check generated API hooks exist:
582
+ 3. Check generated API hooks exist:
520
583
  \`\`\`bash
521
584
  grep -l "{EntityName}" /workspace/packages/backoffice-web/src/generated/queries-commands.ts
522
585
  \`\`\`
523
586
 
524
- 3. MAKE SURE FUNCTIONALITY YOU ARE ABOUT TO BUILD DOES NOT ALREADY EXIST!
525
- Scaffold output includes, from another agent includes:
587
+ 4. MAKE SURE FUNCTIONALITY YOU ARE ABOUT TO BUILD DOES NOT ALREADY EXIST!
588
+ Scaffold output includes:
526
589
  - {Entity}Page (full CRUD list view)
527
590
  - {Entity}Table (table component)
528
591
  - {Entity}CreateDialog (add form)
529
592
  - {Entity}DetailsDialog (edit/detail view)
530
593
  - use{Entity}Management hook (custom logic)
531
594
  \u2192 These are COMPLETE and reusable. Never rebuild
532
- 1. Check /workspace/packages/backoffice-web/src/applications/super-admin/features/{entity}/
533
-
534
- .
595
+ Check /workspace/packages/backoffice-web/src/applications/super-admin/features/{entity}/
535
596
 
536
597
  **\u26A0\uFE0F If hooks don't exist, STOP and tell the main agent to run backend first!**
537
598
 
@@ -771,21 +832,21 @@ Before saving, verify your context:
771
832
  3. **Show patterns** - Include small code examples for common tasks
772
833
  4. **Prioritize** - Put most critical info in <critical_rules>
773
834
  5. **Keep it scannable** - Use bullets, clear sections
774
- 6. **Update, don't replace** - If context exists, improve it rather than starting fresh`};import*as z from"fs";import*as qe from"path";var de=class{buffer=[];flushInterval=null;sessionId=null;outputDir;flushIntervalMs;constructor(t){this.outputDir=t.outputDir||"/tmp/agent-debug",this.flushIntervalMs=t.flushIntervalMs||3e3,t.enabled&&(this.ensureOutputDir(),this.startFlushInterval())}ensureOutputDir(){z.existsSync(this.outputDir)||z.mkdirSync(this.outputDir,{recursive:!0})}startFlushInterval(){this.flushInterval=setInterval(()=>{this.flush()},this.flushIntervalMs)}setSessionId(t){this.sessionId=t}log(t){let a={timestamp:new Date().toISOString(),message:t};this.buffer.push(JSON.stringify(a,null,2)+`
835
+ 6. **Update, don't replace** - If context exists, improve it rather than starting fresh`};import*as z from"fs";import*as $e from"path";var de=class{buffer=[];flushInterval=null;sessionId=null;outputDir;flushIntervalMs;constructor(t){this.outputDir=t.outputDir||"/tmp/agent-debug",this.flushIntervalMs=t.flushIntervalMs||3e3,t.enabled&&(this.ensureOutputDir(),this.startFlushInterval())}ensureOutputDir(){z.existsSync(this.outputDir)||z.mkdirSync(this.outputDir,{recursive:!0})}startFlushInterval(){this.flushInterval=setInterval(()=>{this.flush()},this.flushIntervalMs)}setSessionId(t){this.sessionId=t}log(t){let a={timestamp:new Date().toISOString(),message:t};this.buffer.push(JSON.stringify(a,null,2)+`
775
836
  ---
776
- `)}getLogFilePath(){let n=(this.sessionId||"unknown").replace(/[^a-zA-Z0-9-]/g,"_").slice(0,50),a=new Date().toISOString().split("T")[0];return qe.join(this.outputDir,`session-${a}-${n}.log`)}flush(){if(this.buffer.length===0)return;let t=this.buffer.join("");this.buffer=[];let n=this.getLogFilePath();z.appendFileSync(n,t)}stop(){this.flushInterval&&(clearInterval(this.flushInterval),this.flushInterval=null),this.flush()}};function We(e){return e?typeof e=="boolean"?e?new de({enabled:!0}):null:e.enabled?new de(e):null:null}import{z as c}from"zod";import*as J from"fs/promises";import*as E from"path";var Ge=c.object({port:c.number(),startCommand:c.string(),buildCommand:c.string().optional(),typecheckCommand:c.string().optional(),logFile:c.string().optional(),healthEndpoint:c.string().optional(),extensions:c.array(c.string())}),ut=c.object({port:c.number(),type:c.enum(["postgres","mysql","sqlite","mongodb"])}),gt=c.object({command:c.string(),schemaPath:c.string().optional()}),ft=c.object({email:c.string().optional(),name:c.string().optional(),commitPrefix:c.string().optional()}),mt=c.object({enabled:c.boolean(),port:c.number().optional()}),ee=c.object({version:c.literal("1.0"),paths:c.object({workspace:c.string(),backend:c.string().optional(),frontend:c.string().optional(),features:c.object({backend:c.string().optional(),frontend:c.string().optional()}).optional()}),services:c.object({backend:Ge.optional(),frontend:Ge.optional(),database:ut.optional()}).optional(),scaffold:gt.optional(),git:ft.optional(),techStack:c.object({backend:c.array(c.string()).optional(),frontend:c.array(c.string()).optional(),patterns:c.array(c.string()).optional()}).optional(),tunnel:mt.optional()});function O(e){return{version:"1.0",paths:{workspace:e||process.env.WORKSPACE_DIR||"/workspace",backend:"packages/dotnet-api",frontend:"packages/backoffice-web",features:{backend:"Source/Features",frontend:"src/applications/super-admin/features"}},services:{backend:{port:5338,startCommand:"dotnet run",buildCommand:"dotnet build",typecheckCommand:"dotnet build --no-restore",logFile:"/tmp/api.log",healthEndpoint:"http://localhost:5338",extensions:[".cs",".csproj"]},frontend:{port:5173,startCommand:"npm run dev",typecheckCommand:"npx tsc -p tsconfig.app.json --noEmit",logFile:"/tmp/web.log",healthEndpoint:"http://localhost:5173",extensions:[".ts",".tsx",".json"]},database:{port:43594,type:"postgres"}},scaffold:{command:"scaffold --schema /tmp/scaffold.json --output /workspace --force --full",schemaPath:"/tmp/scaffold.json"},git:{email:"agent@dotnetmentor.se",name:"Agent",commitPrefix:"[agent]"},techStack:{backend:[".NET 9","PostgreSQL","MediatR (CQRS)","EF Core"],frontend:["React 19","TypeScript","Vite","MUI","TanStack Query"],patterns:["Vertical slices","CQRS","Soft delete","Timestamps"]},tunnel:{enabled:!0,port:5173}}}var Ye="project-config.json",He=".sdd";async function Ke(e){let{workspaceDir:t,providedConfig:n,requireConfig:a}=e;if(n)return{config:ee.parse(n),source:"provided"};let o=E.join(t,He,Ye);try{let r=await J.readFile(o,"utf-8"),s=JSON.parse(r);return{config:ee.parse(s),source:"file",configPath:o}}catch(r){r instanceof Error&&"code"in r&&r.code!=="ENOENT"&&console.warn(`[Config] Warning: Invalid project-config.json at ${o}:`,r instanceof c.ZodError?r.errors:r.message)}if(a)throw new Error(`No project configuration found at ${o} and requireConfig is true`);return{config:O(t),source:"default"}}async function Qe(e,t){let n=E.join(e,He),a=E.join(n,Ye);await J.mkdir(n,{recursive:!0});let o=ee.parse(t);return await J.writeFile(a,JSON.stringify(o,null,2),"utf-8"),a}function pe(e,t){if(t)return E.isAbsolute(t)?t:E.join(e.paths.workspace,t)}function F(e){return pe(e,e.paths.backend)}function N(e){return pe(e,e.paths.frontend)}function te(e){let t=F(e);if(!(!t||!e.paths.features?.backend))return E.join(t,e.paths.features.backend)}function X(e){let t=N(e);if(!(!t||!e.paths.features?.frontend))return E.join(t,e.paths.features.frontend)}function Ve(e,t){if(!e.services?.backend?.extensions)return!1;let n=E.extname(t).toLowerCase();return e.services.backend.extensions.includes(n)}function ze(e,t){if(!e.services?.frontend?.extensions)return!1;let n=E.extname(t).toLowerCase();return e.services.frontend.extensions.includes(n)}function ue(e){let t=[];return e.techStack?.backend?.length&&t.push(`**Backend:** ${e.techStack.backend.join(", ")}`),e.techStack?.frontend?.length&&t.push(`**Frontend:** ${e.techStack.frontend.join(", ")}`),e.techStack?.patterns?.length&&t.push(`**Patterns:** ${e.techStack.patterns.join(", ")}`),t.join(`
837
+ `)}getLogFilePath(){let n=(this.sessionId||"unknown").replace(/[^a-zA-Z0-9-]/g,"_").slice(0,50),a=new Date().toISOString().split("T")[0];return $e.join(this.outputDir,`session-${a}-${n}.log`)}flush(){if(this.buffer.length===0)return;let t=this.buffer.join("");this.buffer=[];let n=this.getLogFilePath();z.appendFileSync(n,t)}stop(){this.flushInterval&&(clearInterval(this.flushInterval),this.flushInterval=null),this.flush()}};function We(e){return e?typeof e=="boolean"?e?new de({enabled:!0}):null:e.enabled?new de(e):null:null}import{z as c}from"zod";import*as J from"fs/promises";import*as E from"path";var Ge=c.object({port:c.number(),startCommand:c.string(),buildCommand:c.string().optional(),typecheckCommand:c.string().optional(),logFile:c.string().optional(),healthEndpoint:c.string().optional(),extensions:c.array(c.string())}),ut=c.object({port:c.number(),type:c.enum(["postgres","mysql","sqlite","mongodb"])}),ft=c.object({command:c.string(),schemaPath:c.string().optional()}),gt=c.object({email:c.string().optional(),name:c.string().optional(),commitPrefix:c.string().optional()}),mt=c.object({enabled:c.boolean(),port:c.number().optional()}),ee=c.object({version:c.literal("1.0"),paths:c.object({workspace:c.string(),backend:c.string().optional(),frontend:c.string().optional(),features:c.object({backend:c.string().optional(),frontend:c.string().optional()}).optional()}),services:c.object({backend:Ge.optional(),frontend:Ge.optional(),database:ut.optional()}).optional(),scaffold:ft.optional(),git:gt.optional(),techStack:c.object({backend:c.array(c.string()).optional(),frontend:c.array(c.string()).optional(),patterns:c.array(c.string()).optional()}).optional(),tunnel:mt.optional()});function O(e){return{version:"1.0",paths:{workspace:e||process.env.WORKSPACE_DIR||"/workspace",backend:"packages/dotnet-api",frontend:"packages/backoffice-web",features:{backend:"Source/Features",frontend:"src/applications/super-admin/features"}},services:{backend:{port:5338,startCommand:"dotnet run",buildCommand:"dotnet build",typecheckCommand:"dotnet build --no-restore",logFile:"/tmp/api.log",healthEndpoint:"http://localhost:5338",extensions:[".cs",".csproj"]},frontend:{port:5173,startCommand:"npm run dev",typecheckCommand:"npx tsc -p tsconfig.app.json --noEmit",logFile:"/tmp/web.log",healthEndpoint:"http://localhost:5173",extensions:[".ts",".tsx",".json"]},database:{port:43594,type:"postgres"}},scaffold:{command:"scaffold --schema /tmp/scaffold.json --output /workspace --force --full",schemaPath:"/tmp/scaffold.json"},git:{email:"agent@dotnetmentor.se",name:"Agent",commitPrefix:"[agent]"},techStack:{backend:[".NET 9","PostgreSQL","MediatR (CQRS)","EF Core"],frontend:["React 19","TypeScript","Vite","MUI","TanStack Query"],patterns:["Vertical slices","CQRS","Soft delete","Timestamps"]},tunnel:{enabled:!0,port:5173}}}var He="project-config.json",Ye=".sdd";async function Ke(e){let{workspaceDir:t,providedConfig:n,requireConfig:a}=e;if(n)return{config:ee.parse(n),source:"provided"};let s=E.join(t,Ye,He);try{let o=await J.readFile(s,"utf-8"),r=JSON.parse(o);return{config:ee.parse(r),source:"file",configPath:s}}catch(o){o instanceof Error&&"code"in o&&o.code!=="ENOENT"&&console.warn(`[Config] Warning: Invalid project-config.json at ${s}:`,o instanceof c.ZodError?o.errors:o.message)}if(a)throw new Error(`No project configuration found at ${s} and requireConfig is true`);return{config:O(t),source:"default"}}async function Qe(e,t){let n=E.join(e,Ye),a=E.join(n,He);await J.mkdir(n,{recursive:!0});let s=ee.parse(t);return await J.writeFile(a,JSON.stringify(s,null,2),"utf-8"),a}function pe(e,t){if(t)return E.isAbsolute(t)?t:E.join(e.paths.workspace,t)}function F(e){return pe(e,e.paths.backend)}function N(e){return pe(e,e.paths.frontend)}function te(e){let t=F(e);if(!(!t||!e.paths.features?.backend))return E.join(t,e.paths.features.backend)}function X(e){let t=N(e);if(!(!t||!e.paths.features?.frontend))return E.join(t,e.paths.features.frontend)}function Ve(e,t){if(!e.services?.backend?.extensions)return!1;let n=E.extname(t).toLowerCase();return e.services.backend.extensions.includes(n)}function ze(e,t){if(!e.services?.frontend?.extensions)return!1;let n=E.extname(t).toLowerCase();return e.services.frontend.extensions.includes(n)}function ue(e){let t=[];return e.techStack?.backend?.length&&t.push(`**Backend:** ${e.techStack.backend.join(", ")}`),e.techStack?.frontend?.length&&t.push(`**Frontend:** ${e.techStack.frontend.join(", ")}`),e.techStack?.patterns?.length&&t.push(`**Patterns:** ${e.techStack.patterns.join(", ")}`),t.join(`
777
838
  `)}function Je(e){let t=[];return e.paths.backend&&t.push(`- Backend: ${F(e)}`),e.paths.frontend&&t.push(`- Frontend: ${N(e)}`),e.paths.features?.backend&&t.push(`- Backend Features: ${te(e)}`),e.paths.features?.frontend&&t.push(`- Frontend Features: ${X(e)}`),t.join(`
778
- `)}function ge(e){let t=[];return e.services?.backend&&t.push(`| Backend | ${F(e)} | ${e.services.backend.port} | ${e.services.backend.logFile||"N/A"} |`),e.services?.frontend&&t.push(`| Frontend | ${N(e)} | ${e.services.frontend.port} | ${e.services.frontend.logFile||"N/A"} |`),e.services?.database&&t.push(`| Database (${e.services.database.type}) | localhost | ${e.services.database.port} | - |`),t.length===0?"":`| Service | Location | Port | Logs |
839
+ `)}function fe(e){let t=[];return e.services?.backend&&t.push(`| Backend | ${F(e)} | ${e.services.backend.port} | ${e.services.backend.logFile||"N/A"} |`),e.services?.frontend&&t.push(`| Frontend | ${N(e)} | ${e.services.frontend.port} | ${e.services.frontend.logFile||"N/A"} |`),e.services?.database&&t.push(`| Database (${e.services.database.type}) | localhost | ${e.services.database.port} | - |`),t.length===0?"":`| Service | Location | Port | Logs |
779
840
  |---------|----------|------|------|
780
841
  ${t.join(`
781
- `)}`}function Xe(e={}){let{debugMode:t=!1,projectPrompt:n=null,isLocal:a=!1,projectConfig:o=O(),useDefaultStack:r=!0}=e,s=n?`
842
+ `)}`}function Xe(e={}){let{debugMode:t=!1,projectPrompt:n=null,isLocal:a=!1,projectConfig:s=O(),useDefaultStack:o=!0}=e,r=n?`
782
843
  <project_context>
783
844
  ${n}
784
845
  </project_context>
785
846
 
786
847
  ---
787
848
 
788
- `:"",f=t?`
849
+ `:"",g=t?`
789
850
  ## \u{1F6D1} DEBUG MODE ACTIVE - HIGHEST PRIORITY
790
851
 
791
852
  **You are in DEBUG mode. This instruction block OVERRIDES ALL OTHER INSTRUCTIONS.**
@@ -823,7 +884,7 @@ Then STOP. Do not continue. Do not try to fix it. Wait for user.
823
884
 
824
885
  ---
825
886
 
826
- `:"",_=ht(o),g=yt(o,a),u=kt(o,r),T=bt(r),R=r?wt(o):"",B=r?St(o,a):"",$=_t(r);return`${s}${f}# Agent Instructions
887
+ `:"",_=ht(s),f=yt(s,a),u=kt(s,o),C=bt(o),R=o?wt(s):"",B=o?St(s,a):"",q=_t(o);return`${r}${g}# Agent Instructions
827
888
 
828
889
  ## \u26D4 CRITICAL: EVERYTHING IS A KANBAN TASK
829
890
 
@@ -947,7 +1008,7 @@ When user asks to build ANY feature, your FIRST action is to delegate to @planni
947
1008
  - Use EnterPlanMode or any built-in planning
948
1009
  - Skip the @planning step
949
1010
 
950
- ${T}
1011
+ ${C}
951
1012
 
952
1013
  ---
953
1014
 
@@ -958,27 +1019,27 @@ You know that overengineering is the enemy of good software. You are pragmatic a
958
1019
 
959
1020
  ${_}
960
1021
 
961
- ${g}
1022
+ ${f}
962
1023
 
963
1024
  ${u}
964
1025
 
965
- ${$}
1026
+ ${q}
966
1027
 
967
1028
  ${R}
968
1029
 
969
1030
  ${B}
970
- `}function ht(e){let t=ue(e),n=F(e),a=N(e),o=te(e),r=X(e),s=[];o&&s.push(`- Backend: "${o}/{Entity}/"`),r&&s.push(`- Frontend: "${r}/{entity}/"`);let f=e.techStack?.patterns?.length?`
1031
+ `}function ht(e){let t=ue(e),n=F(e),a=N(e),s=te(e),o=X(e),r=[];s&&r.push(`- Backend: "${s}/{Entity}/"`),o&&r.push(`- Frontend: "${o}/{entity}/"`);let g=e.techStack?.patterns?.length?`
971
1032
  **Key patterns:**
972
1033
  ${e.techStack.patterns.map(u=>`- ${u}`).join(`
973
- `)}`:"",g=e.techStack?.backend?.some(u=>u.includes(".NET")||u.includes("C#"))??!1?`
1034
+ `)}`:"",f=e.techStack?.backend?.some(u=>u.includes(".NET")||u.includes("C#"))??!1?`
974
1035
 
975
1036
  **\u26A0\uFE0F User entity exists!** ASP.NET Identity "ApplicationUser" at "Features/Users/" - don't scaffold User, just relate to it.`:"";return`<tech-stack>
976
1037
  ${t}
977
- ${s.length>0?`
1038
+ ${r.length>0?`
978
1039
  **Architecture:** Feature folders
979
- ${s.join(`
1040
+ ${r.join(`
980
1041
  `)}`:""}
981
- ${f}${g}
1042
+ ${g}${f}
982
1043
  </tech-stack>`}function yt(e,t){return t?`<environment>
983
1044
  **LOCAL MODE - User manages their own services.**
984
1045
 
@@ -1000,7 +1061,7 @@ The user is responsible for:
1000
1061
  </environment>`:`<environment>
1001
1062
  Docker container with pre-started services:
1002
1063
 
1003
- ${ge(e)}
1064
+ ${fe(e)}
1004
1065
  ${e.tunnel?.enabled?"| Tunnel | - | - | Exposes frontend to user |":""}
1005
1066
 
1006
1067
  User sees app through **Cloudflare tunnel** (live preview in browser).
@@ -1009,19 +1070,21 @@ User sees app through **Cloudflare tunnel** (live preview in browser).
1009
1070
  | Trigger | Subagent | Example |
1010
1071
  |---------|----------|---------|
1011
1072
  | Any new feature request | @planning | "build a golf app", "add tasks" |
1073
+ | Create tasks from spec | @kanban | "create tasks for time-off spec" |
1012
1074
  | Create entity, CRUD, scaffold | @scaffolding | "add task management" |
1013
1075
  | Custom backend code | @backend | "add lookup query" |
1014
1076
  | Frontend UI code | @frontend | "create the UI" |
1015
1077
  | Build error, bug, fix | @debugger | "error CS0246" |
1016
1078
 
1017
- **Flow:** @planning \u2192 **[Create Kanban Cards]** \u2192 @scaffolding \u2192 @backend \u2192 @frontend \u2192 @debugger`:`**\u2705 ALWAYS delegate to these subagents:**
1079
+ **Flow:** @planning \u2192 @kanban (creates detailed tasks) \u2192 @scaffolding \u2192 @backend \u2192 @frontend \u2192 @debugger`:`**\u2705 ALWAYS delegate to these subagents:**
1018
1080
 
1019
1081
  | Trigger | Subagent | Example |
1020
1082
  |---------|----------|---------|
1021
1083
  | Any new feature request | @planning | "build a feature", "add tasks" |
1084
+ | Create tasks from spec | @kanban | "create tasks for the spec" |
1022
1085
  | Build error, bug, fix | @debugger | "fix this error" |
1023
1086
 
1024
- **Flow:** @planning \u2192 **[Create Kanban Cards]** \u2192 Execute work \u2192 @debugger (if issues)
1087
+ **Flow:** @planning \u2192 @kanban (creates detailed tasks) \u2192 Execute work \u2192 @debugger (if issues)
1025
1088
 
1026
1089
  Note: For this project, you implement the code directly after planning (no stack-specific subagents).
1027
1090
  Check \`.claude/skills/\` for project-specific patterns and conventions.`}function _t(e){return e?`<workflow>
@@ -1029,42 +1092,42 @@ Check \`.claude/skills/\` for project-specific patterns and conventions.`}functi
1029
1092
  1. **@planning** - Gather requirements, design spec, saves it.
1030
1093
  2. **\u23F8\uFE0F STOP & WAIT** - Tell user: "Spec is ready! Review it and tell me when to implement."
1031
1094
  3. **User approves** - User says "looks good", "let's build".
1032
- 4. **KANBAN CREATION** - YOU (Main Agent) read the spec and create Kanban cards:
1095
+ 4. **KANBAN CREATION** - Delegate to @kanban agent OR create cards yourself:
1033
1096
 
1034
- **\u26A0\uFE0F CRITICAL: Cards MUST include spec details, not generic descriptions!**
1097
+ **Option A: Delegate to @kanban** (recommended)
1098
+ - "@kanban, create tasks for the {spec-name} specification"
1099
+ - The kanban agent reads the spec and creates detailed cards automatically
1035
1100
 
1036
- **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.
1101
+ **Option B: Create cards yourself** (if @kanban unavailable)
1102
+ - Read the spec with \`read_specification("{spec-slug}")\`
1103
+ - Create detailed cards following the format below
1037
1104
 
1038
- For each card, extract the relevant details from the specification:
1105
+ **\u26A0\uFE0F CRITICAL: Cards MUST include spec details AND a spec reference!**
1039
1106
 
1040
- - **"Scaffold [Entity]"** \u2192 Description MUST include:
1041
- - Entity name and all its fields/properties from the spec
1042
- - Relationships to other entities (e.g., "belongs to Project", "has many Tasks")
1043
- - Any enum types or special field types
1107
+ **WHY THIS MATTERS:** Subagents (@scaffolding, @backend, @frontend) receive ONLY the card title and description as context. They do NOT automatically see the original specification.
1044
1108
 
1045
- - **"Backend: [Feature]"** \u2192 Description MUST include:
1046
- - The specific custom logic requirements from the spec
1047
- - Which endpoints/queries need to be created
1048
- - Business rules or validation requirements
1049
- - Example: "Implement GetTasksByStatus query that filters by status enum and orders by dueDate"
1109
+ **CARD DESCRIPTION FORMAT:**
1110
+ \`\`\`
1111
+ \u{1F4CB} Spec: {spec-slug}
1050
1112
 
1051
- - **"Frontend: [Feature]"** \u2192 Description MUST include:
1052
- - Specific UI components needed from the spec
1053
- - User interactions and workflows
1054
- - Which data needs to be displayed and how
1055
- - Example: "Create TaskBoard component with drag-drop between status columns"
1113
+ {Specific requirements extracted from the spec}
1056
1114
 
1057
- **Use subtasks** for individual requirements within each card.
1115
+ ---
1116
+ \u{1F4A1} Read full spec: read_specification("{spec-slug}")
1117
+ \`\`\`
1058
1118
 
1059
- **BAD example (too generic):**
1060
- - Title: "Scaffold Task Feature"
1061
- - Description: "Run scaffolding agent to generate entity and CRUD"
1062
- - Problem: Subagent has no idea what fields Task needs!
1119
+ **This format ensures:**
1120
+ 1. Subagents have immediate context (extracted details)
1121
+ 2. Subagents can read the full spec if they need more detail
1122
+ 3. There's a single source of truth (the spec)
1123
+
1124
+ **For Scaffold cards** - include: entity name, ALL fields with types, ALL relations, features
1125
+ **For Backend cards** - include: endpoints, business rules, validation, inputs/outputs
1126
+ **For Frontend cards** - include: components, user workflows, data to display
1063
1127
 
1064
- **GOOD example (includes spec details):**
1128
+ **GOOD example:**
1065
1129
  - Title: "Scaffold Task Feature"
1066
- - 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."
1067
- - Subtasks: "Add title field", "Add status enum", "Add Project relationship", etc.
1130
+ - 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\\")"
1068
1131
 
1069
1132
  5. **EXECUTION LOOP**:
1070
1133
  - "get_kanban_board"
@@ -1085,18 +1148,26 @@ Check \`.claude/skills/\` for project-specific patterns and conventions.`}functi
1085
1148
  1. **@planning** - Gather requirements, design spec, saves it.
1086
1149
  2. **\u23F8\uFE0F STOP & WAIT** - Tell user: "Spec is ready! Review it and tell me when to implement."
1087
1150
  3. **User approves** - User says "looks good", "let's build".
1088
- 4. **KANBAN CREATION** - Create Kanban cards with detailed descriptions:
1151
+ 4. **KANBAN CREATION** - Delegate to @kanban OR create cards yourself:
1152
+
1153
+ **\u26A0\uFE0F CRITICAL: Cards MUST include spec reference AND extracted details!**
1154
+
1155
+ **CARD DESCRIPTION FORMAT:**
1156
+ \`\`\`
1157
+ \u{1F4CB} Spec: {spec-slug}
1089
1158
 
1090
- **\u26A0\uFE0F CRITICAL: Cards MUST include full implementation details!**
1159
+ {Specific requirements extracted from the spec}
1091
1160
 
1092
- Each card description should include:
1093
- - What needs to be built
1094
- - Specific requirements from the spec
1095
- - Any dependencies or ordering constraints
1161
+ ---
1162
+ \u{1F4A1} Read full spec: read_specification("{spec-slug}")
1163
+ \`\`\`
1164
+
1165
+ This ensures you can read the full spec for context when picking up the task.
1096
1166
 
1097
1167
  5. **EXECUTION LOOP**:
1098
1168
  - "get_kanban_board"
1099
1169
  - Pick top "Todo" card \u2192 Move to "In Progress"
1170
+ - **If card references a spec, read it first** for full context
1100
1171
  - Implement the feature (check \`.claude/skills/\` for project patterns)
1101
1172
  - Move card to "Done"
1102
1173
  - Repeat until board is empty OR if you need user input.
@@ -1105,6 +1176,7 @@ Check \`.claude/skills/\` for project-specific patterns and conventions.`}functi
1105
1176
 
1106
1177
  **Rules:**
1107
1178
  - After @planning \u2192 STOP and wait for user to approve the spec
1179
+ - Always read the referenced spec before starting work on a card
1108
1180
  - Check project's existing code for patterns before implementing
1109
1181
  - Use @debugger if you encounter build errors or bugs
1110
1182
  </workflow>`}function kt(e,t){let n=`**@planning** - Design before building:
@@ -1112,18 +1184,23 @@ Check \`.claude/skills/\` for project-specific patterns and conventions.`}functi
1112
1184
  - Asks questions, designs spec
1113
1185
  - **Saves spec using save_specification MCP tool**
1114
1186
  - **After @planning completes: STOP and wait for user approval!**
1115
- `;if(t){let a=!!e.scaffold?.command,o=!!e.services?.backend,r=!!e.services?.frontend;a&&(n+=`
1187
+
1188
+ **@kanban** - Break down specs into tasks:
1189
+ - Reads the specification and creates detailed Kanban cards
1190
+ - Cards include spec reference + extracted details
1191
+ - Subagents can work independently with full context
1192
+ `;if(t){let a=!!e.scaffold?.command,s=!!e.services?.backend,o=!!e.services?.frontend;a&&(n+=`
1116
1193
  **@scaffolding** - Create CRUD entities, database, backend & frontend:
1117
1194
  - "Add X management" \u2192 one-shot all entities, everything from entity creation to frontend hooks and pages!
1118
1195
  - NEVER write scaffold JSON yourself
1119
- `),o&&(n+=`
1196
+ `),s&&(n+=`
1120
1197
  **@backend** - Custom backend code (non-scaffold):
1121
1198
  - Custom queries, endpoints, lookups
1122
1199
  - Runs build + swagger generation before done
1123
1200
  - **Run \`./scripts/generate-swagger.sh\` after adding/changing endpoints**
1124
1201
  - **Run \`./scripts/generate-signalr.sh\` after adding/changing SignalR hubs**
1125
- ${r?"- **Must complete before @frontend starts!**":""}
1126
- `),r&&(n+=`
1202
+ ${o?"- **Must complete before @frontend starts!**":""}
1203
+ `),o&&(n+=`
1127
1204
  **@frontend** - UI components:
1128
1205
  - Uses GENERATED API hooks only
1129
1206
  - Never writes manual fetch calls
@@ -1178,7 +1255,7 @@ queryClient.invalidateQueries({ queryKey: getGetApiPeopleQueryKey() })
1178
1255
 
1179
1256
  Check the project's existing patterns for API calls and data fetching.
1180
1257
  Look for existing feature code to understand the correct patterns before writing new API calls.
1181
- </frontend-api>`:""}function St(e,t){if(!e.services?.frontend)return"";let n=e.services.frontend.port,a=X(e),o=`http://localhost:${n}`;return`<ui-verification>
1258
+ </frontend-api>`:""}function St(e,t){if(!e.services?.frontend)return"";let n=e.services.frontend.port,a=X(e),s=`http://localhost:${n}`;return`<ui-verification>
1182
1259
  ## \u{1F50D} UI Verification with Playwright
1183
1260
 
1184
1261
  You have access to **Playwright MCP** tools to verify the UI works correctly after making changes.
@@ -1236,7 +1313,7 @@ You have access to **Playwright MCP** tools to verify the UI works correctly aft
1236
1313
  - To test forms and interactions
1237
1314
 
1238
1315
  **Verification workflow:**
1239
- 1. Navigate to the app: \`browser_navigate\` to \`${a?.includes("super-admin")?`${o}/super-admin/[feature]`:`${o}/[feature]`}\`
1316
+ 1. Navigate to the app: \`browser_navigate\` to \`${a?.includes("super-admin")?`${s}/super-admin/[feature]`:`${s}/[feature]`}\`
1240
1317
  2. Get page structure: \`browser_snapshot\` returns the accessibility tree (LLM-friendly)
1241
1318
  3. Optional: Take screenshot for user: \`browser_take_screenshot\`
1242
1319
  4. Verify expected elements exist in the snapshot
@@ -1260,13 +1337,13 @@ You have access to **Playwright MCP** tools to verify the UI works correctly aft
1260
1337
  - **Multi-tab apps**: Use \`browser_tabs\` to manage multiple windows/tabs in legacy applications
1261
1338
  - **Large snapshots**: For pages with huge dropdowns (e.g., country lists), use \`browser_evaluate\` to collapse them first
1262
1339
  - ${t?"In local mode, the browser window will be visible to you":"In container mode, browser runs headless"}
1263
- </ui-verification>`}function Ze(e){return e&&e.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,"\uFFFD")}var vt=null;function et(){return Ze(vt)}import*as W from"path";import*as Ce from"fs";import{fileURLToPath as Tt}from"url";var Et=_e(),ve=W.dirname(Tt(import.meta.url)),tt=[W.resolve(ve,"../mcps/stdio-server.js"),W.resolve(ve,"./adapters/mcps/stdio-server.js")],ne=tt.find(e=>Ce.existsSync(e))||tt[0];console.log("[MCP] Stdio server path resolved:",ne);console.log("[MCP] __dirname:",ve);console.log("[MCP] File exists:",Ce.existsSync(ne));async function nt(e,t={}){let{model:n,sessionId:a=null,projectId:o=null,apiUrl:r=null,callbacks:s={},debugLog:f,abortController:_,debugMode:g=!1,isLocal:u=process.env.LOCAL_MODE==="true",projectConfig:T=O(process.env.WORKSPACE_DIR||process.cwd()),useDefaultStack:R=!0}=t,B=We(f),$=new Set,H=a||"",K,q,C,U=!1,Z=!1,I=process.env.WORKSPACE_DIR||process.cwd();console.log("[MCP] Configuring agent-planning stdio server:"),console.log("[MCP] Command: node"),console.log("[MCP] Args:",[ne]),console.log("[MCP] Env: API_URL=",process.env.API_URL||"http://localhost:5338",", PROJECT_ID=",o||process.env.PROJECT_ID||"",", PROJECT_KEY=",process.env.PROJECT_KEY?"***set***":"(not set)",", WORKSPACE_DIR=",I),console.log("[MCP] File exists check:",ne);try{for await(let d of Ct({prompt:e,options:{abortController:_,cwd:I,resume:a??void 0,allowedTools:["Bash","Read","Write","Edit","MultiEdit","Glob","Grep","LS","WebFetch","NotebookRead","NotebookEdit","Skill","mcp__agent-insights__check_backend_build","mcp__agent-insights__check_frontend_types","mcp__agent-insights__check_service_health",...u?[]:["mcp__agent-insights__stop_services","mcp__agent-insights__start_services","mcp__agent-insights__restart_services"],"mcp__agent-planning__start_question_session","mcp__agent-planning__ask_question","mcp__agent-planning__end_question_session","mcp__agent-planning__save_specification","mcp__agent-planning__list_specifications","mcp__agent-planning__read_specification","mcp__agent-planning__delete_specification","mcp__agent-planning__report_learning","mcp__agent-planning__report_bug","mcp__agent-planning__report_debug_insight","mcp__agent-planning__get_kanban_board","mcp__agent-planning__get_column_cards","mcp__agent-planning__get_card_details","mcp__agent-planning__create_kanban_card","mcp__agent-planning__update_kanban_card","mcp__agent-planning__move_kanban_card","mcp__agent-planning__delete_kanban_card","mcp__agent-planning__create_subtask","mcp__agent-planning__toggle_subtask","mcp__agent-planning__delete_subtask","mcp__agent-planning__get_project_prompt","mcp__agent-planning__update_project_prompt","mcp__agent-planning__create_command","mcp__agent-planning__get_test_suites","mcp__agent-planning__get_test_suite_details","mcp__agent-planning__get_test_details","mcp__agent-planning__run_test","mcp__agent-planning__run_test_suite","mcp__agent-planning__report_test_status","mcp__agent-planning__create_test_suite","mcp__agent-planning__create_test","mcp__agent-planning__update_test","mcp__agent-planning__reference_projects_list","mcp__agent-planning__reference_project_tree","mcp__agent-planning__reference_project_read_file","mcp__agent-planning__reference_project_download","mcp__agent-planning__reference_project_update_status","mcp__playwright__browser_navigate","mcp__playwright__browser_snapshot","mcp__playwright__browser_take_screenshot","mcp__playwright__browser_click","mcp__playwright__browser_type","mcp__playwright__browser_scroll_down","mcp__playwright__browser_scroll_up","mcp__playwright__browser_go_back","mcp__playwright__browser_go_forward","mcp__playwright__browser_wait","mcp__playwright__browser_fill_form","mcp__playwright__browser_select_option","mcp__playwright__browser_press_key","mcp__playwright__browser_hover","mcp__playwright__browser_drag","mcp__playwright__browser_file_upload","mcp__playwright__browser_handle_dialog","mcp__playwright__browser_close","mcp__playwright__browser_resize","mcp__playwright__browser_console_messages","mcp__playwright__browser_network_requests","mcp__playwright__browser_screen_capture","mcp__playwright__browser_screen_move_mouse","mcp__playwright__browser_screen_click","mcp__playwright__browser_screen_drag","mcp__playwright__browser_screen_type","mcp__playwright__browser_evaluate","mcp__playwright__browser_run_code","mcp__playwright__browser_tabs","mcp__playwright__browser_tab_list","mcp__playwright__browser_tab_new","mcp__playwright__browser_tab_select","mcp__playwright__browser_tab_close"],model:n,mcpServers:{"agent-insights":Et,"agent-planning":{type:"stdio",command:"node",args:[ne],env:{WORKSPACE_DIR:I,API_URL: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=${W.join(I,".playwright-data")}`,...u?[]:["--headless"]]}},settingSources:["project"],disallowedTools:["AskUserQuestion","TodoWrite","EnterPlanMode"],agents:R?{scaffolding:ke,debugger:ie,planning:ce,backend:we,frontend:Se,"project-context":le}:{debugger:ie,planning:ce,"project-context":le},systemPrompt:Xe({debugMode:g,projectPrompt:et(),isLocal:u,projectConfig:T,useDefaultStack:R})}}))if(!(!d.uuid||$.has(d.uuid))){switch($.add(d.uuid),B?.log(d),s.onRawMessage?.(d),d.type){case"assistant":if(d.message?.content)for(let S of d.message.content)S.type==="text"?s.onAssistantText?.(S.text):S.type==="tool_use"?s.onAssistantToolUse?.(S.name,S.input):S.type==="tool_result"&&s.onAssistantToolResult?.(S.tool_use_id,S.content);break;case"user":s.onUserMessage?.(d);break;case"result":if(d.subtype==="success")K=d.result,q=d.total_cost_usd,s.onResult?.(d.result,d.total_cost_usd);else{let S=`Agent error: ${d.subtype}`;C=S,s.onError?.(S)}Z=!0;break;case"system":switch(d.subtype){case"init":H=d.session_id,B?.setSessionId(d.session_id),s.onSystemInit?.(d.session_id);break;case"status":s.onSystemStatus?.(JSON.stringify(d));break;case"compact_boundary":break;case"hook_response":break}break;case"stream_event":s.onStreamEvent?.(d);break;case"tool_progress":s.onToolProgress?.(d);break;case"auth_status":s.onAuthStatus?.(d);break}if(Z)break}}catch(d){d instanceof Error&&d.name==="AbortError"||d instanceof Error&&d.message.includes("aborted by user")?(U=!0,s.onAborted?.()):(C=d instanceof Error?d.message:String(d),s.onError?.(C))}finally{B?.stop()}return console.log("after try"),{sessionId:H,result:K,cost:q,error:C,aborted:U}}function ot(e){return`User answered the questions:
1340
+ </ui-verification>`}function Ze(e){return e&&e.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,"\uFFFD")}var vt=null;function et(){return Ze(vt)}import*as W from"path";import*as Te from"fs";import{fileURLToPath as Ct}from"url";var Et=_e(),ve=W.dirname(Ct(import.meta.url)),tt=[W.resolve(ve,"../mcps/stdio-server.js"),W.resolve(ve,"./adapters/mcps/stdio-server.js")],ne=tt.find(e=>Te.existsSync(e))||tt[0];console.log("[MCP] Stdio server path resolved:",ne);console.log("[MCP] __dirname:",ve);console.log("[MCP] File exists:",Te.existsSync(ne));async function nt(e,t={}){let{model:n,sessionId:a=null,projectId:s=null,apiUrl:o=null,callbacks:r={},debugLog:g,abortController:_,debugMode:f=!1,isLocal:u=process.env.LOCAL_MODE==="true",projectConfig:C=O(process.env.WORKSPACE_DIR||process.cwd()),useDefaultStack:R=!0}=t,B=We(g),q=new Set,Y=a||"",K,$,T,U=!1,Z=!1,I=process.env.WORKSPACE_DIR||process.cwd();console.log("[MCP] Configuring agent-planning stdio server:"),console.log("[MCP] Command: node"),console.log("[MCP] Args:",[ne]),console.log("[MCP] Env: API_URL=",process.env.API_URL||"http://localhost:5338",", PROJECT_ID=",s||process.env.PROJECT_ID||"",", PROJECT_KEY=",process.env.PROJECT_KEY?"***set***":"(not set)",", WORKSPACE_DIR=",I),console.log("[MCP] File exists check:",ne);try{for await(let d of Tt({prompt:e,options:{abortController:_,cwd:I,resume:a??void 0,allowedTools:["Bash","Read","Write","Edit","MultiEdit","Glob","Grep","LS","WebFetch","NotebookRead","NotebookEdit","Skill","mcp__agent-insights__check_backend_build","mcp__agent-insights__check_frontend_types","mcp__agent-insights__check_service_health",...u?[]:["mcp__agent-insights__stop_services","mcp__agent-insights__start_services","mcp__agent-insights__restart_services"],"mcp__agent-planning__start_question_session","mcp__agent-planning__ask_question","mcp__agent-planning__end_question_session","mcp__agent-planning__save_specification","mcp__agent-planning__list_specifications","mcp__agent-planning__read_specification","mcp__agent-planning__delete_specification","mcp__agent-planning__report_learning","mcp__agent-planning__report_bug","mcp__agent-planning__report_debug_insight","mcp__agent-planning__get_kanban_board","mcp__agent-planning__get_column_cards","mcp__agent-planning__get_card_details","mcp__agent-planning__create_kanban_card","mcp__agent-planning__update_kanban_card","mcp__agent-planning__move_kanban_card","mcp__agent-planning__delete_kanban_card","mcp__agent-planning__create_subtask","mcp__agent-planning__toggle_subtask","mcp__agent-planning__delete_subtask","mcp__agent-planning__get_project_prompt","mcp__agent-planning__update_project_prompt","mcp__agent-planning__create_command","mcp__agent-planning__get_test_suites","mcp__agent-planning__get_test_suite_details","mcp__agent-planning__get_test_details","mcp__agent-planning__run_test","mcp__agent-planning__run_test_suite","mcp__agent-planning__report_test_status","mcp__agent-planning__create_test_suite","mcp__agent-planning__create_test","mcp__agent-planning__update_test","mcp__agent-planning__reference_projects_list","mcp__agent-planning__reference_project_tree","mcp__agent-planning__reference_project_read_file","mcp__agent-planning__reference_project_download","mcp__agent-planning__reference_project_update_status","mcp__playwright__browser_navigate","mcp__playwright__browser_snapshot","mcp__playwright__browser_take_screenshot","mcp__playwright__browser_click","mcp__playwright__browser_type","mcp__playwright__browser_scroll_down","mcp__playwright__browser_scroll_up","mcp__playwright__browser_go_back","mcp__playwright__browser_go_forward","mcp__playwright__browser_wait","mcp__playwright__browser_fill_form","mcp__playwright__browser_select_option","mcp__playwright__browser_press_key","mcp__playwright__browser_hover","mcp__playwright__browser_drag","mcp__playwright__browser_file_upload","mcp__playwright__browser_handle_dialog","mcp__playwright__browser_close","mcp__playwright__browser_resize","mcp__playwright__browser_console_messages","mcp__playwright__browser_network_requests","mcp__playwright__browser_screen_capture","mcp__playwright__browser_screen_move_mouse","mcp__playwright__browser_screen_click","mcp__playwright__browser_screen_drag","mcp__playwright__browser_screen_type","mcp__playwright__browser_evaluate","mcp__playwright__browser_run_code","mcp__playwright__browser_tabs","mcp__playwright__browser_tab_list","mcp__playwright__browser_tab_new","mcp__playwright__browser_tab_select","mcp__playwright__browser_tab_close"],model:n,mcpServers:{"agent-insights":Et,"agent-planning":{type:"stdio",command:"node",args:[ne],env:{WORKSPACE_DIR:I,API_URL:o||process.env.API_URL||process.env.MASTER_URL||"http://localhost:5338",PROJECT_ID:s||process.env.PROJECT_ID||"",PROJECT_KEY:process.env.PROJECT_KEY||""}},playwright:{type:"stdio",command:"npx",args:["@playwright/mcp@latest","--caps=vision",`--user-data-dir=${W.join(I,".playwright-data")}`,...u?[]:["--headless"]]}},settingSources:["project"],disallowedTools:["AskUserQuestion","TodoWrite","EnterPlanMode"],agents:R?{scaffolding:ke,debugger:ie,planning:ce,backend:we,frontend:Se,"project-context":le}:{debugger:ie,planning:ce,"project-context":le},systemPrompt:Xe({debugMode:f,projectPrompt:et(),isLocal:u,projectConfig:C,useDefaultStack:R})}}))if(!(!d.uuid||q.has(d.uuid))){switch(q.add(d.uuid),B?.log(d),r.onRawMessage?.(d),d.type){case"assistant":if(d.message?.content)for(let S of d.message.content)S.type==="text"?r.onAssistantText?.(S.text):S.type==="tool_use"?r.onAssistantToolUse?.(S.name,S.input):S.type==="tool_result"&&r.onAssistantToolResult?.(S.tool_use_id,S.content);break;case"user":r.onUserMessage?.(d);break;case"result":if(d.subtype==="success")K=d.result,$=d.total_cost_usd,r.onResult?.(d.result,d.total_cost_usd);else{let S=`Agent error: ${d.subtype}`;T=S,r.onError?.(S)}Z=!0;break;case"system":switch(d.subtype){case"init":Y=d.session_id,B?.setSessionId(d.session_id),r.onSystemInit?.(d.session_id);break;case"status":r.onSystemStatus?.(JSON.stringify(d));break;case"compact_boundary":break;case"hook_response":break}break;case"stream_event":r.onStreamEvent?.(d);break;case"tool_progress":r.onToolProgress?.(d);break;case"auth_status":r.onAuthStatus?.(d);break}if(Z)break}}catch(d){d instanceof Error&&d.name==="AbortError"||d instanceof Error&&d.message.includes("aborted by user")?(U=!0,r.onAborted?.()):(T=d instanceof Error?d.message:String(d),r.onError?.(T))}finally{B?.stop()}return console.log("after try"),{sessionId:Y,result:K,cost:$,error:T,aborted:U}}function st(e){return`User answered the questions:
1264
1341
 
1265
- ${Object.entries(e).map(([n,a])=>{let o=Array.isArray(a)?a.join(", "):a;return`${n}: ${o}`}).join(`
1266
- `)}`}import{spawn as st,execSync as G}from"child_process";import*as b from"fs";import*as rt from"http";import*as j from"path";function at(e){let{workspaceDir:t,apiPort:n,webPort:a,onBackendError:o,onFrontendError:r,projectConfig:s=O(t)}=e,f=n??s.services?.backend?.port??5338,_=a??s.services?.frontend?.port??5173,g=F(s),u=N(s),T=s.services?.backend?.startCommand??"dotnet run",R=s.services?.backend?.buildCommand??"dotnet build",B=s.services?.backend?.typecheckCommand??"dotnet build --no-restore",$=s.services?.frontend?.startCommand??"npm run dev",H=s.services?.frontend?.typecheckCommand??"npx tsc -p tsconfig.app.json --noEmit",K=s.services?.backend?.logFile??"/tmp/api.log",q=s.services?.frontend?.logFile??"/tmp/web.log",C=null,U=null,Z=l=>new Promise(i=>{let p=setTimeout(()=>i(!1),3e3);rt.get(`http://localhost:${l}`,v=>{clearTimeout(p),i(v.statusCode!==void 0&&v.statusCode<500)}).on("error",()=>{clearTimeout(p),i(!1)})}),I=l=>{try{let i=G(`lsof -ti:${l} 2>/dev/null || true`,{encoding:"utf-8"}).trim();if(i){let p=i.split(`
1267
- `).filter(Boolean);console.log(`[Services] Killing ${p.length} process(es) on port ${l}: ${p.join(", ")}`);for(let m of p)try{process.kill(parseInt(m,10),"SIGKILL")}catch{}}}catch{try{G(`fuser -k ${l}/tcp 2>/dev/null || true`,{encoding:"utf-8"})}catch{}}},d=(l,i)=>new Promise(p=>{if(!l||!l.pid){p();return}console.log(`[Services] Stopping ${i} (PID: ${l.pid})...`);try{process.kill(-l.pid,"SIGTERM")}catch{try{l.kill("SIGTERM")}catch{}}setTimeout(p,500)}),S=async()=>{if(!g||!b.existsSync(g)){console.log("[Services] No backend found, skipping backend start");return}I(f),console.log(`[Services] Starting backend with: ${T}...`);let l=j.dirname(K);b.existsSync(l)||b.mkdirSync(l,{recursive:!0});let i=b.createWriteStream(K,{flags:"a"}),[p,...m]=T.split(" ");C=st(p,m,{cwd:g,stdio:["ignore","pipe","pipe"],detached:!0,shell:!0});let v="",k=h=>{let L=h.toString();i.write(L);let A=(v+L).split(`
1268
- `);v=A.pop()||"";for(let x of A)x&&Pt(x)&&o&&o(x)};C.stdout?.on("data",k),C.stderr?.on("data",k),C.on("exit",h=>{i.end(),h!==0&&h!==null&&o&&o(`Process exited with code ${h}`)}),C.unref()},Te=async()=>{if(!u||!b.existsSync(j.join(u,"package.json"))){console.log("[Services] No frontend found, skipping frontend start");return}I(_),console.log(`[Services] Starting frontend with: ${$}...`);let l=j.dirname(q);b.existsSync(l)||b.mkdirSync(l,{recursive:!0});let[i,...p]=$.split(" ");U=st(i,p,{cwd:u,stdio:["ignore",b.openSync(q,"w"),b.openSync(q,"w")],detached:!0,shell:!0}),U.unref()},Ee=async()=>{await d(C,"backend"),C=null,I(f)},Pe=async()=>{await d(U,"frontend"),U=null,I(_)},fe=async()=>{let[l,i]=await Promise.all([Z(f),Z(_)]);return{api:l,web:i}},Ae=async(l=15e3)=>{let i=Date.now(),p=1e3,m=0,v=null,k=null;for(console.log(`[Services] Waiting for health (timeout: ${l}ms)...`);Date.now()-i<l;){m++;let A=await fe(),x=Date.now()-i;if(A.web&&k===null&&(k=x,console.log(`[Services] \u2713 Frontend (Vite) ready after ${x}ms`)),A.api&&v===null&&(v=x,console.log(`[Services] \u2713 Backend (dotnet) ready after ${x}ms`)),console.log(`[Services] Health poll #${m} (${x}ms): api=${A.api}, web=${A.web}`),A.api&&A.web)return console.log(`[Services] \u2713 Both services healthy after ${x}ms (${m} polls)`),A;await new Promise(dt=>setTimeout(dt,p))}let h=await fe(),L=Date.now()-i;return h.web&&k===null&&console.log(`[Services] \u2713 Frontend (Vite) ready after ${L}ms`),h.api&&v===null&&console.log(`[Services] \u2713 Backend (dotnet) ready after ${L}ms`),console.log(`[Services] \u26A0 Health timeout after ${L}ms: api=${h.api}, web=${h.web}`),h},ct=async l=>{console.log(`[Services] Restarting ${l}...`);let i={success:!0},p=l==="backend"||l==="both",m=l==="frontend"||l==="both";if(p&&await Ee(),m&&await Pe(),p&&g&&b.existsSync(g)){console.log(`[Services] Building backend with: ${R}...`);try{G(R,{cwd:g,stdio:"pipe",timeout:12e4,encoding:"utf-8"}),console.log("[Services] \u2713 Backend build succeeded")}catch(k){let h=k;i.success=!1,i.backendError=h.stderr||h.stdout||"Build failed",console.error("[Services] \u2717 Backend build failed")}}if(m&&u&&b.existsSync(j.join(u,"package.json"))){console.log(`[Services] Type-checking frontend with: ${H}...`);try{G(H,{cwd:u,stdio:"pipe",timeout:6e4,encoding:"utf-8"}),console.log("[Services] \u2713 Frontend types OK")}catch(k){let h=k;i.success=!1,i.frontendError=h.stderr||h.stdout||"Type check failed",console.error("[Services] \u2717 Frontend type check failed")}}console.log(`[Services] Starting ${l}...`),p&&await S(),m&&await Te();let v=await Ae(1e4);if(p&&!v.api){i.success=!1;let k=await Ie("backend",20),h=k.length>0?`
1342
+ ${Object.entries(e).map(([n,a])=>{let s=Array.isArray(a)?a.join(", "):a;return`${n}: ${s}`}).join(`
1343
+ `)}`}import{spawn as rt,execSync as G}from"child_process";import*as b from"fs";import*as ot from"http";import*as j from"path";function at(e){let{workspaceDir:t,apiPort:n,webPort:a,onBackendError:s,onFrontendError:o,projectConfig:r=O(t)}=e,g=n??r.services?.backend?.port??5338,_=a??r.services?.frontend?.port??5173,f=F(r),u=N(r),C=r.services?.backend?.startCommand??"dotnet run",R=r.services?.backend?.buildCommand??"dotnet build",B=r.services?.backend?.typecheckCommand??"dotnet build --no-restore",q=r.services?.frontend?.startCommand??"npm run dev",Y=r.services?.frontend?.typecheckCommand??"npx tsc -p tsconfig.app.json --noEmit",K=r.services?.backend?.logFile??"/tmp/api.log",$=r.services?.frontend?.logFile??"/tmp/web.log",T=null,U=null,Z=l=>new Promise(i=>{let p=setTimeout(()=>i(!1),3e3);ot.get(`http://localhost:${l}`,v=>{clearTimeout(p),i(v.statusCode!==void 0&&v.statusCode<500)}).on("error",()=>{clearTimeout(p),i(!1)})}),I=l=>{try{let i=G(`lsof -ti:${l} 2>/dev/null || true`,{encoding:"utf-8"}).trim();if(i){let p=i.split(`
1344
+ `).filter(Boolean);console.log(`[Services] Killing ${p.length} process(es) on port ${l}: ${p.join(", ")}`);for(let m of p)try{process.kill(parseInt(m,10),"SIGKILL")}catch{}}}catch{try{G(`fuser -k ${l}/tcp 2>/dev/null || true`,{encoding:"utf-8"})}catch{}}},d=(l,i)=>new Promise(p=>{if(!l||!l.pid){p();return}console.log(`[Services] Stopping ${i} (PID: ${l.pid})...`);try{process.kill(-l.pid,"SIGTERM")}catch{try{l.kill("SIGTERM")}catch{}}setTimeout(p,500)}),S=async()=>{if(!f||!b.existsSync(f)){console.log("[Services] No backend found, skipping backend start");return}I(g),console.log(`[Services] Starting backend with: ${C}...`);let l=j.dirname(K);b.existsSync(l)||b.mkdirSync(l,{recursive:!0});let i=b.createWriteStream(K,{flags:"a"}),[p,...m]=C.split(" ");T=rt(p,m,{cwd:f,stdio:["ignore","pipe","pipe"],detached:!0,shell:!0});let v="",k=h=>{let L=h.toString();i.write(L);let A=(v+L).split(`
1345
+ `);v=A.pop()||"";for(let x of A)x&&Pt(x)&&s&&s(x)};T.stdout?.on("data",k),T.stderr?.on("data",k),T.on("exit",h=>{i.end(),h!==0&&h!==null&&s&&s(`Process exited with code ${h}`)}),T.unref()},Ce=async()=>{if(!u||!b.existsSync(j.join(u,"package.json"))){console.log("[Services] No frontend found, skipping frontend start");return}I(_),console.log(`[Services] Starting frontend with: ${q}...`);let l=j.dirname($);b.existsSync(l)||b.mkdirSync(l,{recursive:!0});let[i,...p]=q.split(" ");U=rt(i,p,{cwd:u,stdio:["ignore",b.openSync($,"w"),b.openSync($,"w")],detached:!0,shell:!0}),U.unref()},Ee=async()=>{await d(T,"backend"),T=null,I(g)},Pe=async()=>{await d(U,"frontend"),U=null,I(_)},ge=async()=>{let[l,i]=await Promise.all([Z(g),Z(_)]);return{api:l,web:i}},Ae=async(l=15e3)=>{let i=Date.now(),p=1e3,m=0,v=null,k=null;for(console.log(`[Services] Waiting for health (timeout: ${l}ms)...`);Date.now()-i<l;){m++;let A=await ge(),x=Date.now()-i;if(A.web&&k===null&&(k=x,console.log(`[Services] \u2713 Frontend (Vite) ready after ${x}ms`)),A.api&&v===null&&(v=x,console.log(`[Services] \u2713 Backend (dotnet) ready after ${x}ms`)),console.log(`[Services] Health poll #${m} (${x}ms): api=${A.api}, web=${A.web}`),A.api&&A.web)return console.log(`[Services] \u2713 Both services healthy after ${x}ms (${m} polls)`),A;await new Promise(dt=>setTimeout(dt,p))}let h=await ge(),L=Date.now()-i;return h.web&&k===null&&console.log(`[Services] \u2713 Frontend (Vite) ready after ${L}ms`),h.api&&v===null&&console.log(`[Services] \u2713 Backend (dotnet) ready after ${L}ms`),console.log(`[Services] \u26A0 Health timeout after ${L}ms: api=${h.api}, web=${h.web}`),h},ct=async l=>{console.log(`[Services] Restarting ${l}...`);let i={success:!0},p=l==="backend"||l==="both",m=l==="frontend"||l==="both";if(p&&await Ee(),m&&await Pe(),p&&f&&b.existsSync(f)){console.log(`[Services] Building backend with: ${R}...`);try{G(R,{cwd:f,stdio:"pipe",timeout:12e4,encoding:"utf-8"}),console.log("[Services] \u2713 Backend build succeeded")}catch(k){let h=k;i.success=!1,i.backendError=h.stderr||h.stdout||"Build failed",console.error("[Services] \u2717 Backend build failed")}}if(m&&u&&b.existsSync(j.join(u,"package.json"))){console.log(`[Services] Type-checking frontend with: ${Y}...`);try{G(Y,{cwd:u,stdio:"pipe",timeout:6e4,encoding:"utf-8"}),console.log("[Services] \u2713 Frontend types OK")}catch(k){let h=k;i.success=!1,i.frontendError=h.stderr||h.stdout||"Type check failed",console.error("[Services] \u2717 Frontend type check failed")}}console.log(`[Services] Starting ${l}...`),p&&await S(),m&&await Ce();let v=await Ae(1e4);if(p&&!v.api){i.success=!1;let k=await Ie("backend",20),h=k.length>0?`
1269
1346
  Recent logs:
1270
1347
  ${k.join(`
1271
- `)}`:"";i.backendError||(i.backendError=`Backend failed to start.${h}`)}return m&&!v.web&&(i.success=!1,i.frontendError||(i.frontendError="Frontend failed to start")),i},xe=async()=>{if(!g||!b.existsSync(g))return{success:!0};try{return G(B,{cwd:g,stdio:"pipe",timeout:12e4,encoding:"utf-8"}),{success:!0}}catch(l){let i=l;return{success:!1,errors:i.stdout||i.stderr||"Build failed"}}},Re=async()=>{if(!u||!b.existsSync(j.join(u,"package.json")))return{success:!0};try{return G(H,{cwd:u,stdio:"pipe",timeout:6e4,encoding:"utf-8"}),{success:!0}}catch(l){let i=l;return{success:!1,errors:i.stdout||i.stderr||"Type check failed"}}},lt=async()=>{let[l,i]=await Promise.all([xe(),Re()]);return{backend:l,frontend:i}},Ie=async(l,i)=>{let p=l==="backend"?K:q;if(!b.existsSync(p))return[`Log file not found: ${p}`];try{return G(`tail -n ${i} ${p}`,{encoding:"utf-8"}).split(`
1272
- `).filter(Boolean)}catch(m){return[`Error reading logs: ${m instanceof Error?m.message:String(m)}`]}};return{startBackend:S,startFrontend:Te,stopBackend:Ee,stopFrontend:Pe,restartServices:ct,checkHealth:fe,waitForHealth:Ae,getProcesses:()=>({backend:C,frontend:U}),checkBackendBuild:xe,checkFrontendTypes:Re,runTypeChecks:lt,tailLogs:Ie,checkSwaggerEndpoints:async l=>{let i=j.join(t,"swagger.json");if(!b.existsSync(i))return{foundEndpoints:["Error: swagger.json not found"],totalEndpoints:0};try{let p=JSON.parse(b.readFileSync(i,"utf-8")),m=Object.keys(p.paths||{}),v=[];for(let k of m)if(k.toLowerCase().includes(l.toLowerCase())){let h=Object.keys(p.paths[k]);for(let L of h)v.push(`${L.toUpperCase()} ${k}`)}return{foundEndpoints:v,totalEndpoints:m.length}}catch(p){return{foundEndpoints:[`Error parsing swagger.json: ${p instanceof Error?p.message:String(p)}`],totalEndpoints:0}}}}}function Pt(e){let t=e.toLowerCase();return t.includes("exception")||t.includes("fail:")||t.includes("[err]")||t.includes("[error]")}import{execSync as Y}from"child_process";function it(e){let t=async()=>{try{return Y("git status --porcelain",{cwd:e,encoding:"utf-8"}).trim().length>0}catch{return!1}},n=async()=>{try{return Y("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 s=o.length>60?o.substring(0,60)+"...":o,_=`${r?"[agent] (partial)":"[agent]"} ${s}`;Y("git add -A",{cwd:e}),Y(`git commit -m "${_.replace(/"/g,'\\"')}"`,{cwd:e,encoding:"utf-8"});let g=Y("git rev-parse --short HEAD",{cwd:e,encoding:"utf-8"}).trim();try{Y("git push",{cwd:e,stdio:"pipe"})}catch(u){let T=u instanceof Error?u.message:String(u);if(T.includes("no upstream branch")){let R=await n();Y(`git push --set-upstream origin ${R}`,{cwd:e,stdio:"pipe"})}else return console.error("[Git] Push failed:",T),{success:!0,commitHash:g,error:`Committed but push failed: ${T}`}}return{success:!0,commitHash:g}}catch(s){let f=s instanceof Error?s.message:String(s);return console.error("[Git] Error:",f),{success:!1,error:f}}},getCurrentBranch:n}}export{ee as ProjectConfigSchema,be as createFilePlanningTransport,he as createFileTransport,it as createLocalGitManager,at as createLocalServiceManager,_e as createMcpServer,ot as formatAnswersAsPrompt,te as getBackendFeaturesPath,F as getBackendPath,O as getDefaultConfig,X as getFrontendFeaturesPath,N as getFrontendPath,Je as getPathsDescription,ge as getServicesDescription,ue as getTechStackDescription,Ve as isBackendFile,ze as isFrontendFile,Ke as loadProjectConfig,ye as readPendingQuestions,pe as resolveConfigPath,nt as runAgent,Qe as saveProjectConfig,Oe as setPlanningTransport,Fe as setServiceManager};
1348
+ `)}`:"";i.backendError||(i.backendError=`Backend failed to start.${h}`)}return m&&!v.web&&(i.success=!1,i.frontendError||(i.frontendError="Frontend failed to start")),i},xe=async()=>{if(!f||!b.existsSync(f))return{success:!0};try{return G(B,{cwd:f,stdio:"pipe",timeout:12e4,encoding:"utf-8"}),{success:!0}}catch(l){let i=l;return{success:!1,errors:i.stdout||i.stderr||"Build failed"}}},Re=async()=>{if(!u||!b.existsSync(j.join(u,"package.json")))return{success:!0};try{return G(Y,{cwd:u,stdio:"pipe",timeout:6e4,encoding:"utf-8"}),{success:!0}}catch(l){let i=l;return{success:!1,errors:i.stdout||i.stderr||"Type check failed"}}},lt=async()=>{let[l,i]=await Promise.all([xe(),Re()]);return{backend:l,frontend:i}},Ie=async(l,i)=>{let p=l==="backend"?K:$;if(!b.existsSync(p))return[`Log file not found: ${p}`];try{return G(`tail -n ${i} ${p}`,{encoding:"utf-8"}).split(`
1349
+ `).filter(Boolean)}catch(m){return[`Error reading logs: ${m instanceof Error?m.message:String(m)}`]}};return{startBackend:S,startFrontend:Ce,stopBackend:Ee,stopFrontend:Pe,restartServices:ct,checkHealth:ge,waitForHealth:Ae,getProcesses:()=>({backend:T,frontend:U}),checkBackendBuild:xe,checkFrontendTypes:Re,runTypeChecks:lt,tailLogs:Ie,checkSwaggerEndpoints:async l=>{let i=j.join(t,"swagger.json");if(!b.existsSync(i))return{foundEndpoints:["Error: swagger.json not found"],totalEndpoints:0};try{let p=JSON.parse(b.readFileSync(i,"utf-8")),m=Object.keys(p.paths||{}),v=[];for(let k of m)if(k.toLowerCase().includes(l.toLowerCase())){let h=Object.keys(p.paths[k]);for(let L of h)v.push(`${L.toUpperCase()} ${k}`)}return{foundEndpoints:v,totalEndpoints:m.length}}catch(p){return{foundEndpoints:[`Error parsing swagger.json: ${p instanceof Error?p.message:String(p)}`],totalEndpoints:0}}}}}function Pt(e){let t=e.toLowerCase();return t.includes("exception")||t.includes("fail:")||t.includes("[err]")||t.includes("[error]")}import{execSync as H}from"child_process";function it(e){let t=async()=>{try{return H("git status --porcelain",{cwd:e,encoding:"utf-8"}).trim().length>0}catch{return!1}},n=async()=>{try{return H("git branch --show-current",{cwd:e,encoding:"utf-8"}).trim()}catch{return"unknown"}};return{hasChanges:t,commitAndPush:async(s,o)=>{try{if(!await t())return{success:!0,noChanges:!0};let r=s.length>60?s.substring(0,60)+"...":s,_=`${o?"[agent] (partial)":"[agent]"} ${r}`;H("git add -A",{cwd:e}),H(`git commit -m "${_.replace(/"/g,'\\"')}"`,{cwd:e,encoding:"utf-8"});let f=H("git rev-parse --short HEAD",{cwd:e,encoding:"utf-8"}).trim();try{H("git push",{cwd:e,stdio:"pipe"})}catch(u){let C=u instanceof Error?u.message:String(u);if(C.includes("no upstream branch")){let R=await n();H(`git push --set-upstream origin ${R}`,{cwd:e,stdio:"pipe"})}else return console.error("[Git] Push failed:",C),{success:!0,commitHash:f,error:`Committed but push failed: ${C}`}}return{success:!0,commitHash:f}}catch(r){let g=r instanceof Error?r.message:String(r);return console.error("[Git] Error:",g),{success:!1,error:g}}},getCurrentBranch:n}}export{ee as ProjectConfigSchema,be as createFilePlanningTransport,he as createFileTransport,it as createLocalGitManager,at as createLocalServiceManager,_e as createMcpServer,st as formatAnswersAsPrompt,te as getBackendFeaturesPath,F as getBackendPath,O as getDefaultConfig,X as getFrontendFeaturesPath,N as getFrontendPath,Je as getPathsDescription,fe as getServicesDescription,ue as getTechStackDescription,Ve as isBackendFile,ze as isFrontendFile,Ke as loadProjectConfig,ye as readPendingQuestions,pe as resolveConfigPath,nt as runAgent,Qe as saveProjectConfig,Oe as setPlanningTransport,Fe as setServiceManager};