glenn-code 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.js CHANGED
@@ -30,12 +30,12 @@ ${s.join(`
30
30
  Parameters:
31
31
  - target: "backend" (default) | "frontend" | "both"
32
32
 
33
- Use target="backend" before running scaffold (required for migrations).`,{target:K.enum(["backend","frontend","both"]).default("backend").describe("Which service to stop")},async e=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=e.target||"backend";console.log(`[MCP] stop_services called (target: ${t})`);let n=t==="backend"||t==="both",a=t==="frontend"||t==="both";n&&await y.stopBackend(),a&&await y.stopFrontend();let s=5e3,r=Date.now(),o=!n,g=!a;for(;Date.now()-r<s;){let p=await y.checkHealth();if(n&&!p.api&&(o=!0),a&&!p.web&&(g=!0),o&&g)break;await new Promise(T=>setTimeout(T,500))}let k=t==="both"?"Backend and frontend":t==="backend"?"Backend":"Frontend";return{content:[{type:"text",text:o&&g?`\u2705 ${k} stopped successfully.`:`\u26A0\uFE0F ${k} stop requested but health check still shows running. Proceeding anyway.`}]}}),Le=M("start_services",`Start services. Call this AFTER running scaffold to bring services back up.
33
+ Use target="backend" before running scaffold (required for migrations).`,{target:K.enum(["backend","frontend","both"]).default("backend").describe("Which service to stop")},async e=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=e.target||"backend";console.log(`[MCP] stop_services called (target: ${t})`);let n=t==="backend"||t==="both",a=t==="frontend"||t==="both";n&&await y.stopBackend(),a&&await y.stopFrontend();let s=5e3,r=Date.now(),o=!n,f=!a;for(;Date.now()-r<s;){let p=await y.checkHealth();if(n&&!p.api&&(o=!0),a&&!p.web&&(f=!0),o&&f)break;await new Promise(T=>setTimeout(T,500))}let k=t==="both"?"Backend and frontend":t==="backend"?"Backend":"Frontend";return{content:[{type:"text",text:o&&f?`\u2705 ${k} stopped successfully.`:`\u26A0\uFE0F ${k} stop requested but health check still shows running. Proceeding anyway.`}]}}),Le=M("start_services",`Start services. Call this AFTER running scaffold to bring services back up.
34
34
 
35
35
  Parameters:
36
36
  - target: "backend" (default) | "frontend" | "both"
37
37
 
38
- Use target="backend" after running scaffold.`,{target:K.enum(["backend","frontend","both"]).default("backend").describe("Which service to start")},async e=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=e.target||"backend";console.log(`[MCP] start_services called (target: ${t})`);let n=t==="backend"||t==="both",a=t==="frontend"||t==="both";n&&await y.startBackend(),a&&await y.startFrontend();let s=await y.waitForHealth(15e3),r=!n||s.api,o=!a||s.web,g=r&&o,k=t==="both"?"Backend and frontend":t==="backend"?"Backend":"Frontend";if(!g){let f=[];return n&&!s.api&&f.push("Backend failed to start"),a&&!s.web&&f.push("Frontend failed to start"),{content:[{type:"text",text:`\u274C ${k} failed to start: ${f.join(", ")}`}]}}return{content:[{type:"text",text:`\u2705 ${k} started successfully.`}]}}),Me=M("check_service_health",`Check if services are running and healthy. Returns current status of backend API and frontend.
38
+ Use target="backend" after running scaffold.`,{target:K.enum(["backend","frontend","both"]).default("backend").describe("Which service to start")},async e=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let t=e.target||"backend";console.log(`[MCP] start_services called (target: ${t})`);let n=t==="backend"||t==="both",a=t==="frontend"||t==="both";n&&await y.startBackend(),a&&await y.startFrontend();let s=await y.waitForHealth(15e3),r=!n||s.api,o=!a||s.web,f=r&&o,k=t==="both"?"Backend and frontend":t==="backend"?"Backend":"Frontend";if(!f){let g=[];return n&&!s.api&&g.push("Backend failed to start"),a&&!s.web&&g.push("Frontend failed to start"),{content:[{type:"text",text:`\u274C ${k} failed to start: ${g.join(", ")}`}]}}return{content:[{type:"text",text:`\u2705 ${k} started successfully.`}]}}),Me=M("check_service_health",`Check if services are running and healthy. Returns current status of backend API and frontend.
39
39
  Note: This only checks if ports are responding, NOT build/type errors. Use check_backend_build or check_frontend_types for that.`,{},async()=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};let e=await y.checkHealth();return{content:[{type:"text",text:`Service Health:
40
40
  \u{1F5A5}\uFE0F Backend API: ${e.api?"\u2705 running":"\u274C not running"}
41
41
  \u{1F310} Frontend: ${e.web?"\u2705 running":"\u274C not running"}`}]}}),je=M("check_backend_build","Run dotnet build to check for C# compilation errors. Use this after fixing backend code to verify the fix works.",{},async()=>{if(!y)return{content:[{type:"text",text:"\u274C Service manager not initialized"}]};console.log("[MCP] check_backend_build called");let e=await y.checkBackendBuild();return e.success?{content:[{type:"text",text:"\u2705 Backend build succeeded - no errors"}]}:{content:[{type:"text",text:`\u274C Backend build failed:
@@ -57,7 +57,7 @@ Parameters:
57
57
  ${t.foundEndpoints.join(`
58
58
  `)}`}]}});import*as P from"fs";import*as Q from"path";var $e="/tmp";function he(e){let t=e?Q.join(e,".sdd","insights"):Q.join(process.cwd(),".sdd","insights");return{async reportInsight(n,a){P.mkdirSync(t,{recursive:!0});let s=a.type==="learning"?"learnings.jsonl":"bugs.jsonl",r=Q.join(t,s);P.appendFileSync(r,JSON.stringify(a)+`
59
59
  `,"utf-8")},async reportProgress(n,a,s){let r=Q.join($e,`agent-progress-${n}.json`),o={timestamp:new Date().toISOString(),event:a,data:s};P.appendFileSync(r,JSON.stringify(o)+`
60
- `,"utf-8")}}}function ye(e){let t=Q.join($e,`agent-questions-${e}.json`);if(!P.existsSync(t))return null;try{let n=P.readFileSync(t,"utf-8");return P.unlinkSync(t),JSON.parse(n)}catch{return null}}import*as S from"fs";import*as ae from"path";function be(e){let t=ae.join(e,".sdd","specifications");function n(){S.existsSync(t)||S.mkdirSync(t,{recursive:!0})}function a(s){return ae.join(t,`${s}.spec.md`)}return{async saveSpecification(s,r){n(),S.writeFileSync(a(s),r,"utf-8")},async getSpecification(s){n();let r=a(s);return S.existsSync(r)?S.readFileSync(r,"utf-8"):null},async listSpecifications(){return n(),S.readdirSync(t).filter(r=>r.endsWith(".spec.md")).map(r=>{let o=S.readFileSync(ae.join(t,r),"utf-8"),g=o.match(/name: "([^"]+)"/),k=o.match(/status: (\w+)/),f=o.match(/version: "([^"]+)"/);return{slug:r.replace(".spec.md",""),name:g?.[1]||r,status:k?.[1]||"unknown",version:f?.[1]||"1.0.0"}})},async deleteSpecification(s){n();let r=a(s);return S.existsSync(r)?(S.unlinkSync(r),!0):!1},async specificationExists(s){return n(),S.existsSync(a(s))}}}function ke(){return ut({name:"agent-insights",version:"1.0.0",tools:[Ne,Ue,Le,Me,je,Be]})}import{query as Ct}from"@anthropic-ai/claude-agent-sdk";var _e={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:
60
+ `,"utf-8")}}}function ye(e){let t=Q.join($e,`agent-questions-${e}.json`);if(!P.existsSync(t))return null;try{let n=P.readFileSync(t,"utf-8");return P.unlinkSync(t),JSON.parse(n)}catch{return null}}import*as S from"fs";import*as ae from"path";function be(e){let t=ae.join(e,".sdd","specifications");function n(){S.existsSync(t)||S.mkdirSync(t,{recursive:!0})}function a(s){return ae.join(t,`${s}.spec.md`)}return{async saveSpecification(s,r){n(),S.writeFileSync(a(s),r,"utf-8")},async getSpecification(s){n();let r=a(s);return S.existsSync(r)?S.readFileSync(r,"utf-8"):null},async listSpecifications(){return n(),S.readdirSync(t).filter(r=>r.endsWith(".spec.md")).map(r=>{let o=S.readFileSync(ae.join(t,r),"utf-8"),f=o.match(/name: "([^"]+)"/),k=o.match(/status: (\w+)/),g=o.match(/version: "([^"]+)"/);return{slug:r.replace(".spec.md",""),name:f?.[1]||r,status:k?.[1]||"unknown",version:g?.[1]||"1.0.0"}})},async deleteSpecification(s){n();let r=a(s);return S.existsSync(r)?(S.unlinkSync(r),!0):!1},async specificationExists(s){return n(),S.existsSync(a(s))}}}function ke(){return ut({name:"agent-insights",version:"1.0.0",tools:[Ne,Ue,Le,Me,je,Be]})}import{query as Ct}from"@anthropic-ai/claude-agent-sdk";var _e={description:"Delegate to this agent when scaffolding new entities or creating CRUD features. This agent handles entity generation using the scaffold CLI with multi-entity support.",model:"inherit",prompt:`You are a scaffolding specialist. Your ONLY job is:
61
61
  1. Check existing features (quick ls)
62
62
  2. Write ALL entities to ONE /tmp/scaffold.json (big bang, no phasing!)
63
63
  3. Run scaffold CLI ONCE
@@ -773,9 +773,9 @@ Before saving, verify your context:
773
773
  5. **Keep it scannable** - Use bullets, clear sections
774
774
  6. **Update, don't replace** - If context exists, improve it rather than starting fresh`};import*as V from"fs";import*as qe from"path";var de=class{buffer=[];flushInterval=null;sessionId=null;outputDir;flushIntervalMs;constructor(t){this.outputDir=t.outputDir||"/tmp/agent-debug",this.flushIntervalMs=t.flushIntervalMs||3e3,t.enabled&&(this.ensureOutputDir(),this.startFlushInterval())}ensureOutputDir(){V.existsSync(this.outputDir)||V.mkdirSync(this.outputDir,{recursive:!0})}startFlushInterval(){this.flushInterval=setInterval(()=>{this.flush()},this.flushIntervalMs)}setSessionId(t){this.sessionId=t}log(t){let a={timestamp:new Date().toISOString(),message:t};this.buffer.push(JSON.stringify(a,null,2)+`
775
775
  ---
776
- `)}getLogFilePath(){let n=(this.sessionId||"unknown").replace(/[^a-zA-Z0-9-]/g,"_").slice(0,50),a=new Date().toISOString().split("T")[0];return qe.join(this.outputDir,`session-${a}-${n}.log`)}flush(){if(this.buffer.length===0)return;let t=this.buffer.join("");this.buffer=[];let n=this.getLogFilePath();V.appendFileSync(n,t)}stop(){this.flushInterval&&(clearInterval(this.flushInterval),this.flushInterval=null),this.flush()}};function We(e){return e?typeof e=="boolean"?e?new de({enabled:!0}):null:e.enabled?new de(e):null:null}import{z as c}from"zod";import*as z from"fs/promises";import*as E from"path";var Ge=c.object({port:c.number(),startCommand:c.string(),buildCommand:c.string().optional(),typecheckCommand:c.string().optional(),logFile:c.string().optional(),healthEndpoint:c.string().optional(),extensions:c.array(c.string())}),pt=c.object({port:c.number(),type:c.enum(["postgres","mysql","sqlite","mongodb"])}),ft=c.object({command:c.string(),schemaPath:c.string().optional()}),gt=c.object({email:c.string().optional(),name:c.string().optional(),commitPrefix:c.string().optional()}),mt=c.object({enabled:c.boolean(),port:c.number().optional()}),Z=c.object({version:c.literal("1.0"),paths:c.object({workspace:c.string(),backend:c.string().optional(),frontend:c.string().optional(),features:c.object({backend:c.string().optional(),frontend:c.string().optional()}).optional()}),services:c.object({backend:Ge.optional(),frontend:Ge.optional(),database:pt.optional()}).optional(),scaffold:ft.optional(),git:gt.optional(),techStack:c.object({backend:c.array(c.string()).optional(),frontend:c.array(c.string()).optional(),patterns:c.array(c.string()).optional()}).optional(),tunnel:mt.optional()});function D(e){return{version:"1.0",paths:{workspace:e||process.env.WORKSPACE_DIR||"/workspace",backend:"packages/dotnet-api",frontend:"packages/backoffice-web",features:{backend:"Source/Features",frontend:"src/applications/super-admin/features"}},services:{backend:{port:5338,startCommand:"dotnet run",buildCommand:"dotnet build",typecheckCommand:"dotnet build --no-restore",logFile:"/tmp/api.log",healthEndpoint:"http://localhost:5338",extensions:[".cs",".csproj"]},frontend:{port:5173,startCommand:"npm run dev",typecheckCommand:"npx tsc -p tsconfig.app.json --noEmit",logFile:"/tmp/web.log",healthEndpoint:"http://localhost:5173",extensions:[".ts",".tsx",".json"]},database:{port:43594,type:"postgres"}},scaffold:{command:"scaffold --schema /tmp/scaffold.json --output /workspace --force --full",schemaPath:"/tmp/scaffold.json"},git:{email:"agent@dotnetmentor.se",name:"Agent",commitPrefix:"[agent]"},techStack:{backend:[".NET 9","PostgreSQL","MediatR (CQRS)","EF Core"],frontend:["React 19","TypeScript","Vite","MUI","TanStack Query"],patterns:["Vertical slices","CQRS","Soft delete","Timestamps"]},tunnel:{enabled:!0,port:5173}}}var Ye="project-config.json",He=".sdd";async function Ke(e){let{workspaceDir:t,providedConfig:n,requireConfig:a}=e;if(n)return{config:Z.parse(n),source:"provided"};let s=E.join(t,He,Ye);try{let r=await z.readFile(s,"utf-8"),o=JSON.parse(r);return{config:Z.parse(o),source:"file",configPath:s}}catch(r){r instanceof Error&&"code"in r&&r.code!=="ENOENT"&&console.warn(`[Config] Warning: Invalid project-config.json at ${s}:`,r instanceof c.ZodError?r.errors:r.message)}if(a)throw new Error(`No project configuration found at ${s} and requireConfig is true`);return{config:D(t),source:"default"}}async function Qe(e,t){let n=E.join(e,He),a=E.join(n,Ye);await z.mkdir(n,{recursive:!0});let s=Z.parse(t);return await z.writeFile(a,JSON.stringify(s,null,2),"utf-8"),a}function ue(e,t){if(t)return E.isAbsolute(t)?t:E.join(e.paths.workspace,t)}function O(e){return ue(e,e.paths.backend)}function F(e){return ue(e,e.paths.frontend)}function ee(e){let t=O(e);if(!(!t||!e.paths.features?.backend))return E.join(t,e.paths.features.backend)}function J(e){let t=F(e);if(!(!t||!e.paths.features?.frontend))return E.join(t,e.paths.features.frontend)}function Ve(e,t){if(!e.services?.backend?.extensions)return!1;let n=E.extname(t).toLowerCase();return e.services.backend.extensions.includes(n)}function ze(e,t){if(!e.services?.frontend?.extensions)return!1;let n=E.extname(t).toLowerCase();return e.services.frontend.extensions.includes(n)}function pe(e){let t=[];return e.techStack?.backend?.length&&t.push(`**Backend:** ${e.techStack.backend.join(", ")}`),e.techStack?.frontend?.length&&t.push(`**Frontend:** ${e.techStack.frontend.join(", ")}`),e.techStack?.patterns?.length&&t.push(`**Patterns:** ${e.techStack.patterns.join(", ")}`),t.join(`
776
+ `)}getLogFilePath(){let n=(this.sessionId||"unknown").replace(/[^a-zA-Z0-9-]/g,"_").slice(0,50),a=new Date().toISOString().split("T")[0];return qe.join(this.outputDir,`session-${a}-${n}.log`)}flush(){if(this.buffer.length===0)return;let t=this.buffer.join("");this.buffer=[];let n=this.getLogFilePath();V.appendFileSync(n,t)}stop(){this.flushInterval&&(clearInterval(this.flushInterval),this.flushInterval=null),this.flush()}};function We(e){return e?typeof e=="boolean"?e?new de({enabled:!0}):null:e.enabled?new de(e):null:null}import{z as c}from"zod";import*as z from"fs/promises";import*as E from"path";var Ge=c.object({port:c.number(),startCommand:c.string(),buildCommand:c.string().optional(),typecheckCommand:c.string().optional(),logFile:c.string().optional(),healthEndpoint:c.string().optional(),extensions:c.array(c.string())}),pt=c.object({port:c.number(),type:c.enum(["postgres","mysql","sqlite","mongodb"])}),gt=c.object({command:c.string(),schemaPath:c.string().optional()}),ft=c.object({email:c.string().optional(),name:c.string().optional(),commitPrefix:c.string().optional()}),mt=c.object({enabled:c.boolean(),port:c.number().optional()}),Z=c.object({version:c.literal("1.0"),paths:c.object({workspace:c.string(),backend:c.string().optional(),frontend:c.string().optional(),features:c.object({backend:c.string().optional(),frontend:c.string().optional()}).optional()}),services:c.object({backend:Ge.optional(),frontend:Ge.optional(),database:pt.optional()}).optional(),scaffold:gt.optional(),git:ft.optional(),techStack:c.object({backend:c.array(c.string()).optional(),frontend:c.array(c.string()).optional(),patterns:c.array(c.string()).optional()}).optional(),tunnel:mt.optional()});function D(e){return{version:"1.0",paths:{workspace:e||process.env.WORKSPACE_DIR||"/workspace",backend:"packages/dotnet-api",frontend:"packages/backoffice-web",features:{backend:"Source/Features",frontend:"src/applications/super-admin/features"}},services:{backend:{port:5338,startCommand:"dotnet run",buildCommand:"dotnet build",typecheckCommand:"dotnet build --no-restore",logFile:"/tmp/api.log",healthEndpoint:"http://localhost:5338",extensions:[".cs",".csproj"]},frontend:{port:5173,startCommand:"npm run dev",typecheckCommand:"npx tsc -p tsconfig.app.json --noEmit",logFile:"/tmp/web.log",healthEndpoint:"http://localhost:5173",extensions:[".ts",".tsx",".json"]},database:{port:43594,type:"postgres"}},scaffold:{command:"scaffold --schema /tmp/scaffold.json --output /workspace --force --full",schemaPath:"/tmp/scaffold.json"},git:{email:"agent@dotnetmentor.se",name:"Agent",commitPrefix:"[agent]"},techStack:{backend:[".NET 9","PostgreSQL","MediatR (CQRS)","EF Core"],frontend:["React 19","TypeScript","Vite","MUI","TanStack Query"],patterns:["Vertical slices","CQRS","Soft delete","Timestamps"]},tunnel:{enabled:!0,port:5173}}}var Ye="project-config.json",He=".sdd";async function Ke(e){let{workspaceDir:t,providedConfig:n,requireConfig:a}=e;if(n)return{config:Z.parse(n),source:"provided"};let s=E.join(t,He,Ye);try{let r=await z.readFile(s,"utf-8"),o=JSON.parse(r);return{config:Z.parse(o),source:"file",configPath:s}}catch(r){r instanceof Error&&"code"in r&&r.code!=="ENOENT"&&console.warn(`[Config] Warning: Invalid project-config.json at ${s}:`,r instanceof c.ZodError?r.errors:r.message)}if(a)throw new Error(`No project configuration found at ${s} and requireConfig is true`);return{config:D(t),source:"default"}}async function Qe(e,t){let n=E.join(e,He),a=E.join(n,Ye);await z.mkdir(n,{recursive:!0});let s=Z.parse(t);return await z.writeFile(a,JSON.stringify(s,null,2),"utf-8"),a}function ue(e,t){if(t)return E.isAbsolute(t)?t:E.join(e.paths.workspace,t)}function O(e){return ue(e,e.paths.backend)}function F(e){return ue(e,e.paths.frontend)}function ee(e){let t=O(e);if(!(!t||!e.paths.features?.backend))return E.join(t,e.paths.features.backend)}function J(e){let t=F(e);if(!(!t||!e.paths.features?.frontend))return E.join(t,e.paths.features.frontend)}function Ve(e,t){if(!e.services?.backend?.extensions)return!1;let n=E.extname(t).toLowerCase();return e.services.backend.extensions.includes(n)}function ze(e,t){if(!e.services?.frontend?.extensions)return!1;let n=E.extname(t).toLowerCase();return e.services.frontend.extensions.includes(n)}function pe(e){let t=[];return e.techStack?.backend?.length&&t.push(`**Backend:** ${e.techStack.backend.join(", ")}`),e.techStack?.frontend?.length&&t.push(`**Frontend:** ${e.techStack.frontend.join(", ")}`),e.techStack?.patterns?.length&&t.push(`**Patterns:** ${e.techStack.patterns.join(", ")}`),t.join(`
777
777
  `)}function Je(e){let t=[];return e.paths.backend&&t.push(`- Backend: ${O(e)}`),e.paths.frontend&&t.push(`- Frontend: ${F(e)}`),e.paths.features?.backend&&t.push(`- Backend Features: ${ee(e)}`),e.paths.features?.frontend&&t.push(`- Frontend Features: ${J(e)}`),t.join(`
778
- `)}function fe(e){let t=[];return e.services?.backend&&t.push(`| Backend | ${O(e)} | ${e.services.backend.port} | ${e.services.backend.logFile||"N/A"} |`),e.services?.frontend&&t.push(`| Frontend | ${F(e)} | ${e.services.frontend.port} | ${e.services.frontend.logFile||"N/A"} |`),e.services?.database&&t.push(`| Database (${e.services.database.type}) | localhost | ${e.services.database.port} | - |`),t.length===0?"":`| Service | Location | Port | Logs |
778
+ `)}function ge(e){let t=[];return e.services?.backend&&t.push(`| Backend | ${O(e)} | ${e.services.backend.port} | ${e.services.backend.logFile||"N/A"} |`),e.services?.frontend&&t.push(`| Frontend | ${F(e)} | ${e.services.frontend.port} | ${e.services.frontend.logFile||"N/A"} |`),e.services?.database&&t.push(`| Database (${e.services.database.type}) | localhost | ${e.services.database.port} | - |`),t.length===0?"":`| Service | Location | Port | Logs |
779
779
  |---------|----------|------|------|
780
780
  ${t.join(`
781
781
  `)}`}function Xe(e={}){let{debugMode:t=!1,projectPrompt:n=null,isLocal:a=!1,projectConfig:s=D(),useDefaultStack:r=!0}=e,o=n?`
@@ -785,7 +785,7 @@ ${n}
785
785
 
786
786
  ---
787
787
 
788
- `:"",g=t?`
788
+ `:"",f=t?`
789
789
  ## \u{1F6D1} DEBUG MODE ACTIVE - HIGHEST PRIORITY
790
790
 
791
791
  **You are in DEBUG mode. This instruction block OVERRIDES ALL OTHER INSTRUCTIONS.**
@@ -823,7 +823,7 @@ Then STOP. Do not continue. Do not try to fix it. Wait for user.
823
823
 
824
824
  ---
825
825
 
826
- `:"",k=ht(s),f=yt(s,a),p=_t(s,r),T=bt(r),R=r?St(s):"",B=r?wt(s,a):"",$=kt(r);return`${o}${g}# Agent Instructions
826
+ `:"",k=ht(s),g=yt(s,a),p=_t(s,r),T=bt(r),R=r?St(s):"",B=r?wt(s,a):"",$=kt(r);return`${o}${f}# Agent Instructions
827
827
 
828
828
  ## \u26D4 CRITICAL: EVERYTHING IS A KANBAN TASK
829
829
 
@@ -958,7 +958,7 @@ You know that overengineering is the enemy of good software. You are pragmatic a
958
958
 
959
959
  ${k}
960
960
 
961
- ${f}
961
+ ${g}
962
962
 
963
963
  ${p}
964
964
 
@@ -967,10 +967,10 @@ ${$}
967
967
  ${R}
968
968
 
969
969
  ${B}
970
- `}function ht(e){let t=pe(e),n=O(e),a=F(e),s=ee(e),r=J(e),o=[];s&&o.push(`- Backend: "${s}/{Entity}/"`),r&&o.push(`- Frontend: "${r}/{entity}/"`);let g=e.techStack?.patterns?.length?`
970
+ `}function ht(e){let t=pe(e),n=O(e),a=F(e),s=ee(e),r=J(e),o=[];s&&o.push(`- Backend: "${s}/{Entity}/"`),r&&o.push(`- Frontend: "${r}/{entity}/"`);let f=e.techStack?.patterns?.length?`
971
971
  **Key patterns:**
972
972
  ${e.techStack.patterns.map(p=>`- ${p}`).join(`
973
- `)}`:"",f=e.techStack?.backend?.some(p=>p.includes(".NET")||p.includes("C#"))??!1?`
973
+ `)}`:"",g=e.techStack?.backend?.some(p=>p.includes(".NET")||p.includes("C#"))??!1?`
974
974
 
975
975
  **\u26A0\uFE0F User entity exists!** ASP.NET Identity "ApplicationUser" at "Features/Users/" - don't scaffold User, just relate to it.`:"";return`<tech-stack>
976
976
  ${t}
@@ -978,7 +978,7 @@ ${o.length>0?`
978
978
  **Architecture:** Feature folders
979
979
  ${o.join(`
980
980
  `)}`:""}
981
- ${g}${f}
981
+ ${f}${g}
982
982
  </tech-stack>`}function yt(e,t){return t?`<environment>
983
983
  **LOCAL MODE - User manages their own services.**
984
984
 
@@ -1000,7 +1000,7 @@ The user is responsible for:
1000
1000
  </environment>`:`<environment>
1001
1001
  Docker container with pre-started services:
1002
1002
 
1003
- ${fe(e)}
1003
+ ${ge(e)}
1004
1004
  ${e.tunnel?.enabled?"| Tunnel | - | - | Exposes frontend to user |":""}
1005
1005
 
1006
1006
  User sees app through **Cloudflare tunnel** (live preview in browser).
@@ -1120,6 +1120,8 @@ Check \`.claude/skills/\` for project-specific patterns and conventions.`}functi
1120
1120
  **@backend** - Custom backend code (non-scaffold):
