glenn-code 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -0
- package/bin/cli.js +75 -0
- package/dist/cli.js +1260 -0
- package/dist/core.js +1231 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1274 -0
- package/package.json +56 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1260 @@
|
|
|
1
|
+
// Glenn Code - Bundled and minified
|
|
2
|
+
// (c) DNM Lab - All rights reserved
|
|
3
|
+
|
|
4
|
+
import B from"inquirer";import*as K from"fs";import*as le from"path";import*as Gt from"os";import{createSdkMcpServer as zt}from"@anthropic-ai/claude-agent-sdk";import{tool as pe}from"@anthropic-ai/claude-agent-sdk";import{z as ue}from"zod";import*as de from"fs";import*as bt from"path";var q=null;function se(n){q=n}function $e(n){return n.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}var Nn=pe("save_specification",`Save a specification to the database.
|
|
5
|
+
|
|
6
|
+
IMPORTANT: First write the specification content to a file using the Write tool, then call this with the file path.
|
|
7
|
+
|
|
8
|
+
Workflow:
|
|
9
|
+
1. Use Write tool to save your spec to a temp file (e.g., /tmp/my-spec.md)
|
|
10
|
+
2. Call this tool with the name and file path
|
|
11
|
+
3. This tool reads the file and saves to database
|
|
12
|
+
|
|
13
|
+
Example:
|
|
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:ue.string().describe("Specification name (e.g., 'User Authentication')"),filePath:ue.string().describe("Path to the file containing the specification content (written via Write tool)")},async n=>{if(!q)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let e=bt.resolve(n.filePath);if(!de.existsSync(e))return{content:[{type:"text",text:`\u274C File not found: ${n.filePath}. Use Write tool first to create the file.`}]};let t=de.readFileSync(e,"utf-8"),s=$e(n.name);return await q.saveSpecification(s,t),{content:[{type:"text",text:`\u2705 Saved specification: ${s}.spec.md`}]}}),Fn=pe("read_specification","Read the content of a specification.",{name:ue.string().describe("Name of the specification to read")},async n=>{if(!q)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let e=$e(n.name),t=await q.getSpecification(e);return t?{content:[{type:"text",text:t}]}:{content:[{type:"text",text:`\u274C Specification "${n.name}" not found.`}]}}),Ln=pe("list_specifications","List all specifications.",{},async()=>{if(!q)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let n=await q.listSpecifications();return n.length===0?{content:[{type:"text",text:"\u{1F4CB} No specifications found."}]}:{content:[{type:"text",text:`\u{1F4CB} Specifications:
|
|
16
|
+
|
|
17
|
+
${n.map(t=>`- ${t.name} (${t.slug}.spec.md)`).join(`
|
|
18
|
+
`)}`}]}}),$n=pe("delete_specification","Delete a specification.",{name:ue.string().describe("Name of the specification to delete")},async n=>{if(!q)return{content:[{type:"text",text:"\u274C Planning transport not initialized"}]};let e=$e(n.name);return await q.deleteSpecification(e)?{content:[{type:"text",text:`\u{1F5D1}\uFE0F Deleted: ${e}.spec.md`}]}:{content:[{type:"text",text:`\u274C Specification "${n.name}" not found.`}]}});import{tool as Y}from"@anthropic-ai/claude-agent-sdk";import{z as ee}from"zod";var T=null;function ge(n){T=n}var St=Y("restart_services",`Rebuild and restart services. Call this after making backend changes (.cs files) to apply them.
|
|
19
|
+
|
|
20
|
+
Parameters:
|
|
21
|
+
- target: "backend" (default) | "frontend" | "both"
|
|
22
|
+
|
|
23
|
+
Use target="backend" after .cs file changes (faster, only restarts .NET).
|
|
24
|
+
Use target="frontend" if Vite is stuck (rare, HMR usually works).
|
|
25
|
+
Use target="both" if unsure or both need restart.`,{target:ee.enum(["backend","frontend","both"]).default("backend").describe("Which service to restart")},async n=>{if(!T)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let e=n.target||"backend";console.log(`[MCP] restart_services called (target: ${e})`);let t=await T.restartServices(e);if(!t.success){let o=[];return t.backendError&&o.push(`Backend: ${t.backendError}`),t.frontendError&&o.push(`Frontend: ${t.frontendError}`),{content:[{type:"text",text:`\u274C Restart failed:
|
|
26
|
+
${o.join(`
|
|
27
|
+
|
|
28
|
+
`)}`}]}}return{content:[{type:"text",text:`\u2705 ${e==="both"?"Backend and frontend":e==="backend"?"Backend":"Frontend"} restarted successfully.`}]}}),kt=Y("stop_services",`Stop running services. Call this BEFORE running scaffold to prevent port conflicts and ensure clean migrations.
|
|
29
|
+
|
|
30
|
+
Parameters:
|
|
31
|
+
- target: "backend" (default) | "frontend" | "both"
|
|
32
|
+
|
|
33
|
+
Use target="backend" before running scaffold (required for migrations).`,{target:ee.enum(["backend","frontend","both"]).default("backend").describe("Which service to stop")},async n=>{if(!T)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let e=n.target||"backend";console.log(`[MCP] stop_services called (target: ${e})`);let t=e==="backend"||e==="both",s=e==="frontend"||e==="both";t&&await T.stopBackend(),s&&await T.stopFrontend();let o=5e3,r=Date.now(),i=!t,a=!s;for(;Date.now()-r<o;){let p=await T.checkHealth();if(t&&!p.api&&(i=!0),s&&!p.web&&(a=!0),i&&a)break;await new Promise(u=>setTimeout(u,500))}let c=e==="both"?"Backend and frontend":e==="backend"?"Backend":"Frontend";return{content:[{type:"text",text:i&&a?`\u2705 ${c} stopped successfully.`:`\u26A0\uFE0F ${c} stop requested but health check still shows running. Proceeding anyway.`}]}}),vt=Y("start_services",`Start services. Call this AFTER running scaffold to bring services back up.
|
|
34
|
+
|
|
35
|
+
Parameters:
|
|
36
|
+
- target: "backend" (default) | "frontend" | "both"
|
|
37
|
+
|
|
38
|
+
Use target="backend" after running scaffold.`,{target:ee.enum(["backend","frontend","both"]).default("backend").describe("Which service to start")},async n=>{if(!T)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let e=n.target||"backend";console.log(`[MCP] start_services called (target: ${e})`);let t=e==="backend"||e==="both",s=e==="frontend"||e==="both";t&&await T.startBackend(),s&&await T.startFrontend();let o=await T.waitForHealth(15e3),r=!t||o.api,i=!s||o.web,a=r&&i,c=e==="both"?"Backend and frontend":e==="backend"?"Backend":"Frontend";if(!a){let l=[];return t&&!o.api&&l.push("Backend failed to start"),s&&!o.web&&l.push("Frontend failed to start"),{content:[{type:"text",text:`\u274C ${c} failed to start: ${l.join(", ")}`}]}}return{content:[{type:"text",text:`\u2705 ${c} started successfully.`}]}}),Ct=Y("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(!T)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let n=await T.checkHealth();return{content:[{type:"text",text:`Service Health:
|
|
40
|
+
\u{1F5A5}\uFE0F Backend API: ${n.api?"\u2705 running":"\u274C not running"}
|
|
41
|
+
\u{1F310} Frontend: ${n.web?"\u2705 running":"\u274C not running"}`}]}}),wt=Y("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(!T)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};console.log("[MCP] check_backend_build called");let n=await T.checkBackendBuild();return n.success?{content:[{type:"text",text:"\u2705 Backend build succeeded - no errors"}]}:{content:[{type:"text",text:`\u274C Backend build failed:
|
|
42
|
+
|
|
43
|
+
${n.errors}`}]}}),Pt=Y("check_frontend_types","Run TypeScript type check (tsc) on frontend code. Use this after fixing frontend code to verify the fix works.",{},async()=>{if(!T)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};console.log("[MCP] check_frontend_types called");let n=await T.checkFrontendTypes();return n.success?{content:[{type:"text",text:"\u2705 Frontend type check passed - no errors"}]}:{content:[{type:"text",text:`\u274C Frontend type errors:
|
|
44
|
+
|
|
45
|
+
${n.errors}`}]}}),qn=Y("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
|
+
|
|
47
|
+
Parameters:
|
|
48
|
+
- target: "backend" | "frontend"
|
|
49
|
+
- lines: number (default 50)`,{target:ee.enum(["backend","frontend"]).describe("Which log to read"),lines:ee.number().default(50).describe("Number of lines to read")},async n=>{if(!T)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let e=await T.tailLogs(n.target,n.lines);return{content:[{type:"text",text:`Last ${n.lines} lines of ${n.target} log:
|
|
50
|
+
|
|
51
|
+
${e.join(`
|
|
52
|
+
`)}`}]}}),Gn=Y("check_swagger_endpoints",`Search for endpoints in the generated swagger.json. Use this to verify that new backend endpoints are correctly exposed.
|
|
53
|
+
|
|
54
|
+
Parameters:
|
|
55
|
+
- pattern: string (e.g., "users", "api/reports")`,{pattern:ee.string().describe("Text to search for in endpoint paths")},async n=>{if(!T)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let e=await T.checkSwaggerEndpoints(n.pattern);return e.foundEndpoints.length===0?{content:[{type:"text",text:`\u274C No endpoints found matching "${n.pattern}" (Total endpoints: ${e.totalEndpoints})`}]}:{content:[{type:"text",text:`\u2705 Found ${e.foundEndpoints.length} matching endpoints:
|
|
56
|
+
|
|
57
|
+
${e.foundEndpoints.join(`
|
|
58
|
+
`)}`}]}});import*as re from"fs";import*as Jt from"path";import*as x from"fs";import*as fe from"path";function ie(n){let e=fe.join(n,".sdd","specifications");function t(){x.existsSync(e)||x.mkdirSync(e,{recursive:!0})}function s(o){return fe.join(e,`${o}.spec.md`)}return{async saveSpecification(o,r){t(),x.writeFileSync(s(o),r,"utf-8")},async getSpecification(o){t();let r=s(o);return x.existsSync(r)?x.readFileSync(r,"utf-8"):null},async listSpecifications(){return t(),x.readdirSync(e).filter(r=>r.endsWith(".spec.md")).map(r=>{let i=x.readFileSync(fe.join(e,r),"utf-8"),a=i.match(/name: "([^"]+)"/),c=i.match(/status: (\w+)/),l=i.match(/version: "([^"]+)"/);return{slug:r.replace(".spec.md",""),name:a?.[1]||r,status:c?.[1]||"unknown",version:l?.[1]||"1.0.0"}})},async deleteSpecification(o){t();let r=s(o);return x.existsSync(r)?(x.unlinkSync(r),!0):!1},async specificationExists(o){return t(),x.existsSync(s(o))}}}function He(){return zt({name:"agent-insights",version:"1.0.0",tools:[St,kt,vt,Ct,wt,Pt]})}import{query as mn}from"@anthropic-ai/claude-agent-sdk";var Be={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
|
|
64
|
+
|
|
65
|
+
**\u26A1 ONE-SHOT RULE: Put ALL entities in ONE JSON. The CLI handles dependencies.**
|
|
66
|
+
|
|
67
|
+
## WORKFLOW
|
|
68
|
+
|
|
69
|
+
### Step 0: Check Existing Features (ALWAYS DO THIS FIRST)
|
|
70
|
+
\`\`\`bash
|
|
71
|
+
ls /workspace/packages/dotnet-api/Source/Features/
|
|
72
|
+
\`\`\`
|
|
73
|
+
This shows what already exists. **DO NOT scaffold entities that already exist!**
|
|
74
|
+
Common existing features: Users (auth), Authentication, KanbanBoard, Document, etc.
|
|
75
|
+
|
|
76
|
+
### Step 1: Write Schema
|
|
77
|
+
\`\`\`bash
|
|
78
|
+
# Use Write tool to create /tmp/scaffold.json
|
|
79
|
+
\`\`\`
|
|
80
|
+
|
|
81
|
+
### Step 2: Run Scaffold
|
|
82
|
+
\`\`\`bash
|
|
83
|
+
scaffold --schema /tmp/scaffold.json --output /workspace --force --full
|
|
84
|
+
\`\`\`
|
|
85
|
+
|
|
86
|
+
### Step 3: Report & STOP
|
|
87
|
+
If CLI shows "success": true, respond with:
|
|
88
|
+
- Entities created: [names from output]
|
|
89
|
+
- Routes: /super-admin/[plural] for each
|
|
90
|
+
- DONE
|
|
91
|
+
|
|
92
|
+
**CRITICAL RULES:**
|
|
93
|
+
- DO NOT run ls, find, grep, or any verification commands
|
|
94
|
+
- DO NOT explore directories to check if files exist
|
|
95
|
+
- DO NOT read generated files to verify content
|
|
96
|
+
- dropdownDisplay MUST match actual fields on target entity:
|
|
97
|
+
- The scaffold CLI output is AUTHORITATIVE
|
|
98
|
+
- When scaffold succeeds, you are DONE - stop immediately
|
|
99
|
+
|
|
100
|
+
## SCHEMA FORMAT
|
|
101
|
+
|
|
102
|
+
\`\`\`json
|
|
103
|
+
{
|
|
104
|
+
"entities": [
|
|
105
|
+
{
|
|
106
|
+
"name": "EntityName",
|
|
107
|
+
"icon": "Category",
|
|
108
|
+
"fields": [
|
|
109
|
+
{ "name": "Name", "type": "string", "required": true, "listColumn": true }
|
|
110
|
+
],
|
|
111
|
+
"relations": [],
|
|
112
|
+
"features": { "softDelete": true, "timestamps": true, "audit": true }
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
\`\`\`
|
|
117
|
+
|
|
118
|
+
## FIELD TYPES
|
|
119
|
+
- string (with optional maxLength)
|
|
120
|
+
- int, long, decimal
|
|
121
|
+
- bool
|
|
122
|
+
- DateTime, DateOnly
|
|
123
|
+
- Guid
|
|
124
|
+
- enum (requires enumValues: ["Value1", "Value2"])
|
|
125
|
+
|
|
126
|
+
## RELATIONS
|
|
127
|
+
\`\`\`json
|
|
128
|
+
{ "name": "Author", "type": "belongsTo", "target": "Author", "required": true, "dropdownDisplay": "Name", "listColumn": true }
|
|
129
|
+
\`\`\`
|
|
130
|
+
Types: belongsTo, hasMany, hasOne
|
|
131
|
+
|
|
132
|
+
**CRITICAL - dropdownDisplay MUST match actual fields on target entity:**
|
|
133
|
+
- If target has \`Name\` field \u2192 \`"dropdownDisplay": "Name"\`
|
|
134
|
+
- If target has \`FirstName\` + \`LastName\` \u2192 \`"dropdownDisplay": "FirstName LastName"\` (space-separated for multi-field)
|
|
135
|
+
- If target has \`Title\` field \u2192 \`"dropdownDisplay": "Title"\`
|
|
136
|
+
- User entity always has \`FullName\` \u2192 \`"dropdownDisplay": "FullName"\`
|
|
137
|
+
|
|
138
|
+
**DEFAULT BEHAVIOR:** If omitted, defaults to "Name" - will FAIL if target entity has no Name field!
|
|
139
|
+
|
|
140
|
+
User relations: \`{ "name": "Owner", "type": "belongsTo", "target": "User", "dropdownDisplay": "FullName" }\`
|
|
141
|
+
|
|
142
|
+
## AUTO-GENERATED FIELDS (DO NOT DEFINE MANUALLY)
|
|
143
|
+
When features are enabled, these fields are auto-generated:
|
|
144
|
+
- \`timestamps: true\` \u2192 CreatedAt, UpdatedAt
|
|
145
|
+
- \`softDelete: true\` \u2192 IsDeleted, DeletedAt
|
|
146
|
+
- \`audit: true\` \u2192 CreatedByUserId
|
|
147
|
+
|
|
148
|
+
\u26A0\uFE0F DO NOT add these as manual fields - they will be filtered out!
|
|
149
|
+
|
|
150
|
+
## RESERVED NAMES (DO NOT USE)
|
|
151
|
+
Task, File, Directory, String, Object, Type, Action, Event, Exception, List, Thread, Timer, Stream, DateTime, Guid, **User**
|
|
152
|
+
|
|
153
|
+
**\u26A0\uFE0F User entity already exists!** The system has ASP.NET Identity with ApplicationUser at \`Features/Users/\`.
|
|
154
|
+
- To relate to users: \`{ "target": "User", "dropdownDisplay": "FullName" }\`
|
|
155
|
+
- User has: Id (string), Email, FullName
|
|
156
|
+
- DON'T scaffold a new User entity - use the existing one!
|
|
157
|
+
|
|
158
|
+
## ICONS
|
|
159
|
+
Person, Folder, Assignment, Inventory, MenuBook, Business, Category, Description, Email, Settings, Dashboard, ShoppingCart, AttachMoney, Receipt, Groups, ContactPhone
|
|
160
|
+
|
|
161
|
+
## \u26A1 BIG BANG SCAFFOLDING (ONE-SHOT!)
|
|
162
|
+
|
|
163
|
+
**ALWAYS scaffold ALL entities in ONE JSON, ONE run.** The CLI handles dependencies automatically.
|
|
164
|
+
DO NOT phase/split into multiple runs - that's slower and error-prone.
|
|
165
|
+
|
|
166
|
+
## EXAMPLE: University System (6 entities, circular refs, one-shot)
|
|
167
|
+
|
|
168
|
+
\`\`\`json
|
|
169
|
+
{
|
|
170
|
+
"entities": [
|
|
171
|
+
{
|
|
172
|
+
"name": "Department",
|
|
173
|
+
"icon": "Business",
|
|
174
|
+
"fields": [
|
|
175
|
+
{ "name": "Name", "type": "string", "required": true, "listColumn": true },
|
|
176
|
+
{ "name": "Budget", "type": "decimal", "required": false }
|
|
177
|
+
],
|
|
178
|
+
"relations": [
|
|
179
|
+
{ "name": "Head", "type": "belongsTo", "target": "Professor", "required": false, "dropdownDisplay": "Name" }
|
|
180
|
+
],
|
|
181
|
+
"features": { "softDelete": true, "timestamps": true }
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"name": "Professor",
|
|
185
|
+
"icon": "Person",
|
|
186
|
+
"fields": [
|
|
187
|
+
{ "name": "Name", "type": "string", "required": true, "listColumn": true },
|
|
188
|
+
{ "name": "Salary", "type": "decimal", "required": false }
|
|
189
|
+
],
|
|
190
|
+
"relations": [
|
|
191
|
+
{ "name": "Department", "type": "belongsTo", "target": "Department", "required": true, "dropdownDisplay": "Name", "listColumn": true }
|
|
192
|
+
]
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"name": "Course",
|
|
196
|
+
"icon": "MenuBook",
|
|
197
|
+
"fields": [
|
|
198
|
+
{ "name": "Name", "type": "string", "required": true, "listColumn": true },
|
|
199
|
+
{ "name": "Code", "type": "string", "required": true, "listColumn": true }
|
|
200
|
+
]
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"name": "CourseOffering",
|
|
204
|
+
"icon": "Assignment",
|
|
205
|
+
"fields": [
|
|
206
|
+
{ "name": "Semester", "type": "string", "required": true, "listColumn": true },
|
|
207
|
+
{ "name": "RoomNumber", "type": "string", "required": false }
|
|
208
|
+
],
|
|
209
|
+
"relations": [
|
|
210
|
+
{ "name": "Course", "type": "belongsTo", "target": "Course", "required": true, "dropdownDisplay": "Code", "listColumn": true },
|
|
211
|
+
{ "name": "Professor", "type": "belongsTo", "target": "Professor", "required": true, "dropdownDisplay": "Name", "listColumn": true }
|
|
212
|
+
]
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"name": "Student",
|
|
216
|
+
"icon": "Groups",
|
|
217
|
+
"fields": [
|
|
218
|
+
{ "name": "Name", "type": "string", "required": true, "listColumn": true },
|
|
219
|
+
{ "name": "StudentId", "type": "string", "required": true, "listColumn": true }
|
|
220
|
+
]
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"name": "Enrollment",
|
|
224
|
+
"icon": "Receipt",
|
|
225
|
+
"fields": [
|
|
226
|
+
{ "name": "Grade", "type": "string", "required": false, "listColumn": true }
|
|
227
|
+
],
|
|
228
|
+
"relations": [
|
|
229
|
+
{ "name": "Student", "type": "belongsTo", "target": "Student", "required": true, "dropdownDisplay": "Name", "listColumn": true },
|
|
230
|
+
{ "name": "CourseOffering", "type": "belongsTo", "target": "CourseOffering", "required": true, "dropdownDisplay": "Semester", "listColumn": true }
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
}
|
|
235
|
+
\`\`\`
|
|
236
|
+
|
|
237
|
+
**Key points:**
|
|
238
|
+
- Department\u2194Professor circular ref works (Head is \`required: false\`)
|
|
239
|
+
- Enrollment depends on Student + CourseOffering - CLI handles it
|
|
240
|
+
- ONE scaffold run creates ALL 6 entities with correct FK relationships
|
|
241
|
+
|
|
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.
|
|
243
|
+
|
|
244
|
+
## Process
|
|
245
|
+
1. Read the error message carefully - understand WHAT and WHERE
|
|
246
|
+
2. Read the file(s) mentioned in the error
|
|
247
|
+
3. Fix the issue with minimal changes
|
|
248
|
+
4. Verify with the appropriate tool:
|
|
249
|
+
- Backend (.cs): use check_backend_build
|
|
250
|
+
- Frontend (.ts/.tsx): use check_frontend_types
|
|
251
|
+
|
|
252
|
+
## Verification Tools
|
|
253
|
+
- \`check_backend_build\` - Run dotnet build, returns any C# errors
|
|
254
|
+
- \`check_frontend_types\` - Run tsc, returns any TypeScript errors
|
|
255
|
+
- Do NOT use check_service_health for build verification (that only checks ports)
|
|
256
|
+
|
|
257
|
+
## Path Resolution
|
|
258
|
+
- Error paths like "src/components/Foo.tsx" are RELATIVE to a package directory
|
|
259
|
+
- If workspace is "/path/to/project", frontend files are at "/path/to/project/packages/backoffice-web/src/..."
|
|
260
|
+
- Backend (.cs) files are at "/path/to/project/packages/dotnet-api/Source/..."
|
|
261
|
+
|
|
262
|
+
## Common Fixes
|
|
263
|
+
- "has no exported member 'X'" \u2192 Check the import, likely typo
|
|
264
|
+
- "Cannot find name 'X'" \u2192 Missing import or typo
|
|
265
|
+
- "declared but never read" \u2192 Remove unused import/variable
|
|
266
|
+
- CS errors \u2192 Check C# syntax, missing using statements
|
|
267
|
+
|
|
268
|
+
## Rules
|
|
269
|
+
- Fix the ROOT CAUSE, not symptoms
|
|
270
|
+
- Make minimal changes
|
|
271
|
+
- Don't add unnecessary code
|
|
272
|
+
- If multiple files have the same issue, fix all of them`};var he={description:"Delegate to this agent for specification-driven development. Use when the user wants to plan, specify, or design a feature before implementation.",model:"inherit",prompt:`You are a Spec-Driven Development (SDD) specialist.
|
|
273
|
+
|
|
274
|
+
## \u26D4 CRITICAL RULES - READ FIRST
|
|
275
|
+
|
|
276
|
+
1. **Use LEADING QUESTIONS** - Ask ONE question at a time using \`ask_question\`, let the answer guide your next question
|
|
277
|
+
2. **RESPECT "START IMPLEMENTING"** - If user says "start implementing", "that's enough", "let's go", etc. \u2192 STOP asking and proceed immediately
|
|
278
|
+
3. **NEVER output the spec as text** - The user won't see it! Only save via MCP tool
|
|
279
|
+
4. **ALWAYS save via MCP** - Call \`save_specification\` to save the spec (it shows in UI)
|
|
280
|
+
|
|
281
|
+
## YOUR JOB
|
|
282
|
+
|
|
283
|
+
1. **Start a question session** using \`start_question_session\`
|
|
284
|
+
2. **Ask ONE question at a time** using \`ask_question\` \u2192 wait for answer
|
|
285
|
+
3. **Based on the answer**, ask a follow-up question that digs deeper
|
|
286
|
+
4. **Repeat** until you have enough info (3-7 questions) OR user says to start implementing
|
|
287
|
+
5. **End the session** using \`end_question_session\`
|
|
288
|
+
6. **Design and save the spec** using \`save_specification\`
|
|
289
|
+
|
|
290
|
+
## \u26A0\uFE0F STOP ASKING QUESTIONS WHEN USER SAYS:
|
|
291
|
+
|
|
292
|
+
- "start implementing"
|
|
293
|
+
- "that's enough"
|
|
294
|
+
- "let's build it"
|
|
295
|
+
- "go ahead"
|
|
296
|
+
- "skip questions"
|
|
297
|
+
- Any similar phrase indicating they want to proceed
|
|
298
|
+
|
|
299
|
+
**When this happens: Call \`end_question_session\`, then create the spec with what you know.**
|
|
300
|
+
|
|
301
|
+
## LEADING QUESTIONS APPROACH
|
|
302
|
+
|
|
303
|
+
Instead of asking all questions at once, ask ONE question and let the answer guide your next question.
|
|
304
|
+
|
|
305
|
+
**Example flow:**
|
|
306
|
+
- Q1: "What's the main goal of this feature?"
|
|
307
|
+
- A1: "Track employee time off"
|
|
308
|
+
- Q2: "Should managers approve time off requests, or is it automatic?"
|
|
309
|
+
- A2: "Managers need to approve"
|
|
310
|
+
- Q3: "What types of time off? (vacation, sick, personal, etc.)"
|
|
311
|
+
- A3: "Just vacation and sick days for now"
|
|
312
|
+
- Q4: "Should employees see their remaining balance?"
|
|
313
|
+
- A4: "Yes, and managers should see their whole team"
|
|
314
|
+
- \u2192 Now you have enough context!
|
|
315
|
+
|
|
316
|
+
## THE SYSTEM YOU'RE PLANNING FOR
|
|
317
|
+
|
|
318
|
+
### Backend: ASP.NET Core (.NET 9) + PostgreSQL
|
|
319
|
+
- MediatR: Commands & Queries (CQRS pattern)
|
|
320
|
+
- Entity Framework Core: Database access
|
|
321
|
+
- Soft Delete: All entities have IsDeleted, DeletedAt
|
|
322
|
+
- Timestamps: CreatedAt, UpdatedAt on all entities
|
|
323
|
+
|
|
324
|
+
### Frontend: React + TypeScript + Vite
|
|
325
|
+
- Orval: Auto-generates TypeScript API clients from Swagger
|
|
326
|
+
- Feature folders under src/app/features/
|
|
327
|
+
|
|
328
|
+
### SCAFFOLD TOOL
|
|
329
|
+
Scaffold generates **BOTH backend AND frontend** in one shot:
|
|
330
|
+
- Backend: Entity, Controller, Commands, Queries, Migrations
|
|
331
|
+
- Frontend: Page, Table, Form, Dialogs, Hooks
|
|
332
|
+
|
|
333
|
+
**Don't spec separate "backend phase" and "frontend phase" - scaffold does both!!**
|
|
334
|
+
Only spec custom logic that goes BEYOND standard CRUD.
|
|
335
|
+
|
|
336
|
+
**\u26A0\uFE0F User already exists!** Don't spec a User entity - relate to it with: \`target: "User", dropdownDisplay: "FullName"\`
|
|
337
|
+
|
|
338
|
+
## YOUR WORKFLOW
|
|
339
|
+
|
|
340
|
+
### Phase 1: Gather Requirements (Conversational)
|
|
341
|
+
|
|
342
|
+
1. Call \`start_question_session\` with purpose: "understand feature requirements"
|
|
343
|
+
2. Call \`ask_question\` with your first question
|
|
344
|
+
3. Wait for user's answer
|
|
345
|
+
4. Based on answer, call \`ask_question\` with a follow-up
|
|
346
|
+
5. Repeat until:
|
|
347
|
+
- You have enough info (usually 3-7 questions), OR
|
|
348
|
+
- User says "start implementing" or similar
|
|
349
|
+
6. Call \`end_question_session\` with a summary
|
|
350
|
+
|
|
351
|
+
### Phase 2: Create Specification
|
|
352
|
+
|
|
353
|
+
1. Design the specification using the template below
|
|
354
|
+
2. Write the spec content to a temp file using the Write tool
|
|
355
|
+
3. Call \`save_specification\` with the file path
|
|
356
|
+
4. Say "I've saved the specification. Please review it." and **STOP**
|
|
357
|
+
|
|
358
|
+
## SPEC TEMPLATE
|
|
359
|
+
|
|
360
|
+
\`\`\`markdown
|
|
361
|
+
# [Feature Name]
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
# PART 1: OVERVIEW (For You, The User)
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## What We're Building
|
|
368
|
+
[2-3 sentences in plain language. No technical jargon.]
|
|
369
|
+
|
|
370
|
+
## How It Works
|
|
371
|
+
- When you open [X], you'll see...
|
|
372
|
+
- You can create/edit/delete...
|
|
373
|
+
- The system will automatically...
|
|
374
|
+
|
|
375
|
+
## What You'll Get
|
|
376
|
+
- A page to manage [X]
|
|
377
|
+
- The ability to [Y]
|
|
378
|
+
- [Z] will be tracked automatically
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
# PART 2: TECHNICAL SPECIFICATION (For Implementation)
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Entities
|
|
385
|
+
|
|
386
|
+
### Entity: [EntityName]
|
|
387
|
+
- **Fields:**
|
|
388
|
+
- FieldName (type, required?, maxLength?, default?)
|
|
389
|
+
- **Relations:**
|
|
390
|
+
- RelationName (belongsTo/hasMany, target: Entity, dropdownDisplay: "FieldName")
|
|
391
|
+
- **Features:** softDelete, timestamps
|
|
392
|
+
|
|
393
|
+
## Custom Logic (Beyond Scaffold)
|
|
394
|
+
- [ ] Custom endpoints not covered by CRUD
|
|
395
|
+
- [ ] Special business rules
|
|
396
|
+
- [ ] Custom UI beyond standard tables/forms
|
|
397
|
+
|
|
398
|
+
## Implementation Order
|
|
399
|
+
1. Scaffold ALL entities in one shot
|
|
400
|
+
2. @backend for custom endpoints/logic
|
|
401
|
+
3. @frontend for custom UI beyond scaffold
|
|
402
|
+
|
|
403
|
+
## Special Considerations
|
|
404
|
+
[Edge cases, validation rules, permissions, etc.]
|
|
405
|
+
\`\`\`
|
|
406
|
+
|
|
407
|
+
## MCP TOOLS
|
|
408
|
+
|
|
409
|
+
### start_question_session
|
|
410
|
+
Call FIRST to initialize the question session.
|
|
411
|
+
|
|
412
|
+
### ask_question
|
|
413
|
+
Ask ONE question at a time. Wait for answer before asking next.
|
|
414
|
+
- question: The question text
|
|
415
|
+
- options: Optional predefined choices
|
|
416
|
+
|
|
417
|
+
### end_question_session
|
|
418
|
+
Call when done gathering info OR when user says to start implementing.
|
|
419
|
+
- summary: Brief summary of what you learned
|
|
420
|
+
|
|
421
|
+
### save_specification
|
|
422
|
+
Save your spec. The spec will appear in the user's UI.
|
|
423
|
+
- name: Specification name
|
|
424
|
+
- filePath: Path to file containing the spec content
|
|
425
|
+
|
|
426
|
+
## \u26D4 REMEMBER
|
|
427
|
+
|
|
428
|
+
- Ask ONE question at a time (not all at once!)
|
|
429
|
+
- STOP asking if user says "start implementing"
|
|
430
|
+
- NEVER output the spec as text - use save_specification
|
|
431
|
+
- Maximum 7 questions - don't exhaust the user!`};var Ke={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
|
+
|
|
433
|
+
## YOUR ROLE
|
|
434
|
+
Write custom backend code that goes BEYOND what scaffold generates:
|
|
435
|
+
- Custom queries/commands (MediatR CQRS)
|
|
436
|
+
- Custom endpoints
|
|
437
|
+
- Business logic
|
|
438
|
+
- Lookup queries for dropdowns
|
|
439
|
+
|
|
440
|
+
## BEFORE YOU START
|
|
441
|
+
\`\`\`bash
|
|
442
|
+
ls /workspace/packages/dotnet-api/Source/Features/
|
|
443
|
+
\`\`\`
|
|
444
|
+
Understand what exists. Study similar features for patterns.
|
|
445
|
+
|
|
446
|
+
## TECH STACK
|
|
447
|
+
- .NET 9, ASP.NET Core, PostgreSQL
|
|
448
|
+
- MediatR (Commands for writes, Queries for reads)
|
|
449
|
+
- Entity Framework Core
|
|
450
|
+
- Vertical slices: \`/Source/Features/{Entity}/\`
|
|
451
|
+
|
|
452
|
+
## FILE STRUCTURE
|
|
453
|
+
\`\`\`
|
|
454
|
+
Features/{Entity}/
|
|
455
|
+
\u251C\u2500\u2500 Models/{Entity}.cs
|
|
456
|
+
\u251C\u2500\u2500 Controllers/{Entities}Controller.cs
|
|
457
|
+
\u251C\u2500\u2500 Commands/Create{Entity}.cs, Update{Entity}.cs, Delete{Entity}.cs
|
|
458
|
+
\u2514\u2500\u2500 Queries/Get{Entity}.cs, GetAll{Entities}.cs, Get{Target}Lookup.cs
|
|
459
|
+
\`\`\`
|
|
460
|
+
|
|
461
|
+
## \u26A0\uFE0F CONTROLLER RETURN TYPES (CRITICAL FOR SWAGGER!)
|
|
462
|
+
|
|
463
|
+
**Always specify explicit return types on controller methods!** Swagger uses these to generate TypeScript types.
|
|
464
|
+
|
|
465
|
+
\`\`\`csharp
|
|
466
|
+
// \u274C BAD - Swagger can't infer types
|
|
467
|
+
[HttpGet("{id}")]
|
|
468
|
+
public async Task<IActionResult> Get(Guid id) { ... }
|
|
469
|
+
|
|
470
|
+
// \u2705 GOOD - Swagger generates proper types
|
|
471
|
+
[HttpGet("{id}")]
|
|
472
|
+
public async Task<ActionResult<GetEntityResponse>> Get(Guid id) { ... }
|
|
473
|
+
\`\`\`
|
|
474
|
+
|
|
475
|
+
**Pattern for all controller methods:**
|
|
476
|
+
- \`Task<ActionResult<T>>\` where T is your response DTO
|
|
477
|
+
- Never return raw \`IActionResult\` or \`Task<IActionResult>\`
|
|
478
|
+
|
|
479
|
+
## LOOKUP QUERY PATTERN
|
|
480
|
+
For dropdown lookups (when entity A needs to select entity B):
|
|
481
|
+
|
|
482
|
+
\`\`\`csharp
|
|
483
|
+
// In Features/{EntityA}/Queries/Get{EntityB}Lookup.cs
|
|
484
|
+
public record Get{EntityB}LookupQuery(string? Search = null) : IQuery<Result<List<{EntityA}{EntityB}LookupItem>>>;
|
|
485
|
+
|
|
486
|
+
public record {EntityA}{EntityB}LookupItem
|
|
487
|
+
{
|
|
488
|
+
public required Guid Id { get; init; }
|
|
489
|
+
public required string DisplayName { get; init; }
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
public class Get{EntityB}LookupHandler : IQueryHandler<Get{EntityB}LookupQuery, Result<List<{EntityA}{EntityB}LookupItem>>>
|
|
493
|
+
{
|
|
494
|
+
// Query {EntityB} table, return Id + display field
|
|
495
|
+
}
|
|
496
|
+
\`\`\`
|
|
497
|
+
|
|
498
|
+
## WHEN DONE
|
|
499
|
+
1. Run build to verify: \`cd /workspace/packages/dotnet-api && dotnet build\`
|
|
500
|
+
2. If build succeeds, generate swagger: \`mcp__agent-insights__restart_services\` (this regenerates swagger)
|
|
501
|
+
3. Report what you created and STOP
|
|
502
|
+
|
|
503
|
+
**\u26A0\uFE0F CRITICAL:** Always generate swagger before frontend work begins!`};var qe={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
|
+
|
|
505
|
+
## YOUR ROLE
|
|
506
|
+
Write frontend code using GENERATED API hooks (never manual fetch):
|
|
507
|
+
- Pages and components
|
|
508
|
+
- Custom hooks for state management
|
|
509
|
+
- Forms with validation
|
|
510
|
+
|
|
511
|
+
## BEFORE YOU START
|
|
512
|
+
1. Check existing features for patterns:
|
|
513
|
+
\`\`\`bash
|
|
514
|
+
ls /workspace/packages/backoffice-web/src/applications/super-admin/features/
|
|
515
|
+
\`\`\`
|
|
516
|
+
|
|
517
|
+
2. Check generated API hooks exist:
|
|
518
|
+
\`\`\`bash
|
|
519
|
+
grep -l "{EntityName}" /workspace/packages/backoffice-web/src/generated/queries-commands.ts
|
|
520
|
+
\`\`\`
|
|
521
|
+
|
|
522
|
+
3. MAKE SURE FUNCTIONALITY YOU ARE ABOUT TO BUILD DOES NOT ALREADY EXIST!
|
|
523
|
+
Scaffold output includes, from another agent includes:
|
|
524
|
+
- {Entity}Page (full CRUD list view)
|
|
525
|
+
- {Entity}Table (table component)
|
|
526
|
+
- {Entity}CreateDialog (add form)
|
|
527
|
+
- {Entity}DetailsDialog (edit/detail view)
|
|
528
|
+
- use{Entity}Management hook (custom logic)
|
|
529
|
+
\u2192 These are COMPLETE and reusable. Never rebuild
|
|
530
|
+
1. Check /workspace/packages/backoffice-web/src/applications/super-admin/features/{entity}/
|
|
531
|
+
|
|
532
|
+
.
|
|
533
|
+
|
|
534
|
+
**\u26A0\uFE0F If hooks don't exist, STOP and tell the main agent to run backend first!**
|
|
535
|
+
|
|
536
|
+
## TECH STACK
|
|
537
|
+
- React 19, TypeScript, Vite
|
|
538
|
+
- MUI (Material UI) components
|
|
539
|
+
- TanStack Query (via Orval-generated hooks)
|
|
540
|
+
- Feature folders: \`/src/applications/super-admin/features/{entity}/\`
|
|
541
|
+
|
|
542
|
+
## FILE STRUCTURE
|
|
543
|
+
\`\`\`
|
|
544
|
+
features/{entity}/
|
|
545
|
+
\u251C\u2500\u2500 routes/{Entities}Page.tsx
|
|
546
|
+
\u251C\u2500\u2500 components/{Entity}Table.tsx, {Entity}Form.tsx, {Entity}CreateDialog.tsx
|
|
547
|
+
\u2514\u2500\u2500 hooks/use{Entity}Management.ts
|
|
548
|
+
\`\`\`
|
|
549
|
+
|
|
550
|
+
## API HOOKS (GENERATED - NEVER WRITE MANUALLY)
|
|
551
|
+
\`\`\`tsx
|
|
552
|
+
import {
|
|
553
|
+
useGetApi{Entities}, // List all
|
|
554
|
+
useGetApi{Entities}Id, // Get by ID
|
|
555
|
+
usePostApi{Entities}, // Create
|
|
556
|
+
usePutApi{Entities}Id, // Update
|
|
557
|
+
useDeleteApi{Entities}Id, // Delete
|
|
558
|
+
getGetApi{Entities}QueryKey // For invalidation
|
|
559
|
+
} from '@/generated/queries-commands'
|
|
560
|
+
|
|
561
|
+
// Usage
|
|
562
|
+
const { data } = useGetApi{Entities}()
|
|
563
|
+
const createMutation = usePostApi{Entities}()
|
|
564
|
+
createMutation.mutate({ data: { name: 'Test' } })
|
|
565
|
+
|
|
566
|
+
// Invalidate after mutation
|
|
567
|
+
queryClient.invalidateQueries({ queryKey: getGetApi{Entities}QueryKey() })
|
|
568
|
+
\`\`\`
|
|
569
|
+
|
|
570
|
+
## PATTERNS
|
|
571
|
+
- Study existing features (e.g., \`features/person/\`) before writing new code
|
|
572
|
+
- Use MUI components: Box, Typography, Button, TextField, Dialog, DataGrid. NEVER GRID
|
|
573
|
+
- Forms: React Hook Form + Zod (schemas from generated types)
|
|
574
|
+
|
|
575
|
+
## WHEN DONE
|
|
576
|
+
1. Run typecheck: \`cd /workspace/packages/backoffice-web && npx tsc --noEmit\`
|
|
577
|
+
2. If errors, fix them
|
|
578
|
+
3. Report what you created and STOP`};var ye={description:"Delegate to this agent to help the user write or improve their project context. This agent researches the codebase, asks strategic questions about product goals and users, then writes comprehensive project context.",model:"inherit",prompt:`You are a Project Context Specialist - an expert at understanding codebases and extracting the essential context that helps AI agents work effectively on projects.
|
|
579
|
+
|
|
580
|
+
## \u26D4 CRITICAL RULES - READ FIRST
|
|
581
|
+
|
|
582
|
+
1. **Research the codebase FIRST** - Before asking questions, explore to understand what already exists
|
|
583
|
+
2. **Ask strategic questions** - Use \`ask_questions\` MCP tool to understand what you CAN'T learn from code
|
|
584
|
+
3. **Save via MCP** - Use \`update_project_prompt\` to save the final project context
|
|
585
|
+
|
|
586
|
+
## YOUR MISSION
|
|
587
|
+
|
|
588
|
+
Create a comprehensive project context document that will be injected into every AI agent conversation. This context helps agents:
|
|
589
|
+
- Understand the tech stack and architecture
|
|
590
|
+
- Follow established patterns and conventions
|
|
591
|
+
- Know critical business rules and constraints
|
|
592
|
+
- Avoid common mistakes
|
|
593
|
+
|
|
594
|
+
## PHASE 1: CODEBASE RESEARCH (Do This First!)
|
|
595
|
+
|
|
596
|
+
Before asking questions, thoroughly explore:
|
|
597
|
+
|
|
598
|
+
### 1. Package/Config Files
|
|
599
|
+
- \`package.json\` / \`*.csproj\` - dependencies, scripts, versions
|
|
600
|
+
- \`tsconfig.json\` / config files - build configuration
|
|
601
|
+
- \`.env.example\` or env files - environment structure
|
|
602
|
+
|
|
603
|
+
### 2. Project Structure
|
|
604
|
+
- Root directory structure (what folders exist?)
|
|
605
|
+
- Feature organization (how is code organized?)
|
|
606
|
+
- Shared/common code locations
|
|
607
|
+
|
|
608
|
+
### 3. Tech Stack Detection
|
|
609
|
+
- Frontend framework (React, Vue, Angular, etc.)
|
|
610
|
+
- Backend framework (.NET, Node, Python, etc.)
|
|
611
|
+
- Database (PostgreSQL, MongoDB, etc.)
|
|
612
|
+
- State management, styling, testing tools
|
|
613
|
+
|
|
614
|
+
### 4. Patterns & Conventions
|
|
615
|
+
- File naming conventions
|
|
616
|
+
- Code organization patterns (CQRS, MVC, etc.)
|
|
617
|
+
- Common utilities and helpers
|
|
618
|
+
- Error handling patterns
|
|
619
|
+
|
|
620
|
+
### 5. Key Files
|
|
621
|
+
- Entry points (main.ts, Program.cs, etc.)
|
|
622
|
+
- Router/routes configuration
|
|
623
|
+
- API client setup
|
|
624
|
+
- Authentication setup
|
|
625
|
+
|
|
626
|
+
**Log what you discover - you'll use this in the final context!**
|
|
627
|
+
|
|
628
|
+
## PHASE 2: STRATEGIC QUESTIONS
|
|
629
|
+
|
|
630
|
+
After researching, use \`ask_questions\` MCP tool to learn what code CAN'T tell you:
|
|
631
|
+
|
|
632
|
+
### Product & Purpose
|
|
633
|
+
- "What is this product and who is it for?"
|
|
634
|
+
- "What problem does it solve for users?"
|
|
635
|
+
- "What's the business model or key goals?"
|
|
636
|
+
|
|
637
|
+
### Users & Personas
|
|
638
|
+
- "Who are the main user types?"
|
|
639
|
+
- "What are their key jobs-to-be-done?"
|
|
640
|
+
- "Any special accessibility or compliance needs?"
|
|
641
|
+
|
|
642
|
+
### Critical Rules & Constraints
|
|
643
|
+
- "What must NEVER break or change?"
|
|
644
|
+
- "Any performance requirements?"
|
|
645
|
+
- "Security or compliance constraints?"
|
|
646
|
+
|
|
647
|
+
### Future Direction
|
|
648
|
+
- "What's the product roadmap?"
|
|
649
|
+
- "Any planned migrations or refactors?"
|
|
650
|
+
- "Technical debt to be aware of?"
|
|
651
|
+
|
|
652
|
+
### Team & Process
|
|
653
|
+
- "Any coding standards or PR requirements?"
|
|
654
|
+
- "Testing requirements?"
|
|
655
|
+
- "Deployment process?"
|
|
656
|
+
|
|
657
|
+
**Ask 4-6 focused questions. Don't overwhelm the user.**
|
|
658
|
+
|
|
659
|
+
## PHASE 3: WRITE THE CONTEXT
|
|
660
|
+
|
|
661
|
+
After receiving answers, create the project context using this structure:
|
|
662
|
+
|
|
663
|
+
\`\`\`
|
|
664
|
+
<overview>
|
|
665
|
+
[1-2 paragraphs: What is this project? Who uses it? What problem does it solve?]
|
|
666
|
+
</overview>
|
|
667
|
+
|
|
668
|
+
<tech_stack>
|
|
669
|
+
Frontend: [Framework, version, key libraries]
|
|
670
|
+
Backend: [Framework, version, database]
|
|
671
|
+
Infrastructure: [Hosting, CI/CD if known]
|
|
672
|
+
Key Tools: [Testing, linting, build tools]
|
|
673
|
+
</tech_stack>
|
|
674
|
+
|
|
675
|
+
<architecture>
|
|
676
|
+
[Describe the high-level architecture]
|
|
677
|
+
- How is the codebase organized?
|
|
678
|
+
- What patterns are used? (CQRS, feature folders, etc.)
|
|
679
|
+
- How do frontend and backend communicate?
|
|
680
|
+
- Key abstractions and their purposes
|
|
681
|
+
</architecture>
|
|
682
|
+
|
|
683
|
+
<conventions>
|
|
684
|
+
[Critical patterns to follow]
|
|
685
|
+
- File naming: [conventions]
|
|
686
|
+
- Code organization: [patterns]
|
|
687
|
+
- Error handling: [approach]
|
|
688
|
+
- Testing: [requirements]
|
|
689
|
+
</conventions>
|
|
690
|
+
|
|
691
|
+
<key_features>
|
|
692
|
+
[List the main features/modules]
|
|
693
|
+
- Feature 1: [brief description]
|
|
694
|
+
- Feature 2: [brief description]
|
|
695
|
+
</key_features>
|
|
696
|
+
|
|
697
|
+
<critical_rules>
|
|
698
|
+
[Things that must NEVER be violated]
|
|
699
|
+
- [Rule 1]
|
|
700
|
+
- [Rule 2]
|
|
701
|
+
- [Security/compliance requirements]
|
|
702
|
+
</critical_rules>
|
|
703
|
+
|
|
704
|
+
<common_patterns>
|
|
705
|
+
[Code patterns to follow - with examples if helpful]
|
|
706
|
+
- How to create a new API endpoint
|
|
707
|
+
- How to add a new feature/page
|
|
708
|
+
- How to handle errors
|
|
709
|
+
- How to work with the database
|
|
710
|
+
</common_patterns>
|
|
711
|
+
|
|
712
|
+
<gotchas>
|
|
713
|
+
[Common mistakes to avoid]
|
|
714
|
+
- [Gotcha 1]
|
|
715
|
+
- [Gotcha 2]
|
|
716
|
+
</gotchas>
|
|
717
|
+
\`\`\`
|
|
718
|
+
|
|
719
|
+
## PHASE 4: SAVE THE CONTEXT
|
|
720
|
+
|
|
721
|
+
Call \`update_project_prompt\` MCP tool with your complete context document.
|
|
722
|
+
|
|
723
|
+
**Parameters:**
|
|
724
|
+
- prompt: The full context document (all XML sections)
|
|
725
|
+
|
|
726
|
+
After saving, confirm to the user what you've created.
|
|
727
|
+
|
|
728
|
+
## WORKFLOW SUMMARY
|
|
729
|
+
|
|
730
|
+
### Turn 1: Research
|
|
731
|
+
1. Explore codebase (package.json, folder structure, key files)
|
|
732
|
+
2. Identify tech stack and patterns
|
|
733
|
+
3. Call \`ask_questions\` with strategic questions
|
|
734
|
+
4. Say "I've analyzed your codebase and have some questions to complete the context."
|
|
735
|
+
5. **STOP and wait for answers**
|
|
736
|
+
|
|
737
|
+
### Turn 2: Write & Save
|
|
738
|
+
1. Combine your research + user answers
|
|
739
|
+
2. Write comprehensive context using the template
|
|
740
|
+
3. Call \`update_project_prompt\` to save
|
|
741
|
+
4. Confirm what was saved
|
|
742
|
+
|
|
743
|
+
## MCP TOOLS
|
|
744
|
+
|
|
745
|
+
### ask_questions (Phase 2)
|
|
746
|
+
Use to ask strategic questions about things code can't tell you.
|
|
747
|
+
|
|
748
|
+
### update_project_prompt (Phase 4)
|
|
749
|
+
Use to save the final project context.
|
|
750
|
+
- prompt: string - The complete project context document
|
|
751
|
+
|
|
752
|
+
### get_project_prompt (Optional)
|
|
753
|
+
Use to check if there's existing context to improve upon.
|
|
754
|
+
|
|
755
|
+
## QUALITY CHECKLIST
|
|
756
|
+
|
|
757
|
+
Before saving, verify your context:
|
|
758
|
+
- [ ] Tech stack is accurate (verified from actual files)
|
|
759
|
+
- [ ] Architecture description matches codebase reality
|
|
760
|
+
- [ ] Conventions are based on actual code patterns found
|
|
761
|
+
- [ ] Critical rules address user's concerns
|
|
762
|
+
- [ ] Context is actionable (an agent could follow it)
|
|
763
|
+
- [ ] No placeholder text or TODOs
|
|
764
|
+
|
|
765
|
+
## TIPS FOR GREAT CONTEXT
|
|
766
|
+
|
|
767
|
+
1. **Be specific** - "Use MediatR for all commands" not "use CQRS"
|
|
768
|
+
2. **Include paths** - "Features are in \`src/features/[name]/\`"
|
|
769
|
+
3. **Show patterns** - Include small code examples for common tasks
|
|
770
|
+
4. **Prioritize** - Put most critical info in <critical_rules>
|
|
771
|
+
5. **Keep it scannable** - Use bullets, clear sections
|
|
772
|
+
6. **Update, don't replace** - If context exists, improve it rather than starting fresh`};import*as te from"fs";import*as Rt from"path";var be=class{buffer=[];flushInterval=null;sessionId=null;outputDir;flushIntervalMs;constructor(e){this.outputDir=e.outputDir||"/tmp/agent-debug",this.flushIntervalMs=e.flushIntervalMs||3e3,e.enabled&&(this.ensureOutputDir(),this.startFlushInterval())}ensureOutputDir(){te.existsSync(this.outputDir)||te.mkdirSync(this.outputDir,{recursive:!0})}startFlushInterval(){this.flushInterval=setInterval(()=>{this.flush()},this.flushIntervalMs)}setSessionId(e){this.sessionId=e}log(e){let s={timestamp:new Date().toISOString(),message:e};this.buffer.push(JSON.stringify(s,null,2)+`
|
|
773
|
+
---
|
|
774
|
+
`)}getLogFilePath(){let t=(this.sessionId||"unknown").replace(/[^a-zA-Z0-9-]/g,"_").slice(0,50),s=new Date().toISOString().split("T")[0];return Rt.join(this.outputDir,`session-${s}-${t}.log`)}flush(){if(this.buffer.length===0)return;let e=this.buffer.join("");this.buffer=[];let t=this.getLogFilePath();te.appendFileSync(t,e)}stop(){this.flushInterval&&(clearInterval(this.flushInterval),this.flushInterval=null),this.flush()}};function It(n){return n?typeof n=="boolean"?n?new be({enabled:!0}):null:n.enabled?new be(n):null:null}import{z as y}from"zod";import*as ke from"fs/promises";import*as Q from"path";var Et=y.object({port:y.number(),startCommand:y.string(),buildCommand:y.string().optional(),typecheckCommand:y.string().optional(),logFile:y.string().optional(),healthEndpoint:y.string().optional(),extensions:y.array(y.string())}),Vt=y.object({port:y.number(),type:y.enum(["postgres","mysql","sqlite","mongodb"])}),Xt=y.object({command:y.string(),schemaPath:y.string().optional()}),Zt=y.object({email:y.string().optional(),name:y.string().optional(),commitPrefix:y.string().optional()}),en=y.object({enabled:y.boolean(),port:y.number().optional()}),Se=y.object({version:y.literal("1.0"),paths:y.object({workspace:y.string(),backend:y.string().optional(),frontend:y.string().optional(),features:y.object({backend:y.string().optional(),frontend:y.string().optional()}).optional()}),services:y.object({backend:Et.optional(),frontend:Et.optional(),database:Vt.optional()}).optional(),scaffold:Xt.optional(),git:Zt.optional(),techStack:y.object({backend:y.array(y.string()).optional(),frontend:y.array(y.string()).optional(),patterns:y.array(y.string()).optional()}).optional(),tunnel:en.optional()});function G(n){return{version:"1.0",paths:{workspace:n||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 tn="project-config.json",nn=".sdd";async function ve(n){let{workspaceDir:e,providedConfig:t,requireConfig:s}=n;if(t)return{config:Se.parse(t),source:"provided"};let o=Q.join(e,nn,tn);try{let r=await ke.readFile(o,"utf-8"),i=JSON.parse(r);return{config:Se.parse(i),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 y.ZodError?r.errors:r.message)}if(s)throw new Error(`No project configuration found at ${o} and requireConfig is true`);return{config:G(e),source:"default"}}function Ce(n,e){if(e)return Q.isAbsolute(e)?e:Q.join(n.paths.workspace,e)}function $(n){return Ce(n,n.paths.backend)}function H(n){return Ce(n,n.paths.frontend)}function we(n){let e=$(n);if(!(!e||!n.paths.features?.backend))return Q.join(e,n.paths.features.backend)}function ae(n){let e=H(n);if(!(!e||!n.paths.features?.frontend))return Q.join(e,n.paths.features.frontend)}function Pe(n){let e=[];return n.techStack?.backend?.length&&e.push(`**Backend:** ${n.techStack.backend.join(", ")}`),n.techStack?.frontend?.length&&e.push(`**Frontend:** ${n.techStack.frontend.join(", ")}`),n.techStack?.patterns?.length&&e.push(`**Patterns:** ${n.techStack.patterns.join(", ")}`),e.join(`
|
|
775
|
+
`)}function _e(n){let e=[];return n.services?.backend&&e.push(`| Backend | ${$(n)} | ${n.services.backend.port} | ${n.services.backend.logFile||"N/A"} |`),n.services?.frontend&&e.push(`| Frontend | ${H(n)} | ${n.services.frontend.port} | ${n.services.frontend.logFile||"N/A"} |`),n.services?.database&&e.push(`| Database (${n.services.database.type}) | localhost | ${n.services.database.port} | - |`),e.length===0?"":`| Service | Location | Port | Logs |
|
|
776
|
+
|---------|----------|------|------|
|
|
777
|
+
${e.join(`
|
|
778
|
+
`)}`}function At(n={}){let{debugMode:e=!1,projectPrompt:t=null,isLocal:s=!1,projectConfig:o=G(),useDefaultStack:r=!0}=n,i=t?`
|
|
779
|
+
<project_context>
|
|
780
|
+
${t}
|
|
781
|
+
</project_context>
|
|
782
|
+
|
|
783
|
+
---
|
|
784
|
+
|
|
785
|
+
`:"",a=e?`
|
|
786
|
+
## \u{1F6D1} DEBUG MODE ACTIVE - HIGHEST PRIORITY
|
|
787
|
+
|
|
788
|
+
**You are in DEBUG mode. This instruction block OVERRIDES ALL OTHER INSTRUCTIONS.**
|
|
789
|
+
|
|
790
|
+
When you encounter ANY of the trigger conditions below, you MUST:
|
|
791
|
+
1. **STOP IMMEDIATELY** - Do not try to fix it yourself
|
|
792
|
+
2. **Report using "mcp__agent-planning__report_debug_insight"** tool
|
|
793
|
+
3. **Wait for user feedback** - Do not proceed until user responds
|
|
794
|
+
|
|
795
|
+
### Trigger conditions (STOP immediately if ANY occur):
|
|
796
|
+
- \u274C Scaffold fails or produces unexpected output
|
|
797
|
+
- \u274C Build errors after scaffold (backend or frontend)
|
|
798
|
+
- \u274C Type errors that seem wrong or unexpected
|
|
799
|
+
- \u274C Anything that "feels off" or deviates from expected flow
|
|
800
|
+
- \u274C Errors you don't understand the root cause of
|
|
801
|
+
- \u274C Subagent returns with failure or unexpected result
|
|
802
|
+
- \u274C Generated code doesn't match expected patterns
|
|
803
|
+
- \u274C Missing files or imports after generation
|
|
804
|
+
|
|
805
|
+
### How to report:
|
|
806
|
+
"""
|
|
807
|
+
Tool: mcp__agent-planning__report_debug_insight
|
|
808
|
+
Parameters:
|
|
809
|
+
- category: "scaffold_failure" | "build_error" | "unexpected_behavior" | "weird_error"
|
|
810
|
+
- description: Clear explanation of what went wrong
|
|
811
|
+
- context: What you were trying to do when this happened
|
|
812
|
+
- errorOutput: The actual error message (paste it)
|
|
813
|
+
"""
|
|
814
|
+
|
|
815
|
+
### After reporting:
|
|
816
|
+
Say exactly: "\u{1F6D1} DEBUG: I've hit an issue and reported it. Please review and tell me how to proceed."
|
|
817
|
+
Then STOP. Do not continue. Do not try to fix it. Wait for user.
|
|
818
|
+
|
|
819
|
+
### Important: PASS THIS INFO TO SUBAGENTS TOO!
|
|
820
|
+
|
|
821
|
+
---
|
|
822
|
+
|
|
823
|
+
`:"",c=cn(o),l=ln(o,s),p=dn(o,r),u=un(r),d=r?gn(o):"",v=r?fn(o,s):"",w=pn(r);return`${i}${a}# Agent Instructions
|
|
824
|
+
|
|
825
|
+
## \u26D4 CRITICAL: EVERYTHING IS A KANBAN TASK
|
|
826
|
+
|
|
827
|
+
**YOU DO NOT HAVE AN INTERNAL TODO LIST.**
|
|
828
|
+
Your brain is the Kanban board. You must act based on what is on the board.
|
|
829
|
+
You have mcp tools to help you:
|
|
830
|
+
|
|
831
|
+
**YOUR CORE LOOP:**
|
|
832
|
+
1. **CHECK BOARD:** "get_kanban_board" to see what to do.
|
|
833
|
+
2. **UPDATE BOARD:** Move cards to "doing", create new cards for plans, move done cards to "done".
|
|
834
|
+
3. **EXECUTE:** Delegate to subagents based on the active card.
|
|
835
|
+
|
|
836
|
+
---
|
|
837
|
+
|
|
838
|
+
## \u26D4 CRITICAL: USE MCP TOOL FOR QUESTIONS
|
|
839
|
+
|
|
840
|
+
**NEVER use the built-in AskUserQuestion tool.** It is disabled.
|
|
841
|
+
|
|
842
|
+
When you need to ask the user questions, you MUST use the MCP tool:
|
|
843
|
+
"""
|
|
844
|
+
mcp__agent-planning__ask_questions
|
|
845
|
+
"""
|
|
846
|
+
|
|
847
|
+
This tool allows you to ask multiple questions with predefined answer options.
|
|
848
|
+
|
|
849
|
+
---
|
|
850
|
+
|
|
851
|
+
## \u26D4 CRITICAL: USE KANBAN MCP FOR TASK MANAGEMENT
|
|
852
|
+
|
|
853
|
+
**NEVER use the built-in TodoWrite tool.** It is disabled.
|
|
854
|
+
|
|
855
|
+
The kanban board is your source of truth for work tracking. Do not create internal lists.
|
|
856
|
+
|
|
857
|
+
### Kanban Tools & Usage
|
|
858
|
+
|
|
859
|
+
**Read operations (lightweight, progressive):**
|
|
860
|
+
- \`get_kanban_board\` - Overview: columns with card counts only
|
|
861
|
+
- \`get_column_cards\` - Cards in a column: titles, priorities, IDs
|
|
862
|
+
- \`get_card_details\` - Full card: description, subtasks
|
|
863
|
+
|
|
864
|
+
**Write operations:**
|
|
865
|
+
- \`create_kanban_card\` - Create a new task card
|
|
866
|
+
- \`update_kanban_card\` - Update card title, description, priority, or due date
|
|
867
|
+
- \`move_kanban_card\` - Move card between columns: **"Todo" \u2192 "In Progress" \u2192 "Done"**
|
|
868
|
+
- \`delete_kanban_card\` - Remove a task
|
|
869
|
+
|
|
870
|
+
**Subtask operations:**
|
|
871
|
+
- \`create_subtask\` - Add a subtask to a card
|
|
872
|
+
- \`toggle_subtask\` - Mark subtask complete/incomplete
|
|
873
|
+
- \`delete_subtask\` - Remove a subtask
|
|
874
|
+
|
|
875
|
+
### How to Use Kanban for Tracking Work
|
|
876
|
+
|
|
877
|
+
**1. Before starting work:**
|
|
878
|
+
- Call "get_kanban_board" to see what needs to be done
|
|
879
|
+
- Pick the top card from "Todo" column
|
|
880
|
+
- Move it to "In Progress" with \`move_kanban_card\`
|
|
881
|
+
|
|
882
|
+
**2. While working:**
|
|
883
|
+
- Keep the card's **Description** updated with what you're currently doing
|
|
884
|
+
- Example: "\u{1F528} Currently scaffolding Task entity with fields: title, status, assignee"
|
|
885
|
+
- Example: "\u{1F504} Running backend build, waiting for result"
|
|
886
|
+
- Example: "\u2705 Entity created, now creating subtasks for backend implementation"
|
|
887
|
+
- As you complete subtasks, use "toggle_subtask" to mark them done
|
|
888
|
+
- Update priority if things change
|
|
889
|
+
- Update due date if timing shifts
|
|
890
|
+
|
|
891
|
+
**3. When blocking/stuck:**
|
|
892
|
+
- Update the description with what's blocking you
|
|
893
|
+
- Example: "\u26D4 Blocked: Frontend build failing with type error in components"
|
|
894
|
+
- Do NOT leave card in "In Progress" if you're waiting for user input
|
|
895
|
+
- Create new card for the blocker or report debug insight
|
|
896
|
+
|
|
897
|
+
**4. When done:**
|
|
898
|
+
- Make sure all subtasks are toggled complete
|
|
899
|
+
- Move card to "Done" with \`move_kanban_card\`
|
|
900
|
+
- The board becomes your execution history
|
|
901
|
+
|
|
902
|
+
### Card Status Rules
|
|
903
|
+
|
|
904
|
+
**Column names (EXACT - case sensitive!):**
|
|
905
|
+
- \`"Backlog"\` - Ideas, not prioritized yet
|
|
906
|
+
- \`"Todo"\` - Tasks queued, ready to start
|
|
907
|
+
- \`"In Progress"\` - Actively working on it right now
|
|
908
|
+
- \`"Done"\` - Completed
|
|
909
|
+
|
|
910
|
+
**Priority values:**
|
|
911
|
+
- "Low" - Can wait
|
|
912
|
+
- "Medium" - Normal work
|
|
913
|
+
- "High" - Important
|
|
914
|
+
- "Urgent" - Critical path
|
|
915
|
+
|
|
916
|
+
**Description field (use this actively):**
|
|
917
|
+
- Start with \u{1F528} (building), \u{1F504} (processing), \u26D4 (blocked), \u2705 (done)
|
|
918
|
+
- Keep it real-time: "Building X", "Testing Y", "Waiting for user", "Error in Z"
|
|
919
|
+
- This is how you stay organized without internal lists
|
|
920
|
+
|
|
921
|
+
### Example Workflow
|
|
922
|
+
|
|
923
|
+
"""
|
|
924
|
+
1. get_kanban_board \u2192 See columns and card counts
|
|
925
|
+
2. get_column_cards("Todo") \u2192 See cards in Todo, pick one
|
|
926
|
+
3. move_kanban_card(cardId, "In Progress")
|
|
927
|
+
4. get_card_details(cardId) \u2192 Read full description if needed
|
|
928
|
+
5. [work]
|
|
929
|
+
6. toggle_subtask \u2192 Mark subtasks done as you complete them
|
|
930
|
+
7. move_kanban_card(cardId, "Done")
|
|
931
|
+
8. get_column_cards("Todo") \u2192 Pick next card
|
|
932
|
+
"""
|
|
933
|
+
|
|
934
|
+
---
|
|
935
|
+
|
|
936
|
+
## \u26D4 CRITICAL: MANDATORY SUBAGENT DELEGATION
|
|
937
|
+
|
|
938
|
+
**YOU MUST DELEGATE TO SUBAGENTS. NEVER DO THE WORK YOURSELF.**
|
|
939
|
+
|
|
940
|
+
When user asks to build ANY feature, your FIRST action is to delegate to @planning subagent.
|
|
941
|
+
|
|
942
|
+
**\u26A0\uFE0F DO NOT:**
|
|
943
|
+
- Write plans yourself
|
|
944
|
+
- Use EnterPlanMode or any built-in planning
|
|
945
|
+
- Skip the @planning step
|
|
946
|
+
|
|
947
|
+
${u}
|
|
948
|
+
|
|
949
|
+
---
|
|
950
|
+
|
|
951
|
+
You are a combination of Jony Ive and Steve Jobs\u2014you create beautiful, valuable, and working software.
|
|
952
|
+
You know that overengineering is the enemy of good software. You are pragmatic and you know when to stop.
|
|
953
|
+
|
|
954
|
+
**Your users may be non-technical.** They have ideas, not code. You are their bridge to magic.
|
|
955
|
+
|
|
956
|
+
${c}
|
|
957
|
+
|
|
958
|
+
${l}
|
|
959
|
+
|
|
960
|
+
${p}
|
|
961
|
+
|
|
962
|
+
${w}
|
|
963
|
+
|
|
964
|
+
${d}
|
|
965
|
+
|
|
966
|
+
${v}
|
|
967
|
+
`}function cn(n){let e=Pe(n),t=$(n),s=H(n),o=we(n),r=ae(n),i=[];o&&i.push(`- Backend: "${o}/{Entity}/"`),r&&i.push(`- Frontend: "${r}/{entity}/"`);let a=n.techStack?.patterns?.length?`
|
|
968
|
+
**Key patterns:**
|
|
969
|
+
${n.techStack.patterns.map(p=>`- ${p}`).join(`
|
|
970
|
+
`)}`:"",l=n.techStack?.backend?.some(p=>p.includes(".NET")||p.includes("C#"))??!1?`
|
|
971
|
+
|
|
972
|
+
**\u26A0\uFE0F User entity exists!** ASP.NET Identity "ApplicationUser" at "Features/Users/" - don't scaffold User, just relate to it.`:"";return`<tech-stack>
|
|
973
|
+
${e}
|
|
974
|
+
${i.length>0?`
|
|
975
|
+
**Architecture:** Feature folders
|
|
976
|
+
${i.join(`
|
|
977
|
+
`)}`:""}
|
|
978
|
+
${a}${l}
|
|
979
|
+
</tech-stack>`}function ln(n,e){return e?`<environment>
|
|
980
|
+
**LOCAL MODE - User manages their own services.**
|
|
981
|
+
|
|
982
|
+
You are running on the user's local machine, NOT in a Docker container.
|
|
983
|
+
|
|
984
|
+
**\u26A0\uFE0F CRITICAL LOCAL MODE RULES:**
|
|
985
|
+
- **NEVER** restart, stop, or start services - the user controls their own processes
|
|
986
|
+
- **NEVER** try to kill processes or manage ports
|
|
987
|
+
- You can still run \`check_backend_build\` and \`check_frontend_types\` to verify code
|
|
988
|
+
- You can still read logs with \`tail_service_log\` if the user has services running
|
|
989
|
+
- If builds fail, fix the code - don't try to restart anything
|
|
990
|
+
|
|
991
|
+
The user is responsible for:
|
|
992
|
+
- Starting/stopping their services
|
|
993
|
+
- Managing their database
|
|
994
|
+
- Any service restarts needed after code changes
|
|
995
|
+
|
|
996
|
+
**Your job:** Write code, verify it compiles, report results. Let the user handle the rest.
|
|
997
|
+
</environment>`:`<environment>
|
|
998
|
+
Docker container with pre-started services:
|
|
999
|
+
|
|
1000
|
+
${_e(n)}
|
|
1001
|
+
${n.tunnel?.enabled?"| Tunnel | - | - | Exposes frontend to user |":""}
|
|
1002
|
+
|
|
1003
|
+
User sees app through **Cloudflare tunnel** (live preview in browser).
|
|
1004
|
+
</environment>`}function un(n){return n?`**\u2705 ALWAYS delegate to these subagents:**
|
|
1005
|
+
|
|
1006
|
+
| Trigger | Subagent | Example |
|
|
1007
|
+
|---------|----------|---------|
|
|
1008
|
+
| Any new feature request | @planning | "build a golf app", "add tasks" |
|
|
1009
|
+
| Create entity, CRUD, scaffold | @scaffolding | "add task management" |
|
|
1010
|
+
| Custom backend code | @backend | "add lookup query" |
|
|
1011
|
+
| Frontend UI code | @frontend | "create the UI" |
|
|
1012
|
+
| Build error, bug, fix | @debugger | "error CS0246" |
|
|
1013
|
+
|
|
1014
|
+
**Flow:** @planning \u2192 **[Create Kanban Cards]** \u2192 @scaffolding \u2192 @backend \u2192 @frontend \u2192 @debugger`:`**\u2705 ALWAYS delegate to these subagents:**
|
|
1015
|
+
|
|
1016
|
+
| Trigger | Subagent | Example |
|
|
1017
|
+
|---------|----------|---------|
|
|
1018
|
+
| Any new feature request | @planning | "build a feature", "add tasks" |
|
|
1019
|
+
| Build error, bug, fix | @debugger | "fix this error" |
|
|
1020
|
+
|
|
1021
|
+
**Flow:** @planning \u2192 **[Create Kanban Cards]** \u2192 Execute work \u2192 @debugger (if issues)
|
|
1022
|
+
|
|
1023
|
+
Note: For this project, you implement the code directly after planning (no stack-specific subagents).
|
|
1024
|
+
Check \`.claude/skills/\` for project-specific patterns and conventions.`}function pn(n){return n?`<workflow>
|
|
1025
|
+
**Standard feature flow:**
|
|
1026
|
+
1. **@planning** - Gather requirements, design spec, saves it.
|
|
1027
|
+
2. **\u23F8\uFE0F STOP & WAIT** - Tell user: "Spec is ready! Review it and tell me when to implement."
|
|
1028
|
+
3. **User approves** - User says "looks good", "let's build".
|
|
1029
|
+
4. **KANBAN CREATION** - YOU (Main Agent) read the spec and create Kanban cards:
|
|
1030
|
+
|
|
1031
|
+
**\u26A0\uFE0F CRITICAL: Cards MUST include spec details, not generic descriptions!**
|
|
1032
|
+
|
|
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.
|
|
1034
|
+
|
|
1035
|
+
For each card, extract the relevant details from the specification:
|
|
1036
|
+
|
|
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
|
|
1041
|
+
|
|
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"
|
|
1047
|
+
|
|
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"
|
|
1053
|
+
|
|
1054
|
+
**Use subtasks** for individual requirements within each card.
|
|
1055
|
+
|
|
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!
|
|
1060
|
+
|
|
1061
|
+
**GOOD example (includes spec details):**
|
|
1062
|
+
- 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.
|
|
1065
|
+
|
|
1066
|
+
5. **EXECUTION LOOP**:
|
|
1067
|
+
- "get_kanban_board"
|
|
1068
|
+
- Pick top "Todo" card \u2192 Move to "In Progress"
|
|
1069
|
+
- Delegate to appropriate subagent (@scaffolding / @backend / @frontend)
|
|
1070
|
+
- Subagent finishes \u2192 Move card to "Done"
|
|
1071
|
+
- Repeat until board is empty OR if you need user input.
|
|
1072
|
+
|
|
1073
|
+
**\u26A0\uFE0F CRITICAL: After @planning, you MUST wait for user approval before continuing!**
|
|
1074
|
+
|
|
1075
|
+
**Rules:**
|
|
1076
|
+
- After @planning \u2192 STOP and wait for user to approve the spec
|
|
1077
|
+
- Backend MUST complete + generate swagger before frontend starts
|
|
1078
|
+
- Frontend NEVER writes fetch calls - uses generated hooks only
|
|
1079
|
+
- Git commits are automatic
|
|
1080
|
+
</workflow>`:`<workflow>
|
|
1081
|
+
**Standard feature flow:**
|
|
1082
|
+
1. **@planning** - Gather requirements, design spec, saves it.
|
|
1083
|
+
2. **\u23F8\uFE0F STOP & WAIT** - Tell user: "Spec is ready! Review it and tell me when to implement."
|
|
1084
|
+
3. **User approves** - User says "looks good", "let's build".
|
|
1085
|
+
4. **KANBAN CREATION** - Create Kanban cards with detailed descriptions:
|
|
1086
|
+
|
|
1087
|
+
**\u26A0\uFE0F CRITICAL: Cards MUST include full implementation details!**
|
|
1088
|
+
|
|
1089
|
+
Each card description should include:
|
|
1090
|
+
- What needs to be built
|
|
1091
|
+
- Specific requirements from the spec
|
|
1092
|
+
- Any dependencies or ordering constraints
|
|
1093
|
+
|
|
1094
|
+
5. **EXECUTION LOOP**:
|
|
1095
|
+
- "get_kanban_board"
|
|
1096
|
+
- Pick top "Todo" card \u2192 Move to "In Progress"
|
|
1097
|
+
- Implement the feature (check \`.claude/skills/\` for project patterns)
|
|
1098
|
+
- Move card to "Done"
|
|
1099
|
+
- Repeat until board is empty OR if you need user input.
|
|
1100
|
+
|
|
1101
|
+
**\u26A0\uFE0F CRITICAL: After @planning, you MUST wait for user approval before continuing!**
|
|
1102
|
+
|
|
1103
|
+
**Rules:**
|
|
1104
|
+
- After @planning \u2192 STOP and wait for user to approve the spec
|
|
1105
|
+
- Check project's existing code for patterns before implementing
|
|
1106
|
+
- Use @debugger if you encounter build errors or bugs
|
|
1107
|
+
</workflow>`}function dn(n,e){let t=`**@planning** - Design before building:
|
|
1108
|
+
- Complex features \u2192 plan first
|
|
1109
|
+
- Asks questions, designs spec
|
|
1110
|
+
- **Saves spec using save_specification MCP tool**
|
|
1111
|
+
- **After @planning completes: STOP and wait for user approval!**
|
|
1112
|
+
`;if(e){let s=!!n.scaffold?.command,o=!!n.services?.backend,r=!!n.services?.frontend;s&&(t+=`
|
|
1113
|
+
**@scaffolding** - Create CRUD entities, database, backend & frontend:
|
|
1114
|
+
- "Add X management" \u2192 one-shot all entities, everything from entity creation to frontend hooks and pages!
|
|
1115
|
+
- NEVER write scaffold JSON yourself
|
|
1116
|
+
`),o&&(t+=`
|
|
1117
|
+
**@backend** - Custom backend code (non-scaffold):
|
|
1118
|
+
- Custom queries, endpoints, lookups
|
|
1119
|
+
- Runs build + swagger generation before done
|
|
1120
|
+
${r?"- **Must complete before @frontend starts!**":""}
|
|
1121
|
+
`),r&&(t+=`
|
|
1122
|
+
**@frontend** - UI components:
|
|
1123
|
+
- Uses GENERATED API hooks only
|
|
1124
|
+
- Never writes manual fetch calls
|
|
1125
|
+
`)}return t+=`
|
|
1126
|
+
**@debugger** - ONLY for bugs/errors:
|
|
1127
|
+
- Build failures, runtime exceptions
|
|
1128
|
+
- NOT for writing new features!
|
|
1129
|
+
`,t+=`
|
|
1130
|
+
**@project-context** - Understand the codebase:
|
|
1131
|
+
- Explore project structure
|
|
1132
|
+
- Find existing patterns
|
|
1133
|
+
- Understand conventions
|
|
1134
|
+
`,`<subagents>
|
|
1135
|
+
**\u26A0\uFE0F MANDATORY DELEGATION - You MUST use subagents:**
|
|
1136
|
+
|
|
1137
|
+
${t}
|
|
1138
|
+
</subagents>`}function gn(n){return n.services?.frontend?n.techStack?.frontend?.some(t=>t.includes("TanStack")||t.includes("React Query")||t.includes("MUI"))??!1?`<frontend-api>
|
|
1139
|
+
## Orval-Generated API Hooks
|
|
1140
|
+
|
|
1141
|
+
Frontend uses **Orval** to auto-generate React Query hooks from Swagger. Import from 'api/queries-commands.ts'.
|
|
1142
|
+
|
|
1143
|
+
**Naming patterns:**
|
|
1144
|
+
- 'useGetApi{Entity}' - List all (e.g., 'useGetApiPeople')
|
|
1145
|
+
- 'useGetApi{Entity}Id' - Get by ID
|
|
1146
|
+
- 'usePostApi{Entity}' - Create
|
|
1147
|
+
- 'usePutApi{Entity}Id' - Update
|
|
1148
|
+
- 'useDeleteApi{Entity}Id' - Delete
|
|
1149
|
+
- 'getGetApi{Entity}QueryKey' - For query invalidation
|
|
1150
|
+
|
|
1151
|
+
**Example usage:**
|
|
1152
|
+
'''tsx
|
|
1153
|
+
import { useGetApiPeople, usePostApiPeople, getGetApiPeopleQueryKey } from '../api/queries-commands'
|
|
1154
|
+
|
|
1155
|
+
// Query
|
|
1156
|
+
const { data } = useGetApiPeople()
|
|
1157
|
+
|
|
1158
|
+
// Mutation
|
|
1159
|
+
const createMutation = usePostApiPeople()
|
|
1160
|
+
createMutation.mutate({ data: { name: 'John' } })
|
|
1161
|
+
|
|
1162
|
+
// Invalidate after mutation
|
|
1163
|
+
queryClient.invalidateQueries({ queryKey: getGetApiPeopleQueryKey() })
|
|
1164
|
+
'''
|
|
1165
|
+
|
|
1166
|
+
**Important:** Check existing feature code (e.g., 'features/person/') for correct patterns before writing new API calls.
|
|
1167
|
+
</frontend-api>`:`<frontend-api>
|
|
1168
|
+
## API Integration
|
|
1169
|
+
|
|
1170
|
+
Check the project's existing patterns for API calls and data fetching.
|
|
1171
|
+
Look for existing feature code to understand the correct patterns before writing new API calls.
|
|
1172
|
+
</frontend-api>`:""}function fn(n,e){if(!n.services?.frontend)return"";let t=n.services.frontend.port,s=ae(n),o=`http://localhost:${t}`;return`<ui-verification>
|
|
1173
|
+
## \u{1F50D} UI Verification with Playwright
|
|
1174
|
+
|
|
1175
|
+
You have access to **Playwright MCP** tools to verify the UI works correctly after making changes.
|
|
1176
|
+
|
|
1177
|
+
**Available tools:**
|
|
1178
|
+
|
|
1179
|
+
*Basic navigation & inspection:*
|
|
1180
|
+
- \`browser_navigate\` - Open a URL in the browser
|
|
1181
|
+
- \`browser_snapshot\` - Get the page structure (accessibility tree) - fast, LLM-friendly
|
|
1182
|
+
- \`browser_take_screenshot\` - Capture a visual screenshot
|
|
1183
|
+
- \`browser_click\` - Click on DOM elements (by accessibility label)
|
|
1184
|
+
- \`browser_type\` - Type text into inputs
|
|
1185
|
+
- \`browser_scroll_down/up\` - Scroll the page
|
|
1186
|
+
- \`browser_wait\` - Wait for page to settle
|
|
1187
|
+
|
|
1188
|
+
*Vision/coordinate-based (for canvas, WebGL, Three.js, games):*
|
|
1189
|
+
- \`browser_screen_click\` - Click at specific x,y coordinates
|
|
1190
|
+
- \`browser_screen_move_mouse\` - Move mouse to x,y position
|
|
1191
|
+
- \`browser_screen_drag\` - Drag from one coordinate to another
|
|
1192
|
+
- \`browser_screen_type\` - Type at current position
|
|
1193
|
+
|
|
1194
|
+
*JavaScript execution:*
|
|
1195
|
+
- \`browser_evaluate\` - Run JavaScript code on the page (powerful!)
|
|
1196
|
+
|
|
1197
|
+
*Tab management:*
|
|
1198
|
+
- \`browser_tab_list\` / \`browser_tab_new\` / \`browser_tab_select\` / \`browser_tab_close\`
|
|
1199
|
+
|
|
1200
|
+
**When to use:**
|
|
1201
|
+
- After @frontend completes a feature, verify it renders correctly
|
|
1202
|
+
- When debugging UI issues - see what's actually on screen
|
|
1203
|
+
- To confirm navigation/routing works
|
|
1204
|
+
- To test forms and interactions
|
|
1205
|
+
|
|
1206
|
+
**Verification workflow:**
|
|
1207
|
+
1. Navigate to the app: \`browser_navigate\` to \`${s?.includes("super-admin")?`${o}/super-admin/[feature]`:`${o}/[feature]`}\`
|
|
1208
|
+
2. Get page structure: \`browser_snapshot\` returns the accessibility tree (LLM-friendly)
|
|
1209
|
+
3. Optional: Take screenshot for user: \`browser_take_screenshot\`
|
|
1210
|
+
4. Verify expected elements exist in the snapshot
|
|
1211
|
+
5. Report any issues found
|
|
1212
|
+
|
|
1213
|
+
**Tips:**
|
|
1214
|
+
- \`browser_snapshot\` is faster and more reliable than screenshots for DOM-based UIs
|
|
1215
|
+
- Use \`browser_screen_click\` for canvas/WebGL/Three.js - accessibility tools can't see inside canvas
|
|
1216
|
+
- Use \`browser_evaluate\` to run JavaScript when you need complex interactions
|
|
1217
|
+
- Use screenshots when you want to show the user what you see
|
|
1218
|
+
- ${e?"In local mode, the browser window will be visible to you":"In container mode, browser runs headless"}
|
|
1219
|
+
</ui-verification>`}function ce(n){return n&&n.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,"\uFFFD")}var Te=null;function Ge(n){Te=ce(n),console.log(`[ProjectPrompt] Updated (${Te?Te.length:0} chars)`)}function xt(){return ce(Te)}import*as Ie from"path";import{fileURLToPath as hn}from"url";var yn=He(),Dt=Ie.dirname(hn(import.meta.url)),Re=Ie.resolve(Dt,"../mcps/stdio-server.js");console.log("[MCP] Stdio server path resolved:",Re);console.log("[MCP] __dirname:",Dt);async function Ee(n,e={}){let{model:t,sessionId:s=null,projectId:o=null,apiUrl:r=null,callbacks:i={},debugLog:a,abortController:c,debugMode:l=!1,isLocal:p=process.env.LOCAL_MODE==="true",projectConfig:u=G(process.env.WORKSPACE_DIR||process.cwd()),useDefaultStack:d=!0}=e,v=It(a),w=new Set,S=s||"",O,j,f,M=!1,V=!1,g=process.env.WORKSPACE_DIR||process.cwd();console.log("[MCP] Configuring agent-planning stdio server:"),console.log("[MCP] Command: node"),console.log("[MCP] Args:",[Re]),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=",g),console.log("[MCP] File exists check:",Re);try{for await(let h of mn({prompt:n,options:{abortController:c,cwd:g,resume:s??void 0,allowedTools:["Bash","Read","Write","Edit","MultiEdit","Glob","Grep","LS","WebFetch","NotebookRead","NotebookEdit","mcp__agent-insights__check_backend_build","mcp__agent-insights__check_frontend_types","mcp__agent-insights__check_service_health",...p?[]:["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__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_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_tab_list","mcp__playwright__browser_tab_new","mcp__playwright__browser_tab_select","mcp__playwright__browser_tab_close"],model:t,mcpServers:{"agent-insights":yn,"agent-planning":{type:"stdio",command:"node",args:[Re],env:{WORKSPACE_DIR:g,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",...p?[]:["--headless"]]}},settingSources:["project"],disallowedTools:["AskUserQuestion","TodoWrite","EnterPlanMode"],agents:d?{scaffolding:Be,debugger:me,planning:he,backend:Ke,frontend:qe,"project-context":ye}:{debugger:me,planning:he,"project-context":ye},systemPrompt:At({debugMode:l,projectPrompt:xt(),isLocal:p,projectConfig:u,useDefaultStack:d})}}))if(!(!h.uuid||w.has(h.uuid))){switch(w.add(h.uuid),v?.log(h),i.onRawMessage?.(h),h.type){case"assistant":if(h.message?.content)for(let E of h.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?.(h);break;case"result":if(h.subtype==="success")O=h.result,j=h.total_cost_usd,i.onResult?.(h.result,h.total_cost_usd);else{let E=`Agent error: ${h.subtype}`;f=E,i.onError?.(E)}V=!0;break;case"system":switch(h.subtype){case"init":S=h.session_id,v?.setSessionId(h.session_id),i.onSystemInit?.(h.session_id);break;case"status":i.onSystemStatus?.(JSON.stringify(h));break;case"compact_boundary":break;case"hook_response":break}break;case"stream_event":i.onStreamEvent?.(h);break;case"tool_progress":i.onToolProgress?.(h);break;case"auth_status":i.onAuthStatus?.(h);break}if(V)break}}catch(h){h instanceof Error&&h.name==="AbortError"||h instanceof Error&&h.message.includes("aborted by user")?(M=!0,i.onAborted?.()):(f=h instanceof Error?h.message:String(h),i.onError?.(f))}finally{v?.stop()}return console.log("after try"),{sessionId:S,result:O,cost:j,error:f,aborted:M}}import{spawn as jt,execSync as X}from"child_process";import*as R from"fs";import*as Ot from"http";import*as J from"path";function Ae(n){let{workspaceDir:e,apiPort:t,webPort:s,onBackendError:o,onFrontendError:r,projectConfig:i=G(e)}=n,a=t??i.services?.backend?.port??5338,c=s??i.services?.frontend?.port??5173,l=$(i),p=H(i),u=i.services?.backend?.startCommand??"dotnet run",d=i.services?.backend?.buildCommand??"dotnet build",v=i.services?.backend?.typecheckCommand??"dotnet build --no-restore",w=i.services?.frontend?.startCommand??"npm run dev",S=i.services?.frontend?.typecheckCommand??"npx tsc -p tsconfig.app.json --noEmit",O=i.services?.backend?.logFile??"/tmp/api.log",j=i.services?.frontend?.logFile??"/tmp/web.log",f=null,M=null,V=b=>new Promise(m=>{let k=setTimeout(()=>m(!1),3e3);Ot.get(`http://localhost:${b}`,D=>{clearTimeout(k),m(D.statusCode!==void 0&&D.statusCode<500)}).on("error",()=>{clearTimeout(k),m(!1)})}),g=b=>{try{let m=X(`lsof -ti:${b} 2>/dev/null || true`,{encoding:"utf-8"}).trim();if(m){let k=m.split(`
|
|
1220
|
+
`).filter(Boolean);console.log(`[Services] Killing ${k.length} process(es) on port ${b}: ${k.join(", ")}`);for(let P of k)try{process.kill(parseInt(P,10),"SIGKILL")}catch{}}}catch{try{X(`fuser -k ${b}/tcp 2>/dev/null || true`,{encoding:"utf-8"})}catch{}}},h=(b,m)=>new Promise(k=>{if(!b||!b.pid){k();return}console.log(`[Services] Stopping ${m} (PID: ${b.pid})...`);try{process.kill(-b.pid,"SIGTERM")}catch{try{b.kill("SIGTERM")}catch{}}setTimeout(k,500)}),E=async()=>{if(!l||!R.existsSync(l)){console.log("[Services] No backend found, skipping backend start");return}g(a),console.log(`[Services] Starting backend with: ${u}...`);let b=J.dirname(O);R.existsSync(b)||R.mkdirSync(b,{recursive:!0});let m=R.createWriteStream(O,{flags:"a"}),[k,...P]=u.split(" ");f=jt(k,P,{cwd:l,stdio:["ignore","pipe","pipe"],detached:!0,shell:!0});let D="",A=_=>{let W=_.toString();m.write(W);let F=(D+W).split(`
|
|
1221
|
+
`);D=F.pop()||"";for(let L of F)L&&Sn(L)&&o&&o(L)};f.stdout?.on("data",A),f.stderr?.on("data",A),f.on("exit",_=>{m.end(),_!==0&&_!==null&&o&&o(`Process exited with code ${_}`)}),f.unref()},pt=async()=>{if(!p||!R.existsSync(J.join(p,"package.json"))){console.log("[Services] No frontend found, skipping frontend start");return}g(c),console.log(`[Services] Starting frontend with: ${w}...`);let b=J.dirname(j);R.existsSync(b)||R.mkdirSync(b,{recursive:!0});let[m,...k]=w.split(" ");M=jt(m,k,{cwd:p,stdio:["ignore",R.openSync(j,"w"),R.openSync(j,"w")],detached:!0,shell:!0}),M.unref()},dt=async()=>{await h(f,"backend"),f=null,g(a)},gt=async()=>{await h(M,"frontend"),M=null,g(c)},Le=async()=>{let[b,m]=await Promise.all([V(a),V(c)]);return{api:b,web:m}},ft=async(b=15e3)=>{let m=Date.now(),k=1e3,P=0,D=null,A=null;for(console.log(`[Services] Waiting for health (timeout: ${b}ms)...`);Date.now()-m<b;){P++;let F=await Le(),L=Date.now()-m;if(F.web&&A===null&&(A=L,console.log(`[Services] \u2713 Frontend (Vite) ready after ${L}ms`)),F.api&&D===null&&(D=L,console.log(`[Services] \u2713 Backend (dotnet) ready after ${L}ms`)),console.log(`[Services] Health poll #${P} (${L}ms): api=${F.api}, web=${F.web}`),F.api&&F.web)return console.log(`[Services] \u2713 Both services healthy after ${L}ms (${P} polls)`),F;await new Promise(Qt=>setTimeout(Qt,k))}let _=await Le(),W=Date.now()-m;return _.web&&A===null&&console.log(`[Services] \u2713 Frontend (Vite) ready after ${W}ms`),_.api&&D===null&&console.log(`[Services] \u2713 Backend (dotnet) ready after ${W}ms`),console.log(`[Services] \u26A0 Health timeout after ${W}ms: api=${_.api}, web=${_.web}`),_},Wt=async b=>{console.log(`[Services] Restarting ${b}...`);let m={success:!0},k=b==="backend"||b==="both",P=b==="frontend"||b==="both";if(k&&await dt(),P&&await gt(),k&&l&&R.existsSync(l)){console.log(`[Services] Building backend with: ${d}...`);try{X(d,{cwd:l,stdio:"pipe",timeout:12e4,encoding:"utf-8"}),console.log("[Services] \u2713 Backend build succeeded")}catch(A){let _=A;m.success=!1,m.backendError=_.stderr||_.stdout||"Build failed",console.error("[Services] \u2717 Backend build failed")}}if(P&&p&&R.existsSync(J.join(p,"package.json"))){console.log(`[Services] Type-checking frontend with: ${S}...`);try{X(S,{cwd:p,stdio:"pipe",timeout:6e4,encoding:"utf-8"}),console.log("[Services] \u2713 Frontend types OK")}catch(A){let _=A;m.success=!1,m.frontendError=_.stderr||_.stdout||"Type check failed",console.error("[Services] \u2717 Frontend type check failed")}}console.log(`[Services] Starting ${b}...`),k&&await E(),P&&await pt();let D=await ft(1e4);if(k&&!D.api){m.success=!1;let A=await yt("backend",20),_=A.length>0?`
|
|
1222
|
+
Recent logs:
|
|
1223
|
+
${A.join(`
|
|
1224
|
+
`)}`:"";m.backendError||(m.backendError=`Backend failed to start.${_}`)}return P&&!D.web&&(m.success=!1,m.frontendError||(m.frontendError="Frontend failed to start")),m},mt=async()=>{if(!l||!R.existsSync(l))return{success:!0};try{return X(v,{cwd:l,stdio:"pipe",timeout:12e4,encoding:"utf-8"}),{success:!0}}catch(b){let m=b;return{success:!1,errors:m.stdout||m.stderr||"Build failed"}}},ht=async()=>{if(!p||!R.existsSync(J.join(p,"package.json")))return{success:!0};try{return X(S,{cwd:p,stdio:"pipe",timeout:6e4,encoding:"utf-8"}),{success:!0}}catch(b){let m=b;return{success:!1,errors:m.stdout||m.stderr||"Type check failed"}}},Yt=async()=>{let[b,m]=await Promise.all([mt(),ht()]);return{backend:b,frontend:m}},yt=async(b,m)=>{let k=b==="backend"?O:j;if(!R.existsSync(k))return[`Log file not found: ${k}`];try{return X(`tail -n ${m} ${k}`,{encoding:"utf-8"}).split(`
|
|
1225
|
+
`).filter(Boolean)}catch(P){return[`Error reading logs: ${P instanceof Error?P.message:String(P)}`]}};return{startBackend:E,startFrontend:pt,stopBackend:dt,stopFrontend:gt,restartServices:Wt,checkHealth:Le,waitForHealth:ft,getProcesses:()=>({backend:f,frontend:M}),checkBackendBuild:mt,checkFrontendTypes:ht,runTypeChecks:Yt,tailLogs:yt,checkSwaggerEndpoints:async b=>{let m=J.join(e,"swagger.json");if(!R.existsSync(m))return{foundEndpoints:["Error: swagger.json not found"],totalEndpoints:0};try{let k=JSON.parse(R.readFileSync(m,"utf-8")),P=Object.keys(k.paths||{}),D=[];for(let A of P)if(A.toLowerCase().includes(b.toLowerCase())){let _=Object.keys(k.paths[A]);for(let W of _)D.push(`${W.toUpperCase()} ${A}`)}return{foundEndpoints:D,totalEndpoints:P.length}}catch(k){return{foundEndpoints:[`Error parsing swagger.json: ${k instanceof Error?k.message:String(k)}`],totalEndpoints:0}}}}}function Sn(n){let e=n.toLowerCase();return e.includes("exception")||e.includes("fail:")||e.includes("[err]")||e.includes("[error]")}import{execSync as Z}from"child_process";function xe(n){let e=async()=>{try{return Z("git status --porcelain",{cwd:n,encoding:"utf-8"}).trim().length>0}catch{return!1}},t=async()=>{try{return Z("git branch --show-current",{cwd:n,encoding:"utf-8"}).trim()}catch{return"unknown"}};return{hasChanges:e,commitAndPush:async(o,r)=>{try{if(!await e())return{success:!0,noChanges:!0};let i=o.length>60?o.substring(0,60)+"...":o,c=`${r?"[agent] (partial)":"[agent]"} ${i}`;Z("git add -A",{cwd:n}),Z(`git commit -m "${c.replace(/"/g,'\\"')}"`,{cwd:n,encoding:"utf-8"});let l=Z("git rev-parse --short HEAD",{cwd:n,encoding:"utf-8"}).trim();try{Z("git push",{cwd:n,stdio:"pipe"})}catch(p){let u=p instanceof Error?p.message:String(p);if(u.includes("no upstream branch")){let d=await t();Z(`git push --set-upstream origin ${d}`,{cwd:n,stdio:"pipe"})}else return console.error("[Git] Push failed:",u),{success:!0,commitHash:l,error:`Committed but push failed: ${u}`}}return{success:!0,commitHash:l}}catch(i){let a=i instanceof Error?i.message:String(i);return console.error("[Git] Error:",a),{success:!1,error:a}}},getCurrentBranch:t}}import*as ne from"@microsoft/signalr";var z=class{constructor(e,t){this.connection=e;this.receiverMethod=t}dispose=()=>{for(let e of this.receiverMethod)this.connection.off(e.methodName,e.method)}},Mt=n=>{if(n==="IDeploymentHub")return We.Instance;if(n==="IKanbanHub")return Qe.Instance;if(n==="IAgentHub")return ze.Instance;if(n==="ISlaveHub")return Xe.Instance;if(n==="ITestHub")return et.Instance},Ut=n=>{if(n==="IDeploymentHubClient")return nt.Instance;if(n==="IKanbanBoardClient")return ot.Instance;if(n==="IAgentReceiver")return st.Instance;if(n==="IBackofficeReceiver")return rt.Instance;if(n==="ISlaveHubClient")return it.Instance;if(n==="ITestHubClient")return at.Instance},We=class n{static Instance=new n;constructor(){}createHubProxy=e=>new Ye(e)},Ye=class{constructor(e){this.connection=e}registerMachine=async(e,t)=>await this.connection.invoke("RegisterMachine",e,t);reportDeploymentStatus=async e=>await this.connection.invoke("ReportDeploymentStatus",e);reportHealth=async e=>await this.connection.invoke("ReportHealth",e);reportMachineStatus=async e=>await this.connection.invoke("ReportMachineStatus",e);sendDeploymentLog=async e=>await this.connection.invoke("SendDeploymentLog",e)},Qe=class n{static Instance=new n;constructor(){}createHubProxy=e=>new Je(e)},Je=class{constructor(e){this.connection=e}joinBoard=async e=>await this.connection.invoke("JoinBoard",e);leaveBoard=async e=>await this.connection.invoke("LeaveBoard",e)},ze=class n{static Instance=new n;constructor(){}createHubProxy=e=>new Ve(e)},Ve=class{constructor(e){this.connection=e}registerAgent=async e=>await this.connection.invoke("RegisterAgent",e);getSecrets=async()=>await this.connection.invoke("GetSecrets");reportStatus=async(e,t)=>await this.connection.invoke("ReportStatus",e,t);reportSessionOutput=async(e,t,s)=>await this.connection.invoke("ReportSessionOutput",e,t,s);reportSessionCost=async(e,t,s)=>await this.connection.invoke("ReportSessionCost",e,t,s);reportInsight=async(e,t)=>await this.connection.invoke("ReportInsight",e,t);setClaudeSessionId=async(e,t)=>await this.connection.invoke("SetClaudeSessionId",e,t);sessionCompleted=async(e,t,s)=>await this.connection.invoke("SessionCompleted",e,t,s);initSessionCompleted=async(e,t,s,o)=>await this.connection.invoke("InitSessionCompleted",e,t,s,o);reportPreviewBuild=async(e,t,s)=>await this.connection.invoke("ReportPreviewBuild",e,t,s);saveSpecification=async(e,t,s,o)=>await this.connection.invoke("SaveSpecification",e,t,s,o);getSpecification=async(e,t)=>await this.connection.invoke("GetSpecification",e,t);listSpecifications=async e=>await this.connection.invoke("ListSpecifications",e);deleteSpecification=async(e,t)=>await this.connection.invoke("DeleteSpecification",e,t);watchContainer=async e=>await this.connection.invoke("WatchContainer",e);watchSession=async e=>await this.connection.invoke("WatchSession",e);watchSlaves=async()=>await this.connection.invoke("WatchSlaves");unwatchSlaves=async()=>await this.connection.invoke("UnwatchSlaves");sendPrompt=async(e,t,s,o)=>await this.connection.invoke("SendPrompt",e,t,s,o);stopSession=async e=>await this.connection.invoke("StopSession",e);switchSession=async(e,t)=>await this.connection.invoke("SwitchSession",e,t);createSession=async e=>await this.connection.invoke("CreateSession",e)},Xe=class n{static Instance=new n;constructor(){}createHubProxy=e=>new Ze(e)},Ze=class{constructor(e){this.connection=e}register=async(e,t)=>await this.connection.invoke("Register",e,t);getSecrets=async()=>await this.connection.invoke("GetSecrets");containerCreated=async e=>await this.connection.invoke("ContainerCreated",e);containerReady=async e=>await this.connection.invoke("ContainerReady",e);reportCapacity=async e=>await this.connection.invoke("ReportCapacity",e)},et=class n{static Instance=new n;constructor(){}createHubProxy=e=>new tt(e)},tt=class{constructor(e){this.connection=e}joinProject=async e=>await this.connection.invoke("JoinProject",e);leaveProject=async e=>await this.connection.invoke("LeaveProject",e)},nt=class n{static Instance=new n;constructor(){}register=(e,t)=>{let s=(...i)=>t.deploy(...i),o=(...i)=>t.stop(...i);e.on("Deploy",s),e.on("Stop",o);let r=[{methodName:"Deploy",method:s},{methodName:"Stop",method:o}];return new z(e,r)}},ot=class n{static Instance=new n;constructor(){}register=(e,t)=>{let s=(...f)=>t.boardUpdated(...f),o=(...f)=>t.boardDeleted(...f),r=(...f)=>t.columnCreated(...f),i=(...f)=>t.columnUpdated(...f),a=(...f)=>t.columnDeleted(...f),c=(...f)=>t.cardCreated(...f),l=(...f)=>t.cardUpdated(...f),p=(...f)=>t.cardDeleted(...f),u=(...f)=>t.subtaskCreated(...f),d=(...f)=>t.subtaskUpdated(...f),v=(...f)=>t.subtaskDeleted(...f),w=(...f)=>t.noteCreated(...f),S=(...f)=>t.noteUpdated(...f),O=(...f)=>t.noteDeleted(...f);e.on("BoardUpdated",s),e.on("BoardDeleted",o),e.on("ColumnCreated",r),e.on("ColumnUpdated",i),e.on("ColumnDeleted",a),e.on("CardCreated",c),e.on("CardUpdated",l),e.on("CardDeleted",p),e.on("SubtaskCreated",u),e.on("SubtaskUpdated",d),e.on("SubtaskDeleted",v),e.on("NoteCreated",w),e.on("NoteUpdated",S),e.on("NoteDeleted",O);let j=[{methodName:"BoardUpdated",method:s},{methodName:"BoardDeleted",method:o},{methodName:"ColumnCreated",method:r},{methodName:"ColumnUpdated",method:i},{methodName:"ColumnDeleted",method:a},{methodName:"CardCreated",method:c},{methodName:"CardUpdated",method:l},{methodName:"CardDeleted",method:p},{methodName:"SubtaskCreated",method:u},{methodName:"SubtaskUpdated",method:d},{methodName:"SubtaskDeleted",method:v},{methodName:"NoteCreated",method:w},{methodName:"NoteUpdated",method:S},{methodName:"NoteDeleted",method:O}];return new z(e,j)}},st=class n{static Instance=new n;constructor(){}register=(e,t)=>{let s=(...u)=>t.initSession(...u),o=(...u)=>t.runPrompt(...u),r=(...u)=>t.setModel(...u),i=()=>t.stopAgent(),a=()=>t.ping(),c=(...u)=>t.switchSession(...u),l=(...u)=>t.projectPromptUpdated(...u);e.on("InitSession",s),e.on("RunPrompt",o),e.on("SetModel",r),e.on("StopAgent",i),e.on("Ping",a),e.on("SwitchSession",c),e.on("ProjectPromptUpdated",l);let p=[{methodName:"InitSession",method:s},{methodName:"RunPrompt",method:o},{methodName:"SetModel",method:r},{methodName:"StopAgent",method:i},{methodName:"Ping",method:a},{methodName:"SwitchSession",method:c},{methodName:"ProjectPromptUpdated",method:l}];return new z(e,p)}},rt=class n{static Instance=new n;constructor(){}register=(e,t)=>{let s=(...S)=>t.sessionOutput(...S),o=(...S)=>t.sessionCost(...S),r=(...S)=>t.sessionCompleted(...S),i=(...S)=>t.containerStatus(...S),a=(...S)=>t.slaveCapacityUpdate(...S),c=(...S)=>t.previewBuildStatus(...S),l=(...S)=>t.previewReload(...S),p=(...S)=>t.questionsReceived(...S),u=(...S)=>t.specificationUpdated(...S),d=(...S)=>t.singleQuestionReceived(...S),v=(...S)=>t.questionSessionEnded(...S);e.on("SessionOutput",s),e.on("SessionCost",o),e.on("SessionCompleted",r),e.on("ContainerStatus",i),e.on("SlaveCapacityUpdate",a),e.on("PreviewBuildStatus",c),e.on("PreviewReload",l),e.on("QuestionsReceived",p),e.on("SpecificationUpdated",u),e.on("SingleQuestionReceived",d),e.on("QuestionSessionEnded",v);let w=[{methodName:"SessionOutput",method:s},{methodName:"SessionCost",method:o},{methodName:"SessionCompleted",method:r},{methodName:"ContainerStatus",method:i},{methodName:"SlaveCapacityUpdate",method:a},{methodName:"PreviewBuildStatus",method:c},{methodName:"PreviewReload",method:l},{methodName:"QuestionsReceived",method:p},{methodName:"SpecificationUpdated",method:u},{methodName:"SingleQuestionReceived",method:d},{methodName:"QuestionSessionEnded",method:v}];return new z(e,w)}},it=class n{static Instance=new n;constructor(){}register=(e,t)=>{let s=(...i)=>t.startContainer(...i),o=(...i)=>t.terminateContainer(...i);e.on("StartContainer",s),e.on("TerminateContainer",o);let r=[{methodName:"StartContainer",method:s},{methodName:"TerminateContainer",method:o}];return new z(e,r)}},at=class n{static Instance=new n;constructor(){}register=(e,t)=>{let s=(...l)=>t.testRunCreated(...l),o=(...l)=>t.testRunUpdated(...l),r=(...l)=>t.testSuiteCreated(...l),i=(...l)=>t.testCreated(...l),a=(...l)=>t.testUpdated(...l);e.on("TestRunCreated",s),e.on("TestRunUpdated",o),e.on("TestSuiteCreated",r),e.on("TestCreated",i),e.on("TestUpdated",a);let c=[{methodName:"TestRunCreated",method:s},{methodName:"TestRunUpdated",method:o},{methodName:"TestSuiteCreated",method:r},{methodName:"TestCreated",method:i},{methodName:"TestUpdated",method:a}];return new z(e,c)}};var De=class{constructor(e){this.config=e;this.connection=new ne.HubConnectionBuilder().withUrl(`${e.masterUrl}/api/hubs/agent`,{headers:{"X-Container-Id":e.containerId,"X-Project-Key":e.projectKey}}).withAutomaticReconnect({nextRetryDelayInMilliseconds:t=>{let s=t.previousRetryCount+1,o=Math.min(1e3*Math.pow(2,t.previousRetryCount),6e4);return console.log(`[SignalR] Reconnect attempt ${s} (will retry in ${o}ms)`),o}}).withServerTimeout(3e5).withKeepAliveInterval(1e4).configureLogging(ne.LogLevel.Information).build(),this.hubProxy=Mt("IAgentHub").createHubProxy(this.connection),this.connection.onreconnected(async()=>{console.log("[SignalR] Reconnected to gateway");try{await this.hubProxy.registerAgent(this.config.containerId),console.log(`[SignalR] Re-registered as agent for container: ${this.config.containerId}`),await this.reportStatus(this.currentStatus,void 0,this.currentSessionId??void 0,this.currentProjectId??void 0)}catch(t){console.error("[SignalR] Failed to re-register agent after reconnect:",t instanceof Error?t.message:t)}}),this.connection.onclose(t=>{console.error("[SignalR] Connection closed:",t?.message),process.exit(1)})}connection;hubProxy;receiverSubscription=null;currentStatus="WarmingUp";currentSessionId=null;currentProjectId=null;tunnelUrl=null;async connect(){console.log(`[SignalR] Connecting to gateway: ${this.config.masterUrl}/api/hubs/agent`),await this.connection.start(),console.log("[SignalR] Connected to gateway!"),await this.hubProxy.registerAgent(this.config.containerId),console.log(`[SignalR] Registered as agent for container: ${this.config.containerId}`)}registerReceiver(e){this.receiverSubscription&&this.receiverSubscription.dispose(),this.receiverSubscription=Ut("IAgentReceiver").register(this.connection,e)}getHubProxy(){return this.hubProxy}getMasterUrl(){return this.config.masterUrl}getProjectKey(){return this.config.projectKey}getConnection(){return this.connection}setTunnelUrl(e){this.tunnelUrl=e}async reportStatus(e,t,s,o){if(this.currentStatus=e,s&&(this.currentSessionId=s),o&&(this.currentProjectId=o),this.connection.state===ne.HubConnectionState.Connected)try{let r={status:e,error:t,sessionId:s,projectId:o,tunnelUrl:this.tunnelUrl??void 0};await this.hubProxy.reportStatus(this.config.containerId,r),console.log(`[Status] ${e}${t?` (${t})`:""}${this.tunnelUrl?` [${this.tunnelUrl}]`:""}${s?` [session: ${s}]`:""}`)}catch(r){console.error("[SignalR] Failed to report status:",r)}}async getSecrets(){let e=await this.hubProxy.getSecrets();if(!e.success)throw new Error(`Failed to get secrets: ${e.error}`);return e.secrets||{}}};function kn(n){let e=n.match(/name:\s*"([^"]+)"/);if(e)return e[1];let t=n.match(/^#\s+(.+)$/m);return t?t[1].trim():"Untitled Specification"}var je=class{constructor(e,t){this.hubProxy=e;this.projectId=t}async saveSpecification(e,t){let s=kn(t),o=await this.hubProxy.saveSpecification(this.projectId,e,s,t);if(!o.success)throw new Error(o.error||"Failed to save specification")}async getSpecification(e){let t=await this.hubProxy.getSpecification(this.projectId,e);return t.success&&t.content||null}async listSpecifications(){let e=await this.hubProxy.listSpecifications(this.projectId);return!e.success||!e.specifications?[]:e.specifications.map(t=>({slug:t.slug,name:t.name,status:t.status,version:"1.0.0"}))}async deleteSpecification(e){return(await this.hubProxy.deleteSpecification(this.projectId,e)).success}async specificationExists(e){let t=await this.hubProxy.getSpecification(this.projectId,e);return t.success&&t.content!=null}};import{exec as vn,execSync as Cn,spawn as wn}from"child_process";import{promisify as Pn}from"util";import{createHash as Nt}from"crypto";import*as C from"fs";import*as N from"path";var Oe=class{domain;accountId;tunnelId;apiToken;zoneId;tunnelApiBase;constructor(){this.domain=process.env.CLOUDFLARE_DEPLOY_DOMAIN||process.env.DEPLOY_DOMAIN||"vibecodementor.net",this.accountId=process.env.CLOUDFLARE_ACCOUNT_ID||"",this.tunnelId=process.env.CLOUDFLARE_TUNNEL_ID||"",this.apiToken=process.env.CLOUDFLARE_API_TOKEN||"",this.zoneId=process.env.CLOUDFLARE_ZONE_ID||"",this.tunnelApiBase=`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/cfd_tunnel/${this.tunnelId}/configurations`}isConfigured(){return!!(this.accountId&&this.tunnelId&&this.apiToken&&this.zoneId)}buildHostname(e){let t=this.domain.split("."),s=t.length>2?t.slice(-2).join("."):this.domain;return`${e}.${s}`}async addRoute(e,t){let s=this.buildHostname(e);if(console.log(`[TunnelManager] Adding route: ${s} -> localhost:${t}`),!this.isConfigured())return console.log("[TunnelManager] Not configured, skipping route addition"),`https://${s}`;try{let o=await this.getConfig(),r=o.ingress.findIndex(a=>a.hostname===s);if(r!==-1)o.ingress[r].service=`http://localhost:${t}`;else{let a=o.ingress.findIndex(l=>!l.hostname),c={hostname:s,service:`http://localhost:${t}`};a!==-1?o.ingress.splice(a,0,c):(o.ingress.push(c),o.ingress.push({service:"http_status:404"}))}await this.putConfig(o),await this.ensureDnsPointsToTunnel(s);let i=`https://${s}`;return console.log(`[TunnelManager] \u2713 Route added: ${i}`),i}catch(o){throw console.error("[TunnelManager] Failed to add route:",o),o}}async removeRoute(e){let t=this.buildHostname(e);if(console.log(`[TunnelManager] Removing route: ${t}`),!this.isConfigured()){console.log("[TunnelManager] Not configured, skipping route removal");return}try{let s=await this.getConfig();s.ingress=s.ingress.filter(o=>o.hostname!==t),s.ingress.some(o=>!o.hostname)||s.ingress.push({service:"http_status:404"}),await this.putConfig(s),console.log(`[TunnelManager] \u2713 Route removed: ${t}`)}catch(s){throw console.error("[TunnelManager] Failed to remove route:",s),s}}async getConfig(){let t=await(await fetch(this.tunnelApiBase,{method:"GET",headers:{Authorization:`Bearer ${this.apiToken}`,"Content-Type":"application/json"}})).json();if(!t.success)throw new Error(`Cloudflare API error: ${t.errors.map(s=>s.message).join(", ")}`);return t.result?.config||{ingress:[{service:"http_status:404"}]}}async putConfig(e){let s=await(await fetch(this.tunnelApiBase,{method:"PUT",headers:{Authorization:`Bearer ${this.apiToken}`,"Content-Type":"application/json"},body:JSON.stringify({config:e})})).json();if(!s.success)throw new Error(`Cloudflare API error: ${s.errors.map(o=>o.message).join(", ")}`);console.log("[TunnelManager] Configuration updated via Cloudflare API")}async ensureDnsPointsToTunnel(e){let t=`${this.tunnelId}.cfargotunnel.com`,s=`https://api.cloudflare.com/client/v4/zones/${this.zoneId}/dns_records`;try{let r=await(await fetch(`${s}?name=${e}&type=CNAME`,{method:"GET",headers:{Authorization:`Bearer ${this.apiToken}`,"Content-Type":"application/json"}})).json();if(!r.success||!r.result?.length){console.log(`[TunnelManager] No DNS record found for ${e}, skipping DNS update`);return}let i=r.result[0];if(i.content===t){console.log(`[TunnelManager] DNS already points to correct tunnel: ${e}`);return}console.log(`[TunnelManager] Updating DNS: ${e} from ${i.content} to ${t}`);let c=await(await fetch(`${s}/${i.id}`,{method:"PATCH",headers:{Authorization:`Bearer ${this.apiToken}`,"Content-Type":"application/json"},body:JSON.stringify({type:"CNAME",name:e,content:t,proxied:!0})})).json();c.success?console.log(`[TunnelManager] \u2713 DNS updated: ${e} -> ${t}`):console.error(`[TunnelManager] Failed to update DNS: ${c.errors.map(l=>l.message).join(", ")}`)}catch(o){console.error(`[TunnelManager] Error updating DNS for ${e}:`,o)}}};var Ft=Pn(vn),I=class{start;label;constructor(e){this.label=e,this.start=Date.now(),console.log(`[TIMING] \u23F1 START: ${e}`)}stop(){let e=Date.now()-this.start,t=(e/1e3).toFixed(2);return console.log(`[TIMING] \u2713 DONE: ${this.label} (${t}s)`),e}};function U(n,e={}){return new Promise((t,s)=>{let o=wn(n,[],{shell:!0,stdio:"inherit",cwd:e.cwd}),r=null;e.timeout&&(r=setTimeout(()=>{o.kill(),s(new Error(`Command timed out: ${n}`))},e.timeout)),o.on("close",i=>{r&&clearTimeout(r),i===0?t():s(new Error(`Command failed with code ${i}: ${n}`))}),o.on("error",i=>{r&&clearTimeout(r),s(i)})})}var Lt="main",Me=class{constructor(e,t,s,o){this.connection=e;this.serviceManager=t;this.workspaceDir=s;this.gitHubPat=o}currentBranchName=Lt;currentRepoUrl="";lastLockfileHash=null;tunnelManager=null;currentPreviewSubdomain=null;getTunnelManager(){return this.tunnelManager||(this.tunnelManager=new Oe),this.tunnelManager}async prewarmWorkspace(){let e=new I("TOTAL INIT"),t=process.env.REPO_URL,s=process.env.BRANCH_NAME||Lt,o=process.env.PREVIEW_SUBDOMAIN,r=process.env.PREVIEW_HOSTNAME,i=process.env.PROJECT_GITHUB_PAT;if(console.log("[Init] Starting single-phase init..."),console.log(`[Init] Repo: ${t?this.extractRepoPath(t):"NOT SET"}`),console.log(`[Init] Branch: ${s}`),console.log(`[Init] Preview: ${o||"none"}`),!t)throw console.error("[Init] \u274C REPO_URL not set - cannot initialize workspace"),new Error("REPO_URL environment variable is required");i&&(this.gitHubPat=i);let a=new I("Clean workspace");Cn("rm -rf /workspace/* /workspace/.* 2>/dev/null || true",{stdio:"inherit"}),a.stop();let c=new I("Clone repository");await this.cloneRepository(t,s),this.currentRepoUrl=t,this.currentBranchName=s,c.stop(),await this.installDependencies(),this.lastLockfileHash=this.getLockfileHash(),console.log("[Init] Starting services...");let l=new I("Start backend (dotnet)");await this.serviceManager.startBackend(),l.stop();let p=new I("Start frontend (vite)");await this.serviceManager.startFrontend(),p.stop();let u=new I("Wait for health check");await this.serviceManager.waitForHealth(3e4),u.stop(),o&&r&&await this.setupPreviewSubdomainFromEnv(o,r),e.stop(),console.log("[Init] \u2713 Workspace ready")}async handleInitSession(e){console.log(`[InitSession] Session ${e.sessionId} starting...`);let t=this.extractRepoPath(e.repoUrl),s=this.extractRepoPath(this.currentRepoUrl),o=t!==s,r=e.branchName!==this.currentBranchName;o||r?(console.log("[InitSession] Repo/branch mismatch - updating workspace..."),console.log(`[InitSession] Current: ${s}@${this.currentBranchName}`),console.log(`[InitSession] Requested: ${t}@${e.branchName}`),await this.connection.reportStatus("Ready",void 0,e.sessionId,e.projectId),await this.connection.getHubProxy().initSessionCompleted(process.env.CONTAINER_ID||"unknown",e.sessionId,!0,""),this.handleRepoChange(e).catch(async a=>{let c=a instanceof Error?a.message:"Unknown error";console.error("[InitSession] Background update failed:",c),await this.connection.reportStatus("Error",c,e.sessionId,e.projectId)})):(console.log("[InitSession] Workspace already matches - ready immediately"),await this.connection.reportStatus("Ready",void 0,e.sessionId,e.projectId),await this.connection.getHubProxy().initSessionCompleted(process.env.CONTAINER_ID||"unknown",e.sessionId,!0,"")),console.log(`[InitSession] \u2713 Ready for session ${e.sessionId}`)}async setupPreviewSubdomainFromEnv(e,t){let s=new I("Setup preview subdomain");console.log(`[Preview] Setting up preview subdomain: ${e} (${t})`);let o=this.getTunnelManager();if(!o){console.log("[Preview] TunnelManager not available, skipping subdomain setup"),s.stop();return}if(!o.isConfigured()){console.log("[Preview] Cloudflare not configured, skipping subdomain setup"),s.stop();return}try{let r=parseInt(process.env.ALLOCATED_HOST_PORT||"5173"),i=await o.addRoute(e,r);this.currentPreviewSubdomain=e,this.connection.setTunnelUrl(i),s.stop(),console.log(`[Preview] \u2713 Preview subdomain configured: ${i}`)}catch(r){s.stop(),console.error("[Preview] Failed to set up subdomain route:",r)}}async handleRepoChange(e){let t=new I("TOTAL REPO CHANGE");e.gitHubPat&&e.gitHubPat!==this.gitHubPat&&(console.log("[RepoChange] Updating git credentials"),this.gitHubPat=e.gitHubPat,await U(`echo "https://${e.gitHubPat}@github.com" > ~/.git-credentials`));let s=this.extractRepoPath(e.repoUrl),o=this.extractRepoPath(this.currentRepoUrl);if(s!==o){let i=new I("Clone new repository");await this.cloneRepository(e.repoUrl,e.branchName),this.currentRepoUrl=e.repoUrl,this.currentBranchName=e.branchName,i.stop(),await this.installDependencies();let a=new I("Restart services");await this.serviceManager.restartServices("both"),a.stop()}else{let i=new I(`Switch branch to ${e.branchName}`);await this.switchBranch(e.branchName),i.stop(),await this.checkNeedsReinstall()&&await this.installDependencies();let c=new I("Restart services");await this.serviceManager.restartServices("both"),c.stop()}e.previewSubdomain&&e.previewHostname&&await this.setupPreviewSubdomainFromEnv(e.previewSubdomain,e.previewHostname),t.stop(),console.log("[RepoChange] \u2713 Workspace updated")}extractRepoPath(e){let t=e.match(/github\.com[:/]([^/]+\/[^/]+?)(?:\.git)?$/);return t?t[1]:e}async cloneRepository(e,t){console.log(`[Clone] Clearing workspace and cloning ${this.extractRepoPath(e)} @ ${t}`),await U("rm -rf /workspace/* /workspace/.* 2>/dev/null || true"),await U(`git clone -b ${t} "${e}" /workspace`,{timeout:12e4}),await U('cd /workspace && git config user.email "agent@dotnetmentor.se" && git config user.name "Agent"'),console.log("[Clone] \u2713 Repository cloned")}async switchBranch(e){if(console.log(`[Branch] Switching from ${this.currentBranchName} to ${e}`),e===this.currentBranchName)console.log(`[Branch] Already on ${e}, pulling latest...`),await U(`git pull origin ${e}`,{cwd:this.workspaceDir,timeout:6e4});else{await U("git fetch origin",{cwd:this.workspaceDir,timeout:6e4});let{stdout:t}=await Ft(`git ls-remote --heads origin ${e}`,{cwd:this.workspaceDir,timeout:3e4});t.trim().length>0?await U(`git checkout -B ${e} origin/${e}`,{cwd:this.workspaceDir,timeout:3e4}):(console.log(`[Branch] Branch ${e} doesn't exist on remote, creating from main...`),await U(`git checkout -b ${e}`,{cwd:this.workspaceDir,timeout:3e4})),this.currentBranchName=e}console.log(`[Branch] \u2713 Now on ${e}`)}getLockfileHash(){let e=N.join(this.workspaceDir,"packages/backoffice-web/package-lock.json");return C.existsSync(e)?Nt("sha256").update(C.readFileSync(e)).digest("hex"):null}async checkNeedsReinstall(){let e=this.getLockfileHash();return!e||!this.lastLockfileHash?!0:e!==this.lastLockfileHash}async checkCodeDiff(e,t){if(e===t)return!1;try{let{stdout:s}=await Ft(`git diff ${e}..HEAD --name-only`,{cwd:this.workspaceDir,timeout:3e4}),o=s.trim();if(!o)return console.log(`[CodeDiff] No file differences between ${e} and ${t}`),!1;let r=o.split(`
|
|
1226
|
+
`).filter(c=>c.length>0);console.log(`[CodeDiff] ${r.length} files changed between ${e} and ${t}`);let i=[".cs",".tsx",".ts",".json",".csproj"],a=r.some(c=>i.some(l=>c.endsWith(l)));return a&&console.log("[CodeDiff] Found code changes that require service restart"),a}catch(s){return console.error("[CodeDiff] Failed to check diff:",s instanceof Error?s.message:s),!0}}async installDependencies(){let e=new I("TOTAL INSTALL DEPENDENCIES");if(C.existsSync(N.join(this.workspaceDir,"packages/dotnet-api"))){let t=new I("dotnet restore");await U("dotnet restore",{cwd:N.join(this.workspaceDir,"packages/dotnet-api"),timeout:12e4}),t.stop()}if(C.existsSync(N.join(this.workspaceDir,"packages/backoffice-web/package.json"))){let t=N.join(this.workspaceDir,"packages/backoffice-web"),s=N.join(t,"node_modules"),o=N.join(t,"package-lock.json"),r=N.join(s,".lockhash"),i="/opt/cache";C.mkdirSync(i,{recursive:!0});let a=C.existsSync(o)?Nt("sha256").update(C.readFileSync(o)).digest("hex"):null,c=C.existsSync(r)?C.readFileSync(r,"utf-8").trim():null,l=N.join(i,"node_modules","current_hash"),p=C.existsSync(l)?C.readFileSync(l,"utf-8").trim():null,u=p?N.join(i,"node_modules",p):null,d=a?N.join(i,"node_modules",a):null;if(console.log(`[Install] node_modules exists: ${C.existsSync(s)}`),console.log(`[Install] lockHash (current branch): ${a?.substring(0,8)||"null"}`),console.log(`[Install] installedHash: ${c?.substring(0,8)||"null"}`),console.log(`[Install] dockerCacheHash (from image): ${p?.substring(0,8)||"null"}`),console.log(`[Install] exactMatchCache exists: ${d?C.existsSync(d):!1}`),console.log(`[Install] dockerCachedNodeModules exists: ${u?C.existsSync(u):!1}`),!C.existsSync(s)){if(d&&C.existsSync(d)){let w=new I("Copy exact-match cached node_modules");console.log(`[Install] Copying exact-match cached node_modules (hash ${a?.substring(0,8)})...`),await U(`cp -r "${d}" "${s}"`),a&&C.writeFileSync(r,a),c=a,w.stop()}else if(u&&C.existsSync(u)){let w=new I("Copy Docker-cached node_modules (as base)");console.log(`[Install] Copying Docker-cached node_modules as base (hash ${p?.substring(0,8)}, need ${a?.substring(0,8)})...`),await U(`cp -r "${u}" "${s}"`),w.stop()}}let v=!C.existsSync(s)||a&&a!==c;if(console.log(`[Install] needsInstall: ${v}`),!v)console.log("[Install] npm install skipped (cache OK)");else{let w=new I("npm install --prefer-offline");await U("npm install --prefer-offline --no-progress --fund=false",{cwd:t,timeout:18e4}),w.stop(),a&&(C.mkdirSync(s,{recursive:!0}),C.writeFileSync(r,a))}this.lastLockfileHash=a}e.stop()}};function $t(n,e,t){let s=n.getHubProxy(),o=process.env.CONTAINER_ID||"unknown";return{onAssistantText:async r=>{let i={type:"Text",content:r,isError:!1};await s.reportSessionOutput(o,e,i).catch(a=>{console.error("Failed to report assistant text:",a)})},onAssistantToolUse:async(r,i)=>{if(["Write","Edit","MultiEdit"].includes(r)){let c=i,l=c.file_path||c.target_file;l&&t.onFileModified(l)}let a={type:"ToolUse",toolName:r,toolInput:JSON.stringify(i),isError:!1};await s.reportSessionOutput(o,e,a).catch(c=>{console.error("Failed to report tool use:",c)})},onResult:async(r,i)=>{let a={type:"System",event:"completed",content:r,cost:i,isError:!1};await s.reportSessionOutput(o,e,a).catch(c=>{console.error("Failed to report result:",c)})},onError:async r=>{let i={type:"System",event:"error",content:r,isError:!0};await s.reportSessionOutput(o,e,i).catch(a=>{console.error("Failed to report error:",a)})},onAborted:async()=>{let r={type:"System",event:"aborted",content:"Agent stopped by user",isError:!1};await s.reportSessionOutput(o,e,r).catch(i=>{console.error("Failed to report abort:",i)})},onSystemInit:async r=>{console.log(`[Callbacks] Claude session initialized: ${r}`),t.onClaudeSessionId(r),await s.setClaudeSessionId(e,r).catch(i=>{console.error("Failed to set Claude session ID:",i)})},onRawMessage:r=>{if(r.type==="assistant"&&r.message?.usage){let i=r.message.usage,a={totalCostUsd:_n(i),inputTokens:i.input_tokens||0,outputTokens:i.output_tokens||0,cacheCreationTokens:i.cache_creation_input_tokens||0,cacheReadTokens:i.cache_read_input_tokens||0};s.reportSessionCost(o,e,a).catch(c=>{console.error("Failed to report cost:",c)})}}}}var Ue={inputPerMillion:3,outputPerMillion:15,cacheReadPerMillion:.3,cacheCreationPerMillion:3.75};function _n(n){return(n.input_tokens||0)/1e6*Ue.inputPerMillion+(n.output_tokens||0)/1e6*Ue.outputPerMillion+(n.cache_read_input_tokens||0)/1e6*Ue.cacheReadPerMillion+(n.cache_creation_input_tokens||0)/1e6*Ue.cacheCreationPerMillion}import*as oe from"fs";import*as Ht from"path";function Bt(n,e){let t=Ht.join(n,`.agent-questions-${e}.json`);if(!oe.existsSync(t))return null;try{let s=oe.readFileSync(t,"utf-8");return oe.unlinkSync(t),JSON.parse(s)}catch(s){return console.error("Failed to read questions file:",s),null}}async function Kt(n,e,t,s){if(e.size===0)return console.log("[TypeCheck] No code changes, skipping type checks"),{passed:!0};let o=s?$(s):`${t}/packages/dotnet-api`,r=s?H(s):`${t}/packages/backoffice-web`,i=s?.services?.backend?.extensions??[".cs",".csproj"],a=s?.services?.frontend?.extensions??[".ts",".tsx"],c=o&&[...e].some(d=>d.includes(o)||i.some(v=>d.endsWith(v))),l=r&&[...e].some(d=>d.includes(r)||a.some(v=>d.endsWith(v)));if(!c&&!l)return console.log("[TypeCheck] No code changes, skipping type checks"),{passed:!0};let p=[];c&&p.push("backend"),l&&p.push("frontend"),console.log(`[TypeCheck] Checking ${p.join(" & ")}...`);let u=[];if(c){let d=await n.checkBackendBuild();!d.success&&d.errors&&u.push(`## Backend Build Errors (in ${o})
|
|
1227
|
+
\`\`\`
|
|
1228
|
+
${d.errors}
|
|
1229
|
+
\`\`\``)}if(l){let d=await n.checkFrontendTypes();!d.success&&d.errors&&u.push(`## Frontend Type Errors (in ${r})
|
|
1230
|
+
\`\`\`
|
|
1231
|
+
${d.errors}
|
|
1232
|
+
\`\`\``)}return u.length>0?(console.log("[TypeCheck] \u274C Type errors found - will auto-correct"),{passed:!1,errorPrompt:`@debugger Type/build errors detected. Fix them.
|
|
1233
|
+
|
|
1234
|
+
WORKSPACE: ${t}
|
|
1235
|
+
|
|
1236
|
+
${u.join(`
|
|
1237
|
+
|
|
1238
|
+
`)}
|
|
1239
|
+
|
|
1240
|
+
File paths like "src/..." are relative to the package directory shown in parentheses.`}):(console.log("[TypeCheck] \u2713 All type checks passed"),{passed:!0})}async function ct(n,e,t,s,o){if(!await n.hasChanges()){console.log("[Git] No changes to commit");return}console.log("[Git] Committing changes...");let i=await n.commitAndPush(s,o);if(i.noChanges)console.log("[Git] No changes to commit");else if(i.success){let a=await n.getCurrentBranch();console.log(`[Git] \u2713 Committed ${i.commitHash||""}`),console.log(`[Git] \u2192 Pushed to ${a}`),await e.getHubProxy().reportSessionOutput(process.env.CONTAINER_ID||"unknown",t,{type:"System",event:"committed",content:`Changes committed to ${a}`,isError:!1}).catch(l=>{console.error("[Git] Failed to report commit:",l)})}else console.error(`[Git] \u274C Git error: ${i.error}`)}var Ne=class{constructor(e,t,s,o,r=!0,i=!0,a,c,l=!0){this.connection=e;this.lifecycle=t;this.serviceManager=s;this.workspaceDir=o;this.enableAutoTypecheck=r;this.enableAutoCommit=i;this.setupPlanningTransport=a;this.projectConfig=c;this.useDefaultStack=l;this.gitManager=xe(o)}currentSession=null;claudeSessionIdMap=new Map;currentAbortController=null;gitManager;async initSession(e){console.log(`[Receiver] InitSession for session ${e.sessionId}`),this.setupPlanningTransport&&e.projectId&&this.setupPlanningTransport(e.projectId),e.projectId&&await this.fetchProjectPrompt(e.projectId),this.currentSession={sessionId:e.sessionId,projectId:e.projectId,branchName:e.branchName,model:e.model==="auto"?void 0:e.model||"claude-haiku-4-5-20251001",debugMode:e.debugMode},await this.lifecycle.handleInitSession(e),e.prompt&&await this.runPrompt(e.sessionId,e.prompt,e.claudeSessionId)}async runPrompt(e,t,s,o=!1){let r=ce(t);if(console.log(`[Receiver] RunPrompt for session ${e}${s?` (ClaudeSessionId: ${s})`:""}${o?" [DEBUG MODE]":""}: ${r.substring(0,50)}...`),(!this.currentSession||this.currentSession.sessionId!==e)&&(console.log(`[Receiver] Session mismatch: expected ${this.currentSession?.sessionId}, got ${e}, switching...`),await this.switchSession(e),!this.currentSession)){console.error("[Receiver] Failed to create session context");return}let i=this.claudeSessionIdMap.get(e);if(s&&i!==s){console.warn(`[Receiver] Claude session not found in container: ${s}`);let l=this.connection.getHubProxy();await l.reportSessionOutput(process.env.CONTAINER_ID||"unknown",e,{type:"System",event:"sessionNotFound",content:s,isError:!0}),await this.connection.reportStatus("Ready","Session lost - summary needed",e,this.currentSession.projectId),await l.sessionCompleted(process.env.CONTAINER_ID||"unknown",e,0);return}await this.connection.reportStatus("Active",void 0,e,this.currentSession.projectId),this.currentSession.debugMode=o,this.currentAbortController=new AbortController;let a=i||null,c=new Set;try{let l=await Ee(r,{model:this.currentSession.model,sessionId:a||null,projectId:this.currentSession.projectId||null,apiUrl:this.connection.getMasterUrl(),debugLog:!0,debugMode:o,abortController:this.currentAbortController,projectConfig:this.projectConfig,useDefaultStack:this.useDefaultStack,callbacks:$t(this.connection,e,{onClaudeSessionId:d=>{this.claudeSessionIdMap.set(e,d)},onFileModified:d=>{c.add(d)}})}),p=Bt(this.workspaceDir,e);if(p&&p.length>0){console.log(`[Receiver] ${p.length} pending questions found, waiting for user answers`),await this.connection.reportStatus("Ready",void 0,e,this.currentSession.projectId),await this.connection.getHubProxy().sessionCompleted(process.env.CONTAINER_ID||"unknown",e,0);return}if(this.enableAutoTypecheck&&!l.error&&!l.aborted){let d=await Kt(this.serviceManager,c,this.workspaceDir,this.projectConfig);if(!d.passed&&d.errorPrompt){console.log("[Receiver] Type errors detected, auto-correcting..."),await this.runPrompt(e,d.errorPrompt,s);return}}this.enableAutoCommit&&!l.error&&!l.aborted?await ct(this.gitManager,this.connection,e,t,!1):(l.error||l.aborted)&&this.enableAutoCommit&&await ct(this.gitManager,this.connection,e,t,!0),await this.connection.reportStatus("Ready",void 0,e,this.currentSession.projectId),await this.connection.getHubProxy().sessionCompleted(process.env.CONTAINER_ID||"unknown",e,l.error?1:0)}catch(l){let p=l instanceof Error&&l.name==="AbortError",u=l instanceof Error?l.message:"Unknown error";p?console.log(`[Receiver] Agent aborted for session ${e}`):(console.error("[Receiver] Agent error:",u),await this.connection.reportStatus("Error",u,e,this.currentSession.projectId)),await this.connection.getHubProxy().sessionCompleted(process.env.CONTAINER_ID||"unknown",e,p?0:1)}finally{this.currentAbortController=null}}async setModel(e){console.log(`[Receiver] Model changed to: ${e}`),this.currentSession&&(this.currentSession.model=e==="auto"?void 0:e)}async stopAgent(){console.log("[Receiver] Stop command received"),this.currentAbortController&&this.currentAbortController.abort()}async ping(){await this.connection.getConnection().invoke("Pong",process.env.CONTAINER_ID||"unknown")}async switchSession(e){console.log(`[Receiver] SwitchSession to ${e}`),this.currentSession?this.currentSession.sessionId=e:(console.log(`[Receiver] Creating new session context for ${e}`),this.currentSession={sessionId:e,projectId:process.env.PROJECT_ID||"unknown",branchName:"main",model:void 0,debugMode:!1})}async projectPromptUpdated(e){console.log(`[Receiver] ProjectPromptUpdated received (${e.length} chars)`),Ge(e)}async fetchProjectPrompt(e){try{let t=this.connection.getMasterUrl(),s=this.connection.getProjectKey(),o=await fetch(`${t}/api/projects/${e}/prompt`,{method:"GET",headers:{"Content-Type":"application/json","X-Project-Key":s}});if(!o.ok){console.log(`[Receiver] Failed to fetch project prompt: ${o.status}`);return}let r=await o.json();r.prompt?(Ge(r.prompt),console.log(`[Receiver] Initial project prompt loaded (${r.prompt.length} chars)`)):console.log(`[Receiver] No project prompt set for project ${e}`)}catch(t){console.error("[Receiver] Error fetching project prompt:",t)}}};process.env.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT=process.env.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT||"60000";function Tn(n){let{useDefaultStack:e,projectConfig:t,configSource:s,tunnelPort:o,enableAutoTypecheck:r,enableAutoCommit:i}=n;if(console.log(`
|
|
1241
|
+
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`),console.log("\u2551 AGENT CONFIGURATION \u2551"),console.log("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"),e?(console.log("\u2551 Mode: DEFAULT STACK (.NET/React) \u2551"),console.log("\u2551 Agents: scaffolding, backend, frontend, debugger, \u2551"),console.log("\u2551 planning, project-context \u2551")):(console.log("\u2551 Mode: CUSTOM STACK (generic agents only) \u2551"),console.log("\u2551 Agents: debugger, planning, project-context \u2551")),console.log("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"),console.log(`\u2551 Config source: ${s.padEnd(42)}\u2551`),console.log("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"),console.log("\u2551 SERVICES: \u2551"),t.services?.backend){let a=t.services.backend;console.log(`\u2551 Backend: port ${a.port}, extensions: ${a.extensions?.join(", ")||"N/A"}`.padEnd(62)+"\u2551")}else console.log("\u2551 Backend: not configured \u2551");if(t.services?.frontend){let a=t.services.frontend;console.log(`\u2551 Frontend: port ${a.port}, extensions: ${a.extensions?.join(", ")||"N/A"}`.padEnd(62)+"\u2551")}else console.log("\u2551 Frontend: not configured \u2551");t.techStack&&(console.log("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"),console.log("\u2551 TECH STACK: \u2551"),t.techStack.backend?.length&&console.log(`\u2551 Backend: ${t.techStack.backend.join(", ")}`.padEnd(62)+"\u2551"),t.techStack.frontend?.length&&console.log(`\u2551 Frontend: ${t.techStack.frontend.join(", ")}`.padEnd(62)+"\u2551"),t.techStack.patterns?.length&&console.log(`\u2551 Patterns: ${t.techStack.patterns.join(", ")}`.padEnd(62)+"\u2551")),console.log("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"),console.log("\u2551 SETTINGS: \u2551"),console.log(`\u2551 Tunnel port: ${o}`.padEnd(62)+"\u2551"),console.log(`\u2551 Auto-typecheck: ${r?"enabled":"disabled"}`.padEnd(62)+"\u2551"),console.log(`\u2551 Auto-commit: ${i?"enabled":"disabled"}`.padEnd(62)+"\u2551"),console.log(`\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
1242
|
+
`)}var Rn=".sdd/local-signalr-config.json";function ut(n){return le.join(n,Rn)}function In(n){try{let e=ut(n);if(!K.existsSync(e))return null;let t=K.readFileSync(e,"utf-8");return JSON.parse(t)}catch{return null}}function lt(n,e){let t=le.dirname(ut(n));K.existsSync(t)||K.mkdirSync(t,{recursive:!0}),K.writeFileSync(ut(n),JSON.stringify(e,null,2))}function Fe(){return`local-${Gt.hostname().toLowerCase().replace(/[^a-z0-9]/g,"-")}-${Date.now().toString(36)}`}function En(n){return!!(n.version||n.paths||n.services||n.techStack)}function An(n,e){return En(n)?{version:n.version||"1.0",paths:n.paths||{workspace:e},services:n.services,techStack:n.techStack,tunnel:n.tunnel,scaffold:n.scaffold,git:n.git}:null}async function xn(n){console.log(`
|
|
1243
|
+
\u{1F527} Interactive Project Config Builder
|
|
1244
|
+
`),console.log(" Answer the following questions to configure your project."),console.log(` Press Enter to skip optional questions.
|
|
1245
|
+
`);let{projectType:e}=await B.prompt([{type:"select",name:"projectType",message:"What type of project is this?",choices:[{name:"Frontend only (React, Next.js, Vue, etc.)",value:"frontend"},{name:"Backend only (Node, Python, Go, etc.)",value:"backend"},{name:"Fullstack (Frontend + Backend)",value:"fullstack"},{name:"Skip - use default .NET/React stack",value:"skip"}]}]);if(e==="skip")return null;let t={version:"1.0",paths:{workspace:"."}};if(e==="frontend"||e==="fullstack"){console.log(`
|
|
1246
|
+
\u{1F4F1} Frontend Configuration:`);let o=await B.prompt([{type:"input",name:"path",message:"Frontend path (relative to workspace):",default:e==="fullstack"?"./frontend":"."},{type:"number",name:"port",message:"Dev server port:",default:3e3},{type:"input",name:"startCommand",message:"Start command (or Enter to skip):",default:"npm run dev"},{type:"input",name:"typecheckCommand",message:"Typecheck command (or Enter to skip):",default:"npx tsc --noEmit"},{type:"input",name:"extensions",message:"File extensions (comma-separated):",default:".ts,.tsx,.js,.jsx"}]);t.paths={workspace:t.paths?.workspace||".",...t.paths,frontend:o.path||"."},t.services={...t.services,frontend:{port:o.port||3e3,startCommand:o.startCommand||"npm run dev",typecheckCommand:o.typecheckCommand||void 0,extensions:o.extensions?o.extensions.split(",").map(r=>r.trim()):[".ts",".tsx",".js",".jsx"]}},t.tunnel={enabled:!0,port:o.port||3e3}}if(e==="backend"||e==="fullstack"){console.log(`
|
|
1247
|
+
\u2699\uFE0F Backend Configuration:`);let o=await B.prompt([{type:"input",name:"path",message:"Backend path (relative to workspace):",default:e==="fullstack"?"./backend":"."},{type:"number",name:"port",message:"API server port:",default:8080},{type:"input",name:"startCommand",message:"Start command (or Enter to skip):",default:""},{type:"input",name:"buildCommand",message:"Build command (or Enter to skip):",default:""},{type:"input",name:"extensions",message:"File extensions (comma-separated):",default:".ts,.js"}]);t.paths={workspace:t.paths?.workspace||".",...t.paths,backend:o.path||"."},t.services={...t.services,backend:{port:o.port||8080,startCommand:o.startCommand||void 0,buildCommand:o.buildCommand||void 0,extensions:o.extensions?o.extensions.split(",").map(r=>r.trim()):[".ts",".js"]}},e==="backend"&&(t.tunnel={enabled:!0,port:o.port||8080})}console.log(`
|
|
1248
|
+
\u{1F4DA} Tech Stack (optional - helps AI understand your project):`);let{addTechStack:s}=await B.prompt([{type:"confirm",name:"addTechStack",message:"Add tech stack info?",default:!1}]);if(s){let o=await B.prompt([{type:"input",name:"frontend",message:"Frontend technologies (comma-separated, or Enter to skip):",default:""},{type:"input",name:"backend",message:"Backend technologies (comma-separated, or Enter to skip):",default:""},{type:"input",name:"patterns",message:"Patterns/practices (comma-separated, or Enter to skip):",default:""}]);t.techStack={},o.frontend&&(t.techStack.frontend=o.frontend.split(",").map(r=>r.trim())),o.backend&&(t.techStack.backend=o.backend.split(",").map(r=>r.trim())),o.patterns&&(t.techStack.patterns=o.patterns.split(",").map(r=>r.trim()))}return t}function qt(n){try{let e={},t=n.match(/MASTER_URL=(\S+)/);t&&(e.masterUrl=t[1].trim());let s=n.match(/PROJECT_ID=(\S+)/);s&&(e.projectId=s[1].trim());let o=n.match(/PROJECT_KEY=(\S+)/);return o&&(e.projectKey=o[1].trim()),e.masterUrl&&e.projectId&&e.projectKey?e:null}catch{return null}}async function Dn(n){let e=In(n);console.log(`
|
|
1249
|
+
\u{1F3E0} Local SignalR Mode - Jack into the real system
|
|
1250
|
+
`);let t=e?.masterUrl&&e?.projectId&&e?.projectKey,{configMethod:s}=await B.prompt([{type:"select",name:"configMethod",message:"How would you like to configure?",choices:[...t?[{name:`Use saved config (${e.projectId})`,value:"saved"}]:[],{name:"Paste config from frontend",value:"paste"},{name:"Enter manually",value:"manual"},{name:"Build project config interactively",value:"interactive"}]}]);if(s==="saved"&&t){let a=An(e,n),c=a?.tunnel?.port||e.tunnelPort||5173;return console.log(`
|
|
1251
|
+
\u{1F4CB} Using saved configuration:`),console.log(` Master URL: ${e.masterUrl}`),console.log(` Project ID: ${e.projectId}`),console.log(` Project Key: ${e.projectKey.substring(0,16)}...`),console.log(` Container ID: ${e.containerId}`),console.log(` Tunnel: ${e.enableTunnel!==!1?`enabled (port ${c})`:"disabled"}`),a&&console.log(" Project config: embedded in local-signalr-config.json"),{masterUrl:e.masterUrl,projectId:e.projectId,projectKey:e.projectKey,containerId:e.containerId||Fe(),enableTunnel:e.enableTunnel!==!1,tunnelPort:c,embeddedProjectConfig:a}}if(s==="paste"){let{pastedConfig:a}=await B.prompt([{type:"input",name:"pastedConfig",message:"Paste the config (MASTER_URL=... PROJECT_ID=... PROJECT_KEY=...):",validate:d=>qt(d)?!0:"Could not parse config. Make sure it contains MASTER_URL=..., PROJECT_ID=..., and PROJECT_KEY=..."}]),c=qt(a),l=e?.containerId||Fe(),p=e?.tunnelPort||5173,u={masterUrl:c.masterUrl,projectId:c.projectId,projectKey:c.projectKey,containerId:l,enableTunnel:!0,tunnelPort:p};return console.log(`
|
|
1252
|
+
\u{1F4CB} Parsed configuration:`),console.log(` Master URL: ${u.masterUrl}`),console.log(` Project ID: ${u.projectId}`),console.log(` Project Key: ${u.projectKey.substring(0,16)}...`),console.log(` Container ID: ${u.containerId}`),console.log(` Tunnel: enabled (port ${p})`),lt(n,u),{...u,embeddedProjectConfig:null}}if(s==="interactive"){console.log(`
|
|
1253
|
+
\u{1F50C} Connection Configuration:`);let a=await B.prompt([{type:"input",name:"masterUrl",message:"Master URL (backend):",default:e?.masterUrl||process.env.MASTER_URL||"http://localhost:5338",validate:d=>{if(!d.trim())return"Master URL is required";try{return new URL(d),!0}catch{return"Please enter a valid URL"}}},{type:"input",name:"projectId",message:"Project ID:",default:e?.projectId||process.env.PROJECT_ID,validate:d=>d.trim()?!0:"Project ID is required"},{type:"password",name:"projectKey",message:"Project Key (pk_proj_...):",mask:"*",default:e?.projectKey||process.env.PROJECT_KEY,validate:d=>d.trim()?d.startsWith("pk_proj_")?!0:"Project Key must start with pk_proj_":"Project Key is required"}]),c=await xn(n),l=e?.containerId||Fe(),p=c?.tunnel?.port||e?.tunnelPort||5173,u={masterUrl:a.masterUrl,projectId:a.projectId,projectKey:a.projectKey,containerId:l,enableTunnel:!0,tunnelPort:p,...c&&{version:c.version,paths:c.paths,services:c.services,techStack:c.techStack,tunnel:c.tunnel}};return console.log(`
|
|
1254
|
+
\u{1F4CB} Configuration built:`),console.log(` Master URL: ${u.masterUrl}`),console.log(` Project ID: ${u.projectId}`),console.log(` Project Key: ${u.projectKey.substring(0,16)}...`),console.log(` Container ID: ${u.containerId}`),console.log(` Tunnel: enabled (port ${p})`),console.log(c?" Project config: custom stack":" Project config: default .NET/React stack"),lt(n,u),{masterUrl:u.masterUrl,projectId:u.projectId,projectKey:u.projectKey,containerId:u.containerId,enableTunnel:u.enableTunnel,tunnelPort:u.tunnelPort,embeddedProjectConfig:c}}let o=await B.prompt([{type:"input",name:"masterUrl",message:"Master URL (backend):",default:e?.masterUrl||process.env.MASTER_URL||"http://localhost:5338",validate:a=>{if(!a.trim())return"Master URL is required";try{return new URL(a),!0}catch{return"Please enter a valid URL"}}},{type:"input",name:"projectId",message:"Project ID:",default:e?.projectId||process.env.PROJECT_ID,validate:a=>a.trim()?!0:"Project ID is required"},{type:"password",name:"projectKey",message:"Project Key (pk_proj_...):",mask:"*",default:e?.projectKey||process.env.PROJECT_KEY,validate:a=>a.trim()?a.startsWith("pk_proj_")?!0:"Project Key must start with pk_proj_":"Project Key is required"},{type:"input",name:"containerId",message:"Container ID (unique identifier for this agent):",default:e?.containerId||Fe()},{type:"confirm",name:"enableTunnel",message:"Enable preview URL reporting (report local dev server URL to backend)?",default:e?.enableTunnel!==!1}]),r=e?.tunnelPort||5173;o.enableTunnel&&(r=(await B.prompt([{type:"number",name:"tunnelPort",message:"Preview port (local dev server port to report):",default:r}])).tunnelPort||r);let i={...o,tunnelPort:r};return lt(n,i),{...i,embeddedProjectConfig:null}}async function jn(){let n=process.env.LOCAL_MODE==="true",e=process.env.WORKSPACE_DIR||process.cwd();console.log(n?"=== Local SignalR Agent ===":"=== SignalR Agent Starting ==="),console.log(`\u{1F4C1} Workspace: ${e}`),n&&!K.existsSync(le.join(e,".git"))&&console.warn("\u26A0\uFE0F Warning: Not a git repository. Some features may not work.");let t,s,o=!1,r=5173,i=null;if(n){let g=await Dn(e);t={masterUrl:g.masterUrl,containerId:g.containerId,projectKey:g.projectKey,workspaceDir:e},s=g.projectId,o=g.enableTunnel,r=g.tunnelPort,i=g.embeddedProjectConfig,process.env.PROJECT_ID=g.projectId,process.env.CONTAINER_ID=g.containerId,process.env.PROJECT_KEY=g.projectKey}else{let g=process.env.PROJECT_KEY;g||(console.error("\u274C PROJECT_KEY environment variable is required"),process.exit(1)),t={masterUrl:process.env.MASTER_URL||"http://host.docker.internal:5338",containerId:process.env.CONTAINER_ID||"unknown",projectKey:g,workspaceDir:e}}console.log(`
|
|
1255
|
+
\u{1F50C} Connecting to backend...`),console.log(` Container ID: ${t.containerId}`),console.log(` Project Key: ${t.projectKey.substring(0,16)}...`),console.log(` Gateway URL: ${t.masterUrl}`);let a=new De(t),c=0,l=10;for(;c<l;)try{await a.connect();break}catch(g){if(c++,console.error(`Connection attempt ${c} failed:`,g instanceof Error?g.message:g),c<l){let h=Math.min(1e3*Math.pow(2,c),3e4);await new Promise(E=>setTimeout(E,h))}}c>=l&&(console.error("\u274C Failed to connect to gateway after max retries"),process.exit(1)),n&&console.log("\u2705 Connected to backend");let p=await a.getSecrets();n?(console.log("\u2705 Handshake complete"),p.gitHubPat&&console.log(" GitHub PAT received from backend")):console.log("\u2705 Secrets received");let u,d=!0,v="default";if(n){if(i)u=i,v="embedded in local-signalr-config.json",d=!1,console.log("[Config] Using embedded project config from local-signalr-config.json");else{let g=await ve({workspaceDir:t.workspaceDir});u=g.config,v=g.source==="file"?g.configPath||".sdd/project-config.json":"default (no config file)",g.source==="file"&&(d=!1)}d||u.tunnel?.port&&(r=u.tunnel.port)}let w=Ae({workspaceDir:t.workspaceDir,projectConfig:u});ge(w);let S=ie(t.workspaceDir);se(S);let O=g=>{console.log(`[Planning] Setting up SignalR transport for project ${g}`),S=new je(a.getHubProxy(),g),se(S)};n&&s&&O(s);let j;n?j={prewarmWorkspace:async()=>{console.log("[LocalMode] Skipping prewarm - using existing workspace")},handleInitSession:async g=>{console.log(`[LocalMode] Session init for ${g.sessionId}`),console.log("[LocalMode] Skipping branch switch - using current branch"),await a.reportStatus("Ready",void 0,g.sessionId,g.projectId),await a.getHubProxy().initSessionCompleted(t.containerId,g.sessionId,!0,"")}}:j=new Me(a,w,t.workspaceDir,p.gitHubPat||null);let f=process.env.DISABLE_AUTO_TYPECHECK!=="true",M=process.env.DISABLE_AUTO_COMMIT!=="true"&&!n;n&&u?Tn({useDefaultStack:d,projectConfig:u,configSource:v,tunnelPort:r,enableAutoTypecheck:f,enableAutoCommit:M}):(console.log(`[Config] Auto-typecheck: ${f?"enabled":"disabled"}`),console.log(`[Config] Auto-commit: ${M?"enabled":"disabled"}`));let V=new Ne(a,j,w,t.workspaceDir,f,M,O,u,d);if(a.registerReceiver(V),n){if(o){let g=`http://localhost:${r}`;a.setTunnelUrl(g),console.log(`
|
|
1256
|
+
\u{1F310} Preview available at: ${g}`)}await a.reportStatus("Ready",void 0,void 0,s),console.log(`
|
|
1257
|
+
\u2705 Agent ready and waiting for commands`),console.log(` Open the frontend and select this agent to start working
|
|
1258
|
+
`),console.log(` Press Ctrl+C to disconnect
|
|
1259
|
+
`),process.on("SIGINT",async()=>{console.log(`
|
|
1260
|
+
\u{1F44B} Disconnecting...`),await a.reportStatus("Error"),process.exit(0)})}else{let g=process.env.SESSION_ID,h=process.env.PROJECT_ID;await a.reportStatus("Initiating",void 0,g,h),await j.prewarmWorkspace(),await a.reportStatus("WarmedUp",void 0,g,h),console.log("\u2705 Agent initialized, waiting for InitSession"),console.log(` Session: ${g||"none"}`),console.log(` Repo: ${process.env.REPO_URL?"configured":"NOT SET"}`)}}import.meta.url===`file://${process.argv[1]}`&&jn().catch(n=>{console.error("Fatal error:",n),process.exit(1)});export{jn as startSignalRAgent};
|