1121
1121
  - Custom queries, endpoints, lookups
1122
1122
  - Runs build + swagger generation before done
1123
+ - **Run \`./scripts/generate-swagger.sh\` after adding/changing endpoints**
1124
+ - **Run \`./scripts/generate-signalr.sh\` after adding/changing SignalR hubs**
1123
1125
  ${r?"- **Must complete before @frontend starts!**":""}
1124
1126
  `),r&&(n+=`
1125
1127
  **@frontend** - UI components:
@@ -1219,13 +1221,13 @@ You have access to **Playwright MCP** tools to verify the UI works correctly aft
1219
1221
  - Use \`browser_evaluate\` to run JavaScript when you need complex interactions
1220
1222
  - Use screenshots when you want to show the user what you see
1221
1223
  - ${t?"In local mode, the browser window will be visible to you":"In container mode, browser runs headless"}
1222
- </ui-verification>`}function Ze(e){return e&&e.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,"\uFFFD")}var vt=null;function et(){return Ze(vt)}import*as ne from"path";import*as Ce from"fs";import{fileURLToPath as Tt}from"url";var Et=ke(),ve=ne.dirname(Tt(import.meta.url)),tt=[ne.resolve(ve,"../mcps/stdio-server.js"),ne.resolve(ve,"./adapters/mcps/stdio-server.js")],te=tt.find(e=>Ce.existsSync(e))||tt[0];console.log("[MCP] Stdio server path resolved:",te);console.log("[MCP] __dirname:",ve);console.log("[MCP] File exists:",Ce.existsSync(te));async function nt(e,t={}){let{model:n,sessionId:a=null,projectId:s=null,apiUrl:r=null,callbacks:o={},debugLog:g,abortController:k,debugMode:f=!1,isLocal:p=process.env.LOCAL_MODE==="true",projectConfig:T=D(process.env.WORKSPACE_DIR||process.cwd()),useDefaultStack:R=!0}=t,B=We(g),$=new Set,Y=a||"",H,q,C,N=!1,X=!1,U=process.env.WORKSPACE_DIR||process.cwd();console.log("[MCP] Configuring agent-planning stdio server:"),console.log("[MCP] Command: node"),console.log("[MCP] Args:",[te]),console.log("[MCP] Env: API_URL=",process.env.API_URL||"http://localhost:5338",", PROJECT_ID=",s||process.env.PROJECT_ID||"",", PROJECT_KEY=",process.env.PROJECT_KEY?"***set***":"(not set)",", WORKSPACE_DIR=",U),console.log("[MCP] File exists check:",te);try{for await(let d of Ct({prompt:e,options:{abortController:k,cwd:U,resume:a??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:n,mcpServers:{"agent-insights":Et,"agent-planning":{type:"stdio",command:"node",args:[te],env:{WORKSPACE_DIR:U,API_URL:r||process.env.API_URL||process.env.MASTER_URL||"http://localhost:5338",PROJECT_ID:s||process.env.PROJECT_ID||"",PROJECT_KEY:process.env.PROJECT_KEY||""}},playwright:{type:"stdio",command:"npx",args:["@playwright/mcp@latest","--caps=vision",...p?[]:["--headless"]]}},settingSources:["project"],disallowedTools:["AskUserQuestion","TodoWrite","EnterPlanMode"],agents:R?{scaffolding:_e,debugger:ie,planning:ce,backend:Se,frontend:we,"project-context":le}:{debugger:ie,planning:ce,"project-context":le},systemPrompt:Xe({debugMode:f,projectPrompt:et(),isLocal:p,projectConfig:T,useDefaultStack:R})}}))if(!(!d.uuid||$.has(d.uuid))){switch($.add(d.uuid),B?.log(d),o.onRawMessage?.(d),d.type){case"assistant":if(d.message?.content)for(let w of d.message.content)w.type==="text"?o.onAssistantText?.(w.text):w.type==="tool_use"?o.onAssistantToolUse?.(w.name,w.input):w.type==="tool_result"&&o.onAssistantToolResult?.(w.tool_use_id,w.content);break;case"user":o.onUserMessage?.(d);break;case"result":if(d.subtype==="success")H=d.result,q=d.total_cost_usd,o.onResult?.(d.result,d.total_cost_usd);else{let w=`Agent error: ${d.subtype}`;C=w,o.onError?.(w)}X=!0;break;case"system":switch(d.subtype){case"init":Y=d.session_id,B?.setSessionId(d.session_id),o.onSystemInit?.(d.session_id);break;case"status":o.onSystemStatus?.(JSON.stringify(d));break;case"compact_boundary":break;case"hook_response":break}break;case"stream_event":o.onStreamEvent?.(d);break;case"tool_progress":o.onToolProgress?.(d);break;case"auth_status":o.onAuthStatus?.(d);break}if(X)break}}catch(d){d instanceof Error&&d.name==="AbortError"||d instanceof Error&&d.message.includes("aborted by user")?(N=!0,o.onAborted?.()):(C=d instanceof Error?d.message:String(d),o.onError?.(C))}finally{B?.stop()}return console.log("after try"),{sessionId:Y,result:H,cost:q,error:C,aborted:N}}function st(e){return`User answered the questions:
1224
+ </ui-verification>`}function Ze(e){return e&&e.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,"\uFFFD")}var vt=null;function et(){return Ze(vt)}import*as ne from"path";import*as Ce from"fs";import{fileURLToPath as Tt}from"url";var Et=ke(),ve=ne.dirname(Tt(import.meta.url)),tt=[ne.resolve(ve,"../mcps/stdio-server.js"),ne.resolve(ve,"./adapters/mcps/stdio-server.js")],te=tt.find(e=>Ce.existsSync(e))||tt[0];console.log("[MCP] Stdio server path resolved:",te);console.log("[MCP] __dirname:",ve);console.log("[MCP] File exists:",Ce.existsSync(te));async function nt(e,t={}){let{model:n,sessionId:a=null,projectId:s=null,apiUrl:r=null,callbacks:o={},debugLog:f,abortController:k,debugMode:g=!1,isLocal:p=process.env.LOCAL_MODE==="true",projectConfig:T=D(process.env.WORKSPACE_DIR||process.cwd()),useDefaultStack:R=!0}=t,B=We(f),$=new Set,Y=a||"",H,q,C,N=!1,X=!1,U=process.env.WORKSPACE_DIR||process.cwd();console.log("[MCP] Configuring agent-planning stdio server:"),console.log("[MCP] Command: node"),console.log("[MCP] Args:",[te]),console.log("[MCP] Env: API_URL=",process.env.API_URL||"http://localhost:5338",", PROJECT_ID=",s||process.env.PROJECT_ID||"",", PROJECT_KEY=",process.env.PROJECT_KEY?"***set***":"(not set)",", WORKSPACE_DIR=",U),console.log("[MCP] File exists check:",te);try{for await(let d of Ct({prompt:e,options:{abortController:k,cwd:U,resume:a??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:n,mcpServers:{"agent-insights":Et,"agent-planning":{type:"stdio",command:"node",args:[te],env:{WORKSPACE_DIR:U,API_URL:r||process.env.API_URL||process.env.MASTER_URL||"http://localhost:5338",PROJECT_ID:s||process.env.PROJECT_ID||"",PROJECT_KEY:process.env.PROJECT_KEY||""}},playwright:{type:"stdio",command:"npx",args:["@playwright/mcp@latest","--caps=vision",...p?[]:["--headless"]]}},settingSources:["project"],disallowedTools:["AskUserQuestion","TodoWrite","EnterPlanMode"],agents:R?{scaffolding:_e,debugger:ie,planning:ce,backend:Se,frontend:we,"project-context":le}:{debugger:ie,planning:ce,"project-context":le},systemPrompt:Xe({debugMode:g,projectPrompt:et(),isLocal:p,projectConfig:T,useDefaultStack:R})}}))if(!(!d.uuid||$.has(d.uuid))){switch($.add(d.uuid),B?.log(d),o.onRawMessage?.(d),d.type){case"assistant":if(d.message?.content)for(let w of d.message.content)w.type==="text"?o.onAssistantText?.(w.text):w.type==="tool_use"?o.onAssistantToolUse?.(w.name,w.input):w.type==="tool_result"&&o.onAssistantToolResult?.(w.tool_use_id,w.content);break;case"user":o.onUserMessage?.(d);break;case"result":if(d.subtype==="success")H=d.result,q=d.total_cost_usd,o.onResult?.(d.result,d.total_cost_usd);else{let w=`Agent error: ${d.subtype}`;C=w,o.onError?.(w)}X=!0;break;case"system":switch(d.subtype){case"init":Y=d.session_id,B?.setSessionId(d.session_id),o.onSystemInit?.(d.session_id);break;case"status":o.onSystemStatus?.(JSON.stringify(d));break;case"compact_boundary":break;case"hook_response":break}break;case"stream_event":o.onStreamEvent?.(d);break;case"tool_progress":o.onToolProgress?.(d);break;case"auth_status":o.onAuthStatus?.(d);break}if(X)break}}catch(d){d instanceof Error&&d.name==="AbortError"||d instanceof Error&&d.message.includes("aborted by user")?(N=!0,o.onAborted?.()):(C=d instanceof Error?d.message:String(d),o.onError?.(C))}finally{B?.stop()}return console.log("after try"),{sessionId:Y,result:H,cost:q,error:C,aborted:N}}function st(e){return`User answered the questions:
1223
1225
 
1224
1226
  ${Object.entries(e).map(([n,a])=>{let s=Array.isArray(a)?a.join(", "):a;return`${n}: ${s}`}).join(`
1225
- `)}`}import{spawn as ot,execSync as W}from"child_process";import*as b from"fs";import*as rt from"http";import*as j from"path";function at(e){let{workspaceDir:t,apiPort:n,webPort:a,onBackendError:s,onFrontendError:r,projectConfig:o=D(t)}=e,g=n??o.services?.backend?.port??5338,k=a??o.services?.frontend?.port??5173,f=O(o),p=F(o),T=o.services?.backend?.startCommand??"dotnet run",R=o.services?.backend?.buildCommand??"dotnet build",B=o.services?.backend?.typecheckCommand??"dotnet build --no-restore",$=o.services?.frontend?.startCommand??"npm run dev",Y=o.services?.frontend?.typecheckCommand??"npx tsc -p tsconfig.app.json --noEmit",H=o.services?.backend?.logFile??"/tmp/api.log",q=o.services?.frontend?.logFile??"/tmp/web.log",C=null,N=null,X=l=>new Promise(i=>{let u=setTimeout(()=>i(!1),3e3);rt.get(`http://localhost:${l}`,v=>{clearTimeout(u),i(v.statusCode!==void 0&&v.statusCode<500)}).on("error",()=>{clearTimeout(u),i(!1)})}),U=l=>{try{let i=W(`lsof -ti:${l} 2>/dev/null || true`,{encoding:"utf-8"}).trim();if(i){let u=i.split(`
1226
- `).filter(Boolean);console.log(`[Services] Killing ${u.length} process(es) on port ${l}: ${u.join(", ")}`);for(let m of u)try{process.kill(parseInt(m,10),"SIGKILL")}catch{}}}catch{try{W(`fuser -k ${l}/tcp 2>/dev/null || true`,{encoding:"utf-8"})}catch{}}},d=(l,i)=>new Promise(u=>{if(!l||!l.pid){u();return}console.log(`[Services] Stopping ${i} (PID: ${l.pid})...`);try{process.kill(-l.pid,"SIGTERM")}catch{try{l.kill("SIGTERM")}catch{}}setTimeout(u,500)}),w=async()=>{if(!f||!b.existsSync(f)){console.log("[Services] No backend found, skipping backend start");return}U(g),console.log(`[Services] Starting backend with: ${T}...`);let l=j.dirname(H);b.existsSync(l)||b.mkdirSync(l,{recursive:!0});let i=b.createWriteStream(H,{flags:"a"}),[u,...m]=T.split(" ");C=ot(u,m,{cwd:f,stdio:["ignore","pipe","pipe"],detached:!0,shell:!0});let v="",_=h=>{let L=h.toString();i.write(L);let A=(v+L).split(`
1227
- `);v=A.pop()||"";for(let x of A)x&&Pt(x)&&s&&s(x)};C.stdout?.on("data",_),C.stderr?.on("data",_),C.on("exit",h=>{i.end(),h!==0&&h!==null&&s&&s(`Process exited with code ${h}`)}),C.unref()},Te=async()=>{if(!p||!b.existsSync(j.join(p,"package.json"))){console.log("[Services] No frontend found, skipping frontend start");return}U(k),console.log(`[Services] Starting frontend with: ${$}...`);let l=j.dirname(q);b.existsSync(l)||b.mkdirSync(l,{recursive:!0});let[i,...u]=$.split(" ");N=ot(i,u,{cwd:p,stdio:["ignore",b.openSync(q,"w"),b.openSync(q,"w")],detached:!0,shell:!0}),N.unref()},Ee=async()=>{await d(C,"backend"),C=null,U(g)},Pe=async()=>{await d(N,"frontend"),N=null,U(k)},ge=async()=>{let[l,i]=await Promise.all([X(g),X(k)]);return{api:l,web:i}},Ae=async(l=15e3)=>{let i=Date.now(),u=1e3,m=0,v=null,_=null;for(console.log(`[Services] Waiting for health (timeout: ${l}ms)...`);Date.now()-i<l;){m++;let A=await ge(),x=Date.now()-i;if(A.web&&_===null&&(_=x,console.log(`[Services] \u2713 Frontend (Vite) ready after ${x}ms`)),A.api&&v===null&&(v=x,console.log(`[Services] \u2713 Backend (dotnet) ready after ${x}ms`)),console.log(`[Services] Health poll #${m} (${x}ms): api=${A.api}, web=${A.web}`),A.api&&A.web)return console.log(`[Services] \u2713 Both services healthy after ${x}ms (${m} polls)`),A;await new Promise(dt=>setTimeout(dt,u))}let h=await ge(),L=Date.now()-i;return h.web&&_===null&&console.log(`[Services] \u2713 Frontend (Vite) ready after ${L}ms`),h.api&&v===null&&console.log(`[Services] \u2713 Backend (dotnet) ready after ${L}ms`),console.log(`[Services] \u26A0 Health timeout after ${L}ms: api=${h.api}, web=${h.web}`),h},ct=async l=>{console.log(`[Services] Restarting ${l}...`);let i={success:!0},u=l==="backend"||l==="both",m=l==="frontend"||l==="both";if(u&&await Ee(),m&&await Pe(),u&&f&&b.existsSync(f)){console.log(`[Services] Building backend with: ${R}...`);try{W(R,{cwd:f,stdio:"pipe",timeout:12e4,encoding:"utf-8"}),console.log("[Services] \u2713 Backend build succeeded")}catch(_){let h=_;i.success=!1,i.backendError=h.stderr||h.stdout||"Build failed",console.error("[Services] \u2717 Backend build failed")}}if(m&&p&&b.existsSync(j.join(p,"package.json"))){console.log(`[Services] Type-checking frontend with: ${Y}...`);try{W(Y,{cwd:p,stdio:"pipe",timeout:6e4,encoding:"utf-8"}),console.log("[Services] \u2713 Frontend types OK")}catch(_){let h=_;i.success=!1,i.frontendError=h.stderr||h.stdout||"Type check failed",console.error("[Services] \u2717 Frontend type check failed")}}console.log(`[Services] Starting ${l}...`),u&&await w(),m&&await Te();let v=await Ae(1e4);if(u&&!v.api){i.success=!1;let _=await Ie("backend",20),h=_.length>0?`
1227
+ `)}`}import{spawn as ot,execSync as W}from"child_process";import*as b from"fs";import*as rt from"http";import*as j from"path";function at(e){let{workspaceDir:t,apiPort:n,webPort:a,onBackendError:s,onFrontendError:r,projectConfig:o=D(t)}=e,f=n??o.services?.backend?.port??5338,k=a??o.services?.frontend?.port??5173,g=O(o),p=F(o),T=o.services?.backend?.startCommand??"dotnet run",R=o.services?.backend?.buildCommand??"dotnet build",B=o.services?.backend?.typecheckCommand??"dotnet build --no-restore",$=o.services?.frontend?.startCommand??"npm run dev",Y=o.services?.frontend?.typecheckCommand??"npx tsc -p tsconfig.app.json --noEmit",H=o.services?.backend?.logFile??"/tmp/api.log",q=o.services?.frontend?.logFile??"/tmp/web.log",C=null,N=null,X=l=>new Promise(i=>{let u=setTimeout(()=>i(!1),3e3);rt.get(`http://localhost:${l}`,v=>{clearTimeout(u),i(v.statusCode!==void 0&&v.statusCode<500)}).on("error",()=>{clearTimeout(u),i(!1)})}),U=l=>{try{let i=W(`lsof -ti:${l} 2>/dev/null || true`,{encoding:"utf-8"}).trim();if(i){let u=i.split(`
1228
+ `).filter(Boolean);console.log(`[Services] Killing ${u.length} process(es) on port ${l}: ${u.join(", ")}`);for(let m of u)try{process.kill(parseInt(m,10),"SIGKILL")}catch{}}}catch{try{W(`fuser -k ${l}/tcp 2>/dev/null || true`,{encoding:"utf-8"})}catch{}}},d=(l,i)=>new Promise(u=>{if(!l||!l.pid){u();return}console.log(`[Services] Stopping ${i} (PID: ${l.pid})...`);try{process.kill(-l.pid,"SIGTERM")}catch{try{l.kill("SIGTERM")}catch{}}setTimeout(u,500)}),w=async()=>{if(!g||!b.existsSync(g)){console.log("[Services] No backend found, skipping backend start");return}U(f),console.log(`[Services] Starting backend with: ${T}...`);let l=j.dirname(H);b.existsSync(l)||b.mkdirSync(l,{recursive:!0});let i=b.createWriteStream(H,{flags:"a"}),[u,...m]=T.split(" ");C=ot(u,m,{cwd:g,stdio:["ignore","pipe","pipe"],detached:!0,shell:!0});let v="",_=h=>{let L=h.toString();i.write(L);let A=(v+L).split(`
1229
+ `);v=A.pop()||"";for(let x of A)x&&Pt(x)&&s&&s(x)};C.stdout?.on("data",_),C.stderr?.on("data",_),C.on("exit",h=>{i.end(),h!==0&&h!==null&&s&&s(`Process exited with code ${h}`)}),C.unref()},Te=async()=>{if(!p||!b.existsSync(j.join(p,"package.json"))){console.log("[Services] No frontend found, skipping frontend start");return}U(k),console.log(`[Services] Starting frontend with: ${$}...`);let l=j.dirname(q);b.existsSync(l)||b.mkdirSync(l,{recursive:!0});let[i,...u]=$.split(" ");N=ot(i,u,{cwd:p,stdio:["ignore",b.openSync(q,"w"),b.openSync(q,"w")],detached:!0,shell:!0}),N.unref()},Ee=async()=>{await d(C,"backend"),C=null,U(f)},Pe=async()=>{await d(N,"frontend"),N=null,U(k)},fe=async()=>{let[l,i]=await Promise.all([X(f),X(k)]);return{api:l,web:i}},Ae=async(l=15e3)=>{let i=Date.now(),u=1e3,m=0,v=null,_=null;for(console.log(`[Services] Waiting for health (timeout: ${l}ms)...`);Date.now()-i<l;){m++;let A=await fe(),x=Date.now()-i;if(A.web&&_===null&&(_=x,console.log(`[Services] \u2713 Frontend (Vite) ready after ${x}ms`)),A.api&&v===null&&(v=x,console.log(`[Services] \u2713 Backend (dotnet) ready after ${x}ms`)),console.log(`[Services] Health poll #${m} (${x}ms): api=${A.api}, web=${A.web}`),A.api&&A.web)return console.log(`[Services] \u2713 Both services healthy after ${x}ms (${m} polls)`),A;await new Promise(dt=>setTimeout(dt,u))}let h=await fe(),L=Date.now()-i;return h.web&&_===null&&console.log(`[Services] \u2713 Frontend (Vite) ready after ${L}ms`),h.api&&v===null&&console.log(`[Services] \u2713 Backend (dotnet) ready after ${L}ms`),console.log(`[Services] \u26A0 Health timeout after ${L}ms: api=${h.api}, web=${h.web}`),h},ct=async l=>{console.log(`[Services] Restarting ${l}...`);let i={success:!0},u=l==="backend"||l==="both",m=l==="frontend"||l==="both";if(u&&await Ee(),m&&await Pe(),u&&g&&b.existsSync(g)){console.log(`[Services] Building backend with: ${R}...`);try{W(R,{cwd:g,stdio:"pipe",timeout:12e4,encoding:"utf-8"}),console.log("[Services] \u2713 Backend build succeeded")}catch(_){let h=_;i.success=!1,i.backendError=h.stderr||h.stdout||"Build failed",console.error("[Services] \u2717 Backend build failed")}}if(m&&p&&b.existsSync(j.join(p,"package.json"))){console.log(`[Services] Type-checking frontend with: ${Y}...`);try{W(Y,{cwd:p,stdio:"pipe",timeout:6e4,encoding:"utf-8"}),console.log("[Services] \u2713 Frontend types OK")}catch(_){let h=_;i.success=!1,i.frontendError=h.stderr||h.stdout||"Type check failed",console.error("[Services] \u2717 Frontend type check failed")}}console.log(`[Services] Starting ${l}...`),u&&await w(),m&&await Te();let v=await Ae(1e4);if(u&&!v.api){i.success=!1;let _=await Ie("backend",20),h=_.length>0?`
1228
1230
  Recent logs:
1229
1231
  ${_.join(`
1230
- `)}`:"";i.backendError||(i.backendError=`Backend failed to start.${h}`)}return m&&!v.web&&(i.success=!1,i.frontendError||(i.frontendError="Frontend failed to start")),i},xe=async()=>{if(!f||!b.existsSync(f))return{success:!0};try{return W(B,{cwd:f,stdio:"pipe",timeout:12e4,encoding:"utf-8"}),{success:!0}}catch(l){let i=l;return{success:!1,errors:i.stdout||i.stderr||"Build failed"}}},Re=async()=>{if(!p||!b.existsSync(j.join(p,"package.json")))return{success:!0};try{return W(Y,{cwd:p,stdio:"pipe",timeout:6e4,encoding:"utf-8"}),{success:!0}}catch(l){let i=l;return{success:!1,errors:i.stdout||i.stderr||"Type check failed"}}},lt=async()=>{let[l,i]=await Promise.all([xe(),Re()]);return{backend:l,frontend:i}},Ie=async(l,i)=>{let u=l==="backend"?H:q;if(!b.existsSync(u))return[`Log file not found: ${u}`];try{return W(`tail -n ${i} ${u}`,{encoding:"utf-8"}).split(`
1231
- `).filter(Boolean)}catch(m){return[`Error reading logs: ${m instanceof Error?m.message:String(m)}`]}};return{startBackend:w,startFrontend:Te,stopBackend:Ee,stopFrontend:Pe,restartServices:ct,checkHealth:ge,waitForHealth:Ae,getProcesses:()=>({backend:C,frontend:N}),checkBackendBuild:xe,checkFrontendTypes:Re,runTypeChecks:lt,tailLogs:Ie,checkSwaggerEndpoints:async l=>{let i=j.join(t,"swagger.json");if(!b.existsSync(i))return{foundEndpoints:["Error: swagger.json not found"],totalEndpoints:0};try{let u=JSON.parse(b.readFileSync(i,"utf-8")),m=Object.keys(u.paths||{}),v=[];for(let _ of m)if(_.toLowerCase().includes(l.toLowerCase())){let h=Object.keys(u.paths[_]);for(let L of h)v.push(`${L.toUpperCase()} ${_}`)}return{foundEndpoints:v,totalEndpoints:m.length}}catch(u){return{foundEndpoints:[`Error parsing swagger.json: ${u instanceof Error?u.message:String(u)}`],totalEndpoints:0}}}}}function Pt(e){let t=e.toLowerCase();return t.includes("exception")||t.includes("fail:")||t.includes("[err]")||t.includes("[error]")}import{execSync as G}from"child_process";function it(e){let t=async()=>{try{return G("git status --porcelain",{cwd:e,encoding:"utf-8"}).trim().length>0}catch{return!1}},n=async()=>{try{return G("git branch --show-current",{cwd:e,encoding:"utf-8"}).trim()}catch{return"unknown"}};return{hasChanges:t,commitAndPush:async(s,r)=>{try{if(!await t())return{success:!0,noChanges:!0};let o=s.length>60?s.substring(0,60)+"...":s,k=`${r?"[agent] (partial)":"[agent]"} ${o}`;G("git add -A",{cwd:e}),G(`git commit -m "${k.replace(/"/g,'\\"')}"`,{cwd:e,encoding:"utf-8"});let f=G("git rev-parse --short HEAD",{cwd:e,encoding:"utf-8"}).trim();try{G("git push",{cwd:e,stdio:"pipe"})}catch(p){let T=p instanceof Error?p.message:String(p);if(T.includes("no upstream branch")){let R=await n();G(`git push --set-upstream origin ${R}`,{cwd:e,stdio:"pipe"})}else return console.error("[Git] Push failed:",T),{success:!0,commitHash:f,error:`Committed but push failed: ${T}`}}return{success:!0,commitHash:f}}catch(o){let g=o instanceof Error?o.message:String(o);return console.error("[Git] Error:",g),{success:!1,error:g}}},getCurrentBranch:n}}export{Z as ProjectConfigSchema,be as createFilePlanningTransport,he as createFileTransport,it as createLocalGitManager,at as createLocalServiceManager,ke as createMcpServer,st as formatAnswersAsPrompt,ee as getBackendFeaturesPath,O as getBackendPath,D as getDefaultConfig,J as getFrontendFeaturesPath,F as getFrontendPath,Je as getPathsDescription,fe as getServicesDescription,pe as getTechStackDescription,Ve as isBackendFile,ze as isFrontendFile,Ke as loadProjectConfig,ye as readPendingQuestions,ue as resolveConfigPath,nt as runAgent,Qe as saveProjectConfig,Oe as setPlanningTransport,Fe as setServiceManager};
1232
+ `)}`:"";i.backendError||(i.backendError=`Backend failed to start.${h}`)}return m&&!v.web&&(i.success=!1,i.frontendError||(i.frontendError="Frontend failed to start")),i},xe=async()=>{if(!g||!b.existsSync(g))return{success:!0};try{return W(B,{cwd:g,stdio:"pipe",timeout:12e4,encoding:"utf-8"}),{success:!0}}catch(l){let i=l;return{success:!1,errors:i.stdout||i.stderr||"Build failed"}}},Re=async()=>{if(!p||!b.existsSync(j.join(p,"package.json")))return{success:!0};try{return W(Y,{cwd:p,stdio:"pipe",timeout:6e4,encoding:"utf-8"}),{success:!0}}catch(l){let i=l;return{success:!1,errors:i.stdout||i.stderr||"Type check failed"}}},lt=async()=>{let[l,i]=await Promise.all([xe(),Re()]);return{backend:l,frontend:i}},Ie=async(l,i)=>{let u=l==="backend"?H:q;if(!b.existsSync(u))return[`Log file not found: ${u}`];try{return W(`tail -n ${i} ${u}`,{encoding:"utf-8"}).split(`
1233
+ `).filter(Boolean)}catch(m){return[`Error reading logs: ${m instanceof Error?m.message:String(m)}`]}};return{startBackend:w,startFrontend:Te,stopBackend:Ee,stopFrontend:Pe,restartServices:ct,checkHealth:fe,waitForHealth:Ae,getProcesses:()=>({backend:C,frontend:N}),checkBackendBuild:xe,checkFrontendTypes:Re,runTypeChecks:lt,tailLogs:Ie,checkSwaggerEndpoints:async l=>{let i=j.join(t,"swagger.json");if(!b.existsSync(i))return{foundEndpoints:["Error: swagger.json not found"],totalEndpoints:0};try{let u=JSON.parse(b.readFileSync(i,"utf-8")),m=Object.keys(u.paths||{}),v=[];for(let _ of m)if(_.toLowerCase().includes(l.toLowerCase())){let h=Object.keys(u.paths[_]);for(let L of h)v.push(`${L.toUpperCase()} ${_}`)}return{foundEndpoints:v,totalEndpoints:m.length}}catch(u){return{foundEndpoints:[`Error parsing swagger.json: ${u instanceof Error?u.message:String(u)}`],totalEndpoints:0}}}}}function Pt(e){let t=e.toLowerCase();return t.includes("exception")||t.includes("fail:")||t.includes("[err]")||t.includes("[error]")}import{execSync as G}from"child_process";function it(e){let t=async()=>{try{return G("git status --porcelain",{cwd:e,encoding:"utf-8"}).trim().length>0}catch{return!1}},n=async()=>{try{return G("git branch --show-current",{cwd:e,encoding:"utf-8"}).trim()}catch{return"unknown"}};return{hasChanges:t,commitAndPush:async(s,r)=>{try{if(!await t())return{success:!0,noChanges:!0};let o=s.length>60?s.substring(0,60)+"...":s,k=`${r?"[agent] (partial)":"[agent]"} ${o}`;G("git add -A",{cwd:e}),G(`git commit -m "${k.replace(/"/g,'\\"')}"`,{cwd:e,encoding:"utf-8"});let g=G("git rev-parse --short HEAD",{cwd:e,encoding:"utf-8"}).trim();try{G("git push",{cwd:e,stdio:"pipe"})}catch(p){let T=p instanceof Error?p.message:String(p);if(T.includes("no upstream branch")){let R=await n();G(`git push --set-upstream origin ${R}`,{cwd:e,stdio:"pipe"})}else return console.error("[Git] Push failed:",T),{success:!0,commitHash:g,error:`Committed but push failed: ${T}`}}return{success:!0,commitHash:g}}catch(o){let f=o instanceof Error?o.message:String(o);return console.error("[Git] Error:",f),{success:!1,error:f}}},getCurrentBranch:n}}export{Z as ProjectConfigSchema,be as createFilePlanningTransport,he as createFileTransport,it as createLocalGitManager,at as createLocalServiceManager,ke as createMcpServer,st as formatAnswersAsPrompt,ee as getBackendFeaturesPath,O as getBackendPath,D as getDefaultConfig,J as getFrontendFeaturesPath,F as getFrontendPath,Je as getPathsDescription,ge as getServicesDescription,pe as getTechStackDescription,Ve as isBackendFile,ze as isFrontendFile,Ke as loadProjectConfig,ye as readPendingQuestions,ue as resolveConfigPath,nt as runAgent,Qe as saveProjectConfig,Oe as setPlanningTransport,Fe as setServiceManager};
package/dist/index.js CHANGED
@@ -1117,6 +1117,8 @@ Check \`.claude/skills/\` for project-specific patterns and conventions.`}functi
1117
1117
  **@backend** - Custom backend code (non-scaffold):
1118
1118
  - Custom queries, endpoints, lookups
1119
1119
  - Runs build + swagger generation before done
1120
+ - **Run \`./scripts/generate-swagger.sh\` after adding/changing endpoints**
1121
+ - **Run \`./scripts/generate-signalr.sh\` after adding/changing SignalR hubs**
1120
1122
  ${r?"- **Must complete before @frontend starts!**":""}
1121
1123
  `),r&&(n+=`
1122
1124
  **@frontend** - UI components:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glenn-code",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Glenn Code - Connect your local development environment to DNM Lab",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",