@polymorphism-tech/morph-spec 4.11.1 → 4.13.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.
Files changed (317) hide show
  1. package/README.md +21 -29
  2. package/bin/morph-spec.js +41 -24
  3. package/bin/task-manager.js +962 -1341
  4. package/bin/validate.js +1 -1
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +10 -11
  7. package/docs/QUICKSTART.md +15 -15
  8. package/framework/CLAUDE.md +5 -2
  9. package/framework/agents/README.md +3 -4
  10. package/framework/agents/ai-agents/ai-system-architect.md +6 -10
  11. package/framework/agents/architecture/seo-growth-hacker.md +166 -326
  12. package/framework/agents/architecture/standards-architect.md +21 -31
  13. package/framework/agents/backend/dotnet-senior.md +160 -86
  14. package/framework/agents/backend/ef-modeler.md +2 -6
  15. package/framework/agents/backend/hangfire-orchestrator.md +8 -9
  16. package/framework/agents/backend/ms-agent-expert.md +1 -1
  17. package/framework/agents/frontend/nextjs-expert.md +198 -133
  18. package/framework/agents/frontend/ui-ux-designer.md +226 -267
  19. package/framework/agents/infrastructure/infra-architect.md +191 -27
  20. package/framework/agents/integrations/clerk-auth.md +19 -26
  21. package/framework/agents/integrations/hangfire-integration.md +1 -4
  22. package/framework/agents.json +226 -1454
  23. package/framework/commands/morph-apply.md +4 -4
  24. package/framework/commands/morph-preflight.md +4 -26
  25. package/framework/commands/morph-proposal.md +19 -19
  26. package/framework/commands/morph-status.md +2 -2
  27. package/framework/commands/morph-troubleshoot.md +6 -13
  28. package/framework/hooks/README.md +1 -6
  29. package/framework/hooks/claude-code/post-tool-use/agent-complete.js +122 -0
  30. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +4 -6
  31. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +12 -7
  32. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +2 -2
  33. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +2 -2
  34. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +5 -10
  35. package/framework/hooks/claude-code/session-start/inject-morph-context.js +63 -30
  36. package/framework/hooks/claude-code/session-start/resume-context.js +77 -0
  37. package/framework/hooks/claude-code/statusline.py +4 -5
  38. package/framework/hooks/claude-code/stop/validate-completion.js +4 -4
  39. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +8 -8
  40. package/framework/hooks/shared/compact-restore.js +28 -5
  41. package/framework/hooks/shared/dispatch-helpers.js +17 -0
  42. package/framework/hooks/shared/phase-utils.js +4 -8
  43. package/framework/hooks/shared/state-reader.js +34 -3
  44. package/framework/phases.json +7 -45
  45. package/framework/rules/ai-agents-standards.md +15 -0
  46. package/framework/rules/asaas-standards.md +13 -0
  47. package/framework/rules/clerk-standards.md +13 -0
  48. package/framework/rules/csharp-standards.md +8 -4
  49. package/framework/rules/data-standards.md +18 -0
  50. package/framework/rules/dotnet-templates.md +87 -0
  51. package/framework/rules/frontend-standards.md +13 -13
  52. package/framework/rules/hangfire-standards.md +13 -0
  53. package/framework/rules/infrastructure-standards.md +27 -16
  54. package/framework/rules/morph-workflow.md +73 -13
  55. package/framework/rules/nextjs-standards.md +1 -0
  56. package/framework/rules/nextjs-templates.md +40 -0
  57. package/framework/rules/resend-standards.md +12 -0
  58. package/framework/rules/supabase-standards.md +29 -0
  59. package/framework/skills/README.md +6 -8
  60. package/framework/skills/level-0-meta/mcp-registry.json +21 -99
  61. package/framework/{standards/ai-agents/team-orchestration.md → skills/level-0-meta/morph-agent-teams/SKILL.md} +472 -479
  62. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +28 -1
  63. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
  64. package/framework/skills/level-0-meta/morph-code-review/SKILL.md +1 -5
  65. package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +1 -1
  66. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +23 -41
  67. package/framework/skills/level-0-meta/morph-init/SKILL.md +18 -14
  68. package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +225 -0
  69. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +20 -0
  70. package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +4 -8
  71. package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +15 -59
  72. package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +7 -19
  73. package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +8 -8
  74. package/framework/skills/level-1-workflows/README.md +1 -1
  75. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +49 -50
  76. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +58 -124
  77. package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +5 -50
  78. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +1 -1
  79. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +127 -159
  80. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +4 -3
  81. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +2 -2
  82. package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +1 -1
  83. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +17 -13
  84. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +22 -40
  85. package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -2
  86. package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +5 -5
  87. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +275 -61
  88. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
  89. package/framework/squad-templates/backend-only.json +6 -6
  90. package/framework/squad-templates/frontend-only.json +17 -8
  91. package/framework/squad-templates/full-stack.json +29 -24
  92. package/framework/standards/STANDARDS.json +1021 -2338
  93. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +200 -0
  94. package/framework/standards/backend/dotnet/result-pattern.md +213 -0
  95. package/framework/standards/backend/dotnet/scalar-ui.md +172 -0
  96. package/framework/standards/backend/dotnet/scrutor.md +158 -0
  97. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +274 -0
  98. package/framework/standards/frontend/nextjs/motion-patterns.md +1 -1
  99. package/framework/standards/infrastructure/supabase/mcp-setup.md +83 -193
  100. package/framework/standards/integration/mcp/mcp-tools.md +91 -77
  101. package/framework/templates/IDE-SNIPPETS.md +2 -6
  102. package/framework/templates/README.md +709 -814
  103. package/framework/templates/REGISTRY.json +223 -762
  104. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -0
  105. package/framework/templates/code/dotnet/abstractions/error.cs +60 -0
  106. package/framework/templates/code/dotnet/abstractions/handler.cs +12 -0
  107. package/framework/templates/code/dotnet/abstractions/result.cs +74 -0
  108. package/framework/templates/code/dotnet/backend/repository.cs +148 -141
  109. package/framework/templates/code/dotnet/constants/api-tags.cs +6 -0
  110. package/framework/templates/code/dotnet/exceptions/exception-handler.cs +32 -0
  111. package/framework/templates/code/dotnet/extensions/database-extensions.cs +20 -0
  112. package/framework/templates/code/dotnet/extensions/handler-registration.cs +38 -0
  113. package/framework/templates/code/dotnet/extensions/health-checks.cs +24 -0
  114. package/framework/templates/code/dotnet/extensions/map-endpoints.cs +35 -0
  115. package/framework/templates/code/dotnet/extensions/result-extensions.cs +29 -0
  116. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -0
  117. package/framework/templates/code/dotnet/feature/entity.cs.hbs +8 -0
  118. package/framework/templates/code/dotnet/feature/errors.cs.hbs +8 -0
  119. package/framework/templates/code/dotnet/feature/handler.cs.hbs +24 -0
  120. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -0
  121. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -0
  122. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -0
  123. package/framework/templates/code/dotnet/jobs/job.cs +21 -17
  124. package/framework/templates/code/dotnet/pipelines/logging-decorator.cs +31 -0
  125. package/framework/templates/code/dotnet/pipelines/validation-decorator.cs +97 -0
  126. package/framework/templates/code/dotnet/test.cs +192 -106
  127. package/framework/templates/context/ARCHITECTURE.md +89 -0
  128. package/framework/templates/context/CONTEXT-FEATURE.md +2 -2
  129. package/framework/templates/context/CONTEXT.md +180 -181
  130. package/framework/templates/context/design-system.md +342 -211
  131. package/framework/templates/docs/onboarding.md +2 -3
  132. package/framework/templates/docs/proposal.md +171 -182
  133. package/framework/templates/docs/spec.md +1 -3
  134. package/framework/templates/docs/ui-components.md +0 -72
  135. package/framework/templates/examples/design-system-examples.md +4 -4
  136. package/framework/templates/feature/decisions.md +0 -66
  137. package/framework/templates/feature/recap.md +1 -2
  138. package/framework/templates/infrastructure/github/README.md +56 -593
  139. package/framework/templates/integrations/clerk-config.cs +0 -41
  140. package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +2 -2
  141. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -2
  142. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +83 -126
  143. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +2 -2
  144. package/framework/templates/project/validate.js +2 -4
  145. package/framework/templates/ui/design-system.css +1 -1
  146. package/framework/workflows/README.md +8 -8
  147. package/framework/workflows/configs/fast-track.json +51 -0
  148. package/framework/workflows/configs/full.json +68 -0
  149. package/framework/workflows/configs/standard.json +7 -3
  150. package/framework/workflows/docs/STORY-DRIVEN-DEVELOPMENT.md +1 -1
  151. package/framework/workflows/docs/enforcement-pipeline.md +18 -19
  152. package/framework/workflows/docs/full-morph.md +69 -76
  153. package/framework/workflows/docs/standard.md +49 -44
  154. package/framework/workflows/docs/ui-refresh.md +1 -1
  155. package/package.json +3 -3
  156. package/src/commands/agents/dispatch-agents.js +55 -116
  157. package/src/commands/context/context-refresh.js +79 -0
  158. package/src/commands/mcp/mcp-setup.js +28 -6
  159. package/src/commands/phase/phase-reset.js +1 -1
  160. package/src/commands/project/doctor.js +10 -32
  161. package/src/commands/project/init.js +32 -1
  162. package/src/commands/project/status.js +6 -12
  163. package/src/commands/scaffold/scaffold.js +131 -0
  164. package/src/commands/scope/escalate.js +1 -1
  165. package/src/commands/state/advance-phase.js +15 -29
  166. package/src/commands/state/state.js +20 -5
  167. package/src/commands/tasks/task.js +0 -30
  168. package/src/commands/validation/validate-feature.js +3 -10
  169. package/src/core/paths/output-schema.js +12 -13
  170. package/src/core/state/phase-state-machine.js +211 -217
  171. package/src/core/state/state-manager.js +9 -9
  172. package/src/lib/agents/agents-manager.js +2 -2
  173. package/src/lib/agents/output-collector.js +1 -2
  174. package/src/lib/detectors/design-system-detector.js +4 -4
  175. package/src/lib/detectors/index.js +1 -11
  176. package/src/lib/generators/architecture-scanner.js +640 -0
  177. package/src/lib/generators/context-generator.js +3 -3
  178. package/src/lib/generators/design-system-generator.js +508 -298
  179. package/src/lib/generators/index.js +12 -10
  180. package/src/lib/generators/metadata-extractor.js +386 -387
  181. package/src/lib/generators/recap-generator.js +4 -4
  182. package/src/lib/generators/scaffold-generator.js +317 -0
  183. package/src/lib/generators/settings-generator.js +0 -1
  184. package/src/lib/phase-chain/phase-validator.js +64 -42
  185. package/src/lib/stack/stack-profile.js +2 -2
  186. package/src/lib/standards/digest-builder.js +72 -285
  187. package/src/lib/tasks/task-parser.js +11 -5
  188. package/src/lib/validators/architecture/architecture-validator.js +259 -387
  189. package/src/lib/validators/content/content-validator.js +287 -287
  190. package/src/lib/validators/css/css-validator.js +340 -352
  191. package/src/lib/validators/design-system/design-system-validator.js +11 -5
  192. package/src/lib/validators/packages/package-validator.js +347 -360
  193. package/src/lib/validators/shared/issue-counter.js +14 -18
  194. package/src/lib/validators/validation-runner.js +3 -19
  195. package/src/scripts/setup-infra.js +1 -1
  196. package/src/utils/agents-installer.js +24 -3
  197. package/src/utils/file-copier.js +1 -0
  198. package/src/utils/hooks-installer.js +21 -19
  199. package/src/utils/skills-installer.js +178 -178
  200. package/framework/agents/architecture/prompt-engineer.md +0 -195
  201. package/framework/agents/backend/api-designer.md +0 -106
  202. package/framework/agents/frontend/blazor-builder.md +0 -229
  203. package/framework/agents/frontend/nextjs-ui-specialist.md +0 -218
  204. package/framework/agents/infrastructure/azure-architect.md +0 -150
  205. package/framework/agents/infrastructure/azure-deploy-specialist.md +0 -707
  206. package/framework/agents/infrastructure/bicep-architect.md +0 -132
  207. package/framework/agents/infrastructure/container-specialist.md +0 -137
  208. package/framework/agents/infrastructure/devops-engineer.md +0 -126
  209. package/framework/agents/integrations/azure-identity.md +0 -148
  210. package/framework/agents/quality/code-analyzer.md +0 -239
  211. package/framework/agents/quality/testing-specialist.md +0 -130
  212. package/framework/hooks/claude-code/notification/approval-reminder.js +0 -57
  213. package/framework/hooks/claude-code/post-tool-use/context-refresh.js +0 -109
  214. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +0 -78
  215. package/framework/index/troubleshooting-index.json +0 -184
  216. package/framework/rules/testing-standards.md +0 -14
  217. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +0 -230
  218. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +0 -70
  219. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +0 -256
  220. package/framework/skills/level-1-workflows/morph-phase-clarify/references/clarifications-example.md +0 -117
  221. package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +0 -269
  222. package/framework/standards/ai-agents/blazor-ui.md +0 -364
  223. package/framework/standards/architecture/ddd/aggregates.md +0 -120
  224. package/framework/standards/architecture/ddd/bounded-contexts.md +0 -105
  225. package/framework/standards/architecture/ddd/complexity-levels.md +0 -108
  226. package/framework/standards/architecture/ddd/entities.md +0 -99
  227. package/framework/standards/architecture/ddd/ubiquitous-language.md +0 -58
  228. package/framework/standards/architecture/ddd/value-objects.md +0 -124
  229. package/framework/standards/backend/api/minimal-api.md +0 -494
  230. package/framework/standards/backend/api/rest.md +0 -492
  231. package/framework/standards/backend/authentication/passkeys.md +0 -428
  232. package/framework/standards/backend/database/ef-core.md +0 -199
  233. package/framework/standards/backend/database/migrations.md +0 -393
  234. package/framework/standards/backend/database/repository-patterns.md +0 -528
  235. package/framework/standards/backend/dotnet/async.md +0 -366
  236. package/framework/standards/backend/dotnet/core.md +0 -117
  237. package/framework/standards/backend/dotnet/di.md +0 -439
  238. package/framework/standards/backend/dotnet/program-cs-checklist.md +0 -92
  239. package/framework/standards/context/analytics.md +0 -96
  240. package/framework/standards/context/bundles.md +0 -110
  241. package/framework/standards/context/priming.md +0 -78
  242. package/framework/standards/core/architecture.md +0 -185
  243. package/framework/standards/core/coding.md +0 -214
  244. package/framework/standards/core/git-branching-strategy.md +0 -403
  245. package/framework/standards/core/git.md +0 -185
  246. package/framework/standards/core/testing.md +0 -295
  247. package/framework/standards/data/nosql/blob-storage.md +0 -102
  248. package/framework/standards/data/vector-search/azure-ai-search.md +0 -121
  249. package/framework/standards/frontend/blazor/design-checklist.md +0 -222
  250. package/framework/standards/frontend/blazor/fluent-ui-setup.md +0 -595
  251. package/framework/standards/frontend/blazor/fluent-ui.md +0 -137
  252. package/framework/standards/frontend/blazor/html-conversion.md +0 -184
  253. package/framework/standards/frontend/blazor/lifecycle.md +0 -195
  254. package/framework/standards/frontend/blazor/pitfalls.md +0 -198
  255. package/framework/standards/frontend/blazor/state.md +0 -191
  256. package/framework/standards/frontend/design-system/aesthetic-direction.md +0 -213
  257. package/framework/standards/infrastructure/azure/azure.md +0 -624
  258. package/framework/standards/infrastructure/azure/bicep/bicep-patterns.md +0 -422
  259. package/framework/standards/infrastructure/azure/devops/azure-devops-setup.md +0 -516
  260. package/framework/standards/infrastructure/azure/devops/local-development.md +0 -520
  261. package/framework/standards/infrastructure/azure/services/functions.md +0 -486
  262. package/framework/standards/infrastructure/azure/services/service-bus.md +0 -459
  263. package/framework/standards/infrastructure/azure/services/storage.md +0 -407
  264. package/framework/standards/infrastructure/vercel/vercel-database.md +0 -106
  265. package/framework/standards/integration/api/graphql.md +0 -91
  266. package/framework/standards/integration/api/grpc.md +0 -114
  267. package/framework/standards/integration/api/rest-design.md +0 -95
  268. package/framework/standards/integration/event-driven/cqrs.md +0 -101
  269. package/framework/standards/integration/event-driven/event-sourcing.md +0 -124
  270. package/framework/standards/integration/event-driven/service-bus.md +0 -95
  271. package/framework/standards/observability/logging.md +0 -131
  272. package/framework/standards/observability/metrics.md +0 -121
  273. package/framework/standards/observability/monitoring.md +0 -114
  274. package/framework/standards/observability/tracing.md +0 -132
  275. package/framework/standards/workflows/parallel-execution.md +0 -112
  276. package/framework/standards/workflows/thread-management.md +0 -113
  277. package/framework/templates/code/dotnet/backend/service.cs +0 -139
  278. package/framework/templates/code/dotnet/contracts/Commands.cs +0 -74
  279. package/framework/templates/code/dotnet/contracts/Entities.cs +0 -25
  280. package/framework/templates/code/dotnet/contracts/Queries.cs +0 -74
  281. package/framework/templates/code/dotnet/contracts/api-contracts.cs +0 -173
  282. package/framework/templates/code/dotnet/contracts/contracts-level1.cs +0 -69
  283. package/framework/templates/code/dotnet/contracts/contracts-level2.cs +0 -86
  284. package/framework/templates/code/dotnet/contracts/contracts-level3.cs +0 -41
  285. package/framework/templates/code/dotnet/frontend/component.razor +0 -239
  286. package/framework/templates/infrastructure/azure/Dockerfile.example +0 -82
  287. package/framework/templates/infrastructure/azure/README.md +0 -286
  288. package/framework/templates/infrastructure/azure/app-insights.bicep +0 -63
  289. package/framework/templates/infrastructure/azure/app-service.bicep +0 -164
  290. package/framework/templates/infrastructure/azure/container-app-env.bicep +0 -49
  291. package/framework/templates/infrastructure/azure/container-app.bicep +0 -156
  292. package/framework/templates/infrastructure/azure/deploy-checklist.md +0 -426
  293. package/framework/templates/infrastructure/azure/deploy.ps1 +0 -229
  294. package/framework/templates/infrastructure/azure/deploy.sh +0 -208
  295. package/framework/templates/infrastructure/azure/key-vault.bicep +0 -91
  296. package/framework/templates/infrastructure/azure/main.bicep +0 -189
  297. package/framework/templates/infrastructure/azure/parameters.dev.json +0 -29
  298. package/framework/templates/infrastructure/azure/parameters.prod.json +0 -29
  299. package/framework/templates/infrastructure/azure/parameters.staging.json +0 -29
  300. package/framework/templates/infrastructure/azure/sql-database.bicep +0 -103
  301. package/framework/templates/infrastructure/azure/storage.bicep +0 -106
  302. package/framework/templates/infrastructure/github/actions/azure-auth/action.yml.hbs +0 -22
  303. package/framework/templates/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +0 -61
  304. package/framework/templates/integrations/azure-identity-config.cs +0 -288
  305. package/framework/templates/project-structure/dotnet-ddd.md +0 -70
  306. package/framework/templates/saas/subscription.cs +0 -347
  307. package/framework/templates/saas/tenant.cs +0 -338
  308. package/framework/templates/ui/FluentDesignTheme.cs +0 -149
  309. package/framework/templates/ui/MudTheme.cs +0 -281
  310. package/framework/workflows/configs/nodejs-cli.json +0 -40
  311. package/src/commands/standards/context-budget.js +0 -89
  312. package/src/lib/standards/index.js +0 -7
  313. package/src/lib/standards/standards-context-injector.js +0 -302
  314. package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +0 -316
  315. package/src/lib/validators/blazor/blazor-state-validator.js +0 -279
  316. package/src/lib/validators/blazor/blazor-validator.js +0 -369
  317. package/src/lib/validators/blazor/index.js +0 -9
@@ -68,10 +68,6 @@ export function detectProject(targetPath, opts = {}) {
68
68
  // UI library detection
69
69
  if (existsSync(join(targetPath, 'components.json')) || existsSync(join(targetPath, structure.frontendPath || '.', 'components.json')) || depNames.includes('@shadcn/ui')) {
70
70
  structure.uiLibrary = 'shadcn';
71
- } else if (depNames.some(d => d.includes('mudblazor'))) {
72
- structure.uiLibrary = 'mudblazor';
73
- } else if (depNames.some(d => d.includes('fluent'))) {
74
- structure.uiLibrary = 'fluent-ui';
75
71
  }
76
72
 
77
73
  // Integrations
@@ -93,16 +89,12 @@ export function detectProject(targetPath, opts = {}) {
93
89
 
94
90
  // ── .csproj detection ──────────────────────────────────────────────────
95
91
  let hasDotnet = false;
96
- let hasBlazer = false;
97
92
  const csprojLocations = ['.', 'src', 'src/Api', 'src/Web', 'src/backend'];
98
93
 
99
94
  function checkCsproj(filePath) {
100
95
  hasDotnet = true;
101
96
  try {
102
97
  const content = readFileSync(filePath, 'utf8');
103
- if (content.includes('Microsoft.AspNetCore.Components') || content.includes('Blazor')) {
104
- hasBlazer = true;
105
- }
106
98
  if (content.includes('Hangfire')) {
107
99
  if (!structure.integrations.includes('hangfire')) structure.integrations.push('hangfire');
108
100
  }
@@ -128,13 +120,11 @@ export function detectProject(targetPath, opts = {}) {
128
120
  } catch { /* ignore */ }
129
121
  }
130
122
 
131
- // Determine stack: monorepo (nextjs+dotnet), blazor, dotnet, or keep frontend stack
123
+ // Determine stack: monorepo (nextjs+dotnet), dotnet, or keep frontend stack
132
124
  if (hasDotnet) {
133
125
  if (structure.stack === 'nextjs' || structure.stack === 'react' || structure.stack === 'vue') {
134
126
  // Monorepo: frontend + .NET backend
135
127
  structure.stack = `${structure.stack}+dotnet`;
136
- } else if (hasBlazer) {
137
- structure.stack = 'blazor';
138
128
  } else if (structure.stack === 'unknown') {
139
129
  structure.stack = 'dotnet';
140
130
  }
@@ -0,0 +1,640 @@
1
+ /**
2
+ * Architecture Scanner - Generates rich architectural context from project filesystem.
3
+ *
4
+ * Performs fast (<5s) glob-based scanning to produce a snapshot of:
5
+ * - .NET entities (sealed class/record)
6
+ * - API endpoints (IApiEndpoint implementations)
7
+ * - Next.js pages and components
8
+ * - Integrations (from appsettings.json)
9
+ * - Database schema (from EF migrations)
10
+ * - Stack dependencies (.csproj + package.json)
11
+ *
12
+ * All scanning is regex-based (no AST parsing) for speed.
13
+ *
14
+ * @module architecture-scanner
15
+ */
16
+
17
+ import fs from 'fs/promises';
18
+ import path from 'path';
19
+ import { existsSync, readFileSync } from 'fs';
20
+ import { renderTemplate } from '../../core/templates/template-renderer.js';
21
+
22
+ const SKIP_DIRS = new Set(['node_modules', 'bin', 'obj', '.git', '.morph', '.next', 'dist', 'out', '.worktrees']);
23
+ const MAX_FILES_PER_SCANNER = 200;
24
+
25
+ /**
26
+ * Recursively walk a directory, returning files matching a filter.
27
+ * Skips known heavy directories for performance.
28
+ *
29
+ * @param {string} dir - Directory to walk
30
+ * @param {function} filter - (relativePath) => boolean
31
+ * @param {string} [base] - Base path for relative calculation
32
+ * @param {number} [limit] - Max files to return
33
+ * @returns {Promise<Array<{abs: string, rel: string}>>}
34
+ */
35
+ async function walkDir(dir, filter, base = dir, limit = MAX_FILES_PER_SCANNER) {
36
+ const results = [];
37
+
38
+ async function recurse(currentDir) {
39
+ if (results.length >= limit) return;
40
+
41
+ let entries;
42
+ try {
43
+ entries = await fs.readdir(currentDir, { withFileTypes: true });
44
+ } catch {
45
+ return;
46
+ }
47
+
48
+ for (const entry of entries) {
49
+ if (results.length >= limit) break;
50
+
51
+ if (entry.isDirectory()) {
52
+ if (SKIP_DIRS.has(entry.name)) continue;
53
+ await recurse(path.join(currentDir, entry.name));
54
+ } else if (entry.isFile()) {
55
+ const abs = path.join(currentDir, entry.name);
56
+ const rel = path.relative(base, abs).replace(/\\/g, '/');
57
+ if (filter(rel, entry.name)) {
58
+ results.push({ abs, rel });
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ await recurse(dir);
65
+ return results;
66
+ }
67
+
68
+ /**
69
+ * Scan .NET entities from Entities/ or Features/ directories.
70
+ *
71
+ * @param {string} projectPath
72
+ * @returns {Promise<Array<{name: string, type: string, path: string}>>}
73
+ */
74
+ export async function scanEntities(projectPath) {
75
+ try {
76
+ const files = await walkDir(
77
+ projectPath,
78
+ (rel, name) => name.endsWith('.cs') && (rel.includes('Entities/') || rel.includes('Features/') || rel.includes('Models/'))
79
+ );
80
+
81
+ const entities = [];
82
+ const entityRegex = /(?:public\s+)?(?:sealed\s+)?(class|record)\s+(\w+)/g;
83
+
84
+ for (const file of files) {
85
+ try {
86
+ const content = await fs.readFile(file.abs, 'utf8');
87
+ let match;
88
+ entityRegex.lastIndex = 0;
89
+ while ((match = entityRegex.exec(content)) !== null) {
90
+ const line = content.slice(Math.max(0, match.index - 80), match.index + match[0].length);
91
+ // Only capture sealed/record types or classes in entity-like directories
92
+ if (/sealed|record/.test(line) || file.rel.includes('Entities/')) {
93
+ entities.push({
94
+ name: match[2],
95
+ type: match[1],
96
+ path: file.rel
97
+ });
98
+ }
99
+ }
100
+ } catch { /* skip unreadable files */ }
101
+ }
102
+
103
+ return entities;
104
+ } catch {
105
+ return [];
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Scan API endpoints implementing IApiEndpoint.
111
+ *
112
+ * @param {string} projectPath
113
+ * @returns {Promise<Array<{name: string, route: string, method: string, path: string}>>}
114
+ */
115
+ export async function scanEndpoints(projectPath) {
116
+ try {
117
+ const files = await walkDir(
118
+ projectPath,
119
+ (rel, name) => name.endsWith('.cs') && (rel.includes('Endpoints/') || rel.includes('Features/'))
120
+ );
121
+
122
+ const endpoints = [];
123
+ const classRegex = /class\s+(\w+)[^{]*(?:IApiEndpoint|IEndpoint|Endpoint)/;
124
+ const routeRegex = /Route\s*(?:=>|=)\s*"([^"]+)"/;
125
+ const methodRegex = /(?:Http(Get|Post|Put|Delete|Patch))|(?:\.Map(Get|Post|Put|Delete|Patch))/i;
126
+
127
+ for (const file of files) {
128
+ try {
129
+ const content = await fs.readFile(file.abs, 'utf8');
130
+ const classMatch = classRegex.exec(content);
131
+ if (!classMatch) continue;
132
+
133
+ const routeMatch = routeRegex.exec(content);
134
+ const methodMatch = methodRegex.exec(content);
135
+
136
+ endpoints.push({
137
+ name: classMatch[1],
138
+ route: routeMatch ? routeMatch[1] : '(unknown)',
139
+ method: methodMatch ? (methodMatch[1] || methodMatch[2] || '').toUpperCase() : '',
140
+ path: file.rel
141
+ });
142
+ } catch { /* skip */ }
143
+ }
144
+
145
+ return endpoints;
146
+ } catch {
147
+ return [];
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Scan Next.js pages and components.
153
+ *
154
+ * @param {string} projectPath
155
+ * @returns {Promise<{pages: Array<{route: string, path: string}>, layouts: Array<{path: string}>, components: Array<{name: string, path: string}>}>}
156
+ */
157
+ export async function scanNextjsComponents(projectPath) {
158
+ try {
159
+ const result = { pages: [], layouts: [], components: [] };
160
+
161
+ // Scan pages and layouts from app directory
162
+ const appFiles = await walkDir(
163
+ projectPath,
164
+ (rel, name) =>
165
+ (name === 'page.tsx' || name === 'page.jsx' || name === 'layout.tsx' || name === 'layout.jsx') &&
166
+ (rel.startsWith('app/') || rel.startsWith('src/app/'))
167
+ );
168
+
169
+ for (const file of appFiles) {
170
+ const isLayout = file.rel.includes('layout.');
171
+ const routeDir = path.dirname(file.rel).replace(/^(?:src\/)?app/, '').replace(/\\/g, '/');
172
+ const route = routeDir || '/';
173
+
174
+ if (isLayout) {
175
+ result.layouts.push({ path: file.rel });
176
+ } else {
177
+ result.pages.push({ route: route === '' ? '/' : route, path: file.rel });
178
+ }
179
+ }
180
+
181
+ // Scan components
182
+ const componentFiles = await walkDir(
183
+ projectPath,
184
+ (rel, name) =>
185
+ (name.endsWith('.tsx') || name.endsWith('.jsx')) &&
186
+ (rel.startsWith('src/components/') || rel.startsWith('components/') || rel.includes('/components/'))
187
+ );
188
+
189
+ for (const file of componentFiles) {
190
+ const name = path.basename(file.rel, path.extname(file.rel));
191
+ // Skip index files and test files
192
+ if (name === 'index' || name.includes('.test') || name.includes('.spec')) continue;
193
+ result.components.push({ name, path: file.rel });
194
+ }
195
+
196
+ return result;
197
+ } catch {
198
+ return { pages: [], layouts: [], components: [] };
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Scan integrations from appsettings*.json files.
204
+ *
205
+ * @param {string} projectPath
206
+ * @returns {Promise<Array<{name: string, type: string, configured: boolean}>>}
207
+ */
208
+ export async function scanIntegrations(projectPath) {
209
+ try {
210
+ const files = await walkDir(
211
+ projectPath,
212
+ (rel, name) => /^appsettings(?:\.\w+)?\.json$/.test(name),
213
+ projectPath,
214
+ 20
215
+ );
216
+
217
+ // Also check .env files for common integrations
218
+ const envFiles = await walkDir(
219
+ projectPath,
220
+ (rel, name) => name === '.env' || name === '.env.local' || name === '.env.production',
221
+ projectPath,
222
+ 5
223
+ );
224
+
225
+ const knownServices = {
226
+ 'Clerk': 'auth',
227
+ 'AsaaS': 'payment',
228
+ 'Resend': 'email',
229
+ 'Hangfire': 'background-jobs',
230
+ 'Supabase': 'database/auth',
231
+ 'ConnectionStrings': 'database',
232
+ 'Redis': 'cache',
233
+ 'SendGrid': 'email',
234
+ 'Stripe': 'payment',
235
+ 'AzureAd': 'auth',
236
+ 'OpenAI': 'ai',
237
+ 'AzureOpenAI': 'ai'
238
+ };
239
+
240
+ const detected = new Map();
241
+
242
+ // Scan appsettings files
243
+ for (const file of files) {
244
+ try {
245
+ const content = JSON.parse(await fs.readFile(file.abs, 'utf8'));
246
+ for (const [service, type] of Object.entries(knownServices)) {
247
+ if (content[service] || content[service.toLowerCase()]) {
248
+ const hasValues = content[service] && typeof content[service] === 'object'
249
+ ? Object.values(content[service]).some(v => v && v !== '' && !String(v).startsWith('{{'))
250
+ : true;
251
+ if (!detected.has(service) || hasValues) {
252
+ detected.set(service, { name: service, type, configured: hasValues });
253
+ }
254
+ }
255
+ }
256
+ // Check ConnectionStrings specifically
257
+ if (content.ConnectionStrings) {
258
+ for (const [key, val] of Object.entries(content.ConnectionStrings)) {
259
+ if (val && !String(val).startsWith('{{')) {
260
+ detected.set(`DB:${key}`, { name: `ConnectionString:${key}`, type: 'database', configured: true });
261
+ }
262
+ }
263
+ }
264
+ } catch { /* skip invalid JSON */ }
265
+ }
266
+
267
+ // Scan .env files for known prefixes
268
+ const envPrefixes = {
269
+ 'NEXT_PUBLIC_CLERK': 'Clerk',
270
+ 'CLERK_': 'Clerk',
271
+ 'NEXT_PUBLIC_SUPABASE': 'Supabase',
272
+ 'SUPABASE_': 'Supabase',
273
+ 'RESEND_': 'Resend',
274
+ 'STRIPE_': 'Stripe',
275
+ 'OPENAI_': 'OpenAI',
276
+ 'ASAAS_': 'AsaaS'
277
+ };
278
+
279
+ for (const file of envFiles) {
280
+ try {
281
+ const content = await fs.readFile(file.abs, 'utf8');
282
+ for (const [prefix, service] of Object.entries(envPrefixes)) {
283
+ if (content.includes(prefix)) {
284
+ const type = knownServices[service] || 'integration';
285
+ if (!detected.has(service)) {
286
+ detected.set(service, { name: service, type, configured: true });
287
+ }
288
+ }
289
+ }
290
+ } catch { /* skip */ }
291
+ }
292
+
293
+ return Array.from(detected.values());
294
+ } catch {
295
+ return [];
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Scan database schema from EF Core migrations.
301
+ *
302
+ * @param {string} projectPath
303
+ * @returns {Promise<Array<{table: string, migration: string}>>}
304
+ */
305
+ export async function scanDatabaseSchema(projectPath) {
306
+ try {
307
+ const files = await walkDir(
308
+ projectPath,
309
+ (rel, name) => name.endsWith('.cs') && rel.includes('Migrations/') && !name.endsWith('.Designer.cs'),
310
+ projectPath,
311
+ 100
312
+ );
313
+
314
+ const tables = new Map();
315
+ const toTableRegex = /\.ToTable\("(\w+)"/g;
316
+ const createTableRegex = /migrationBuilder\.CreateTable\(\s*name:\s*"(\w+)"/g;
317
+
318
+ for (const file of files) {
319
+ try {
320
+ const content = await fs.readFile(file.abs, 'utf8');
321
+ const migrationName = path.basename(file.rel, '.cs');
322
+
323
+ for (const regex of [toTableRegex, createTableRegex]) {
324
+ regex.lastIndex = 0;
325
+ let match;
326
+ while ((match = regex.exec(content)) !== null) {
327
+ if (!tables.has(match[1])) {
328
+ tables.set(match[1], { table: match[1], migration: migrationName });
329
+ }
330
+ }
331
+ }
332
+ } catch { /* skip */ }
333
+ }
334
+
335
+ // Also scan Supabase migrations (SQL)
336
+ const sqlFiles = await walkDir(
337
+ projectPath,
338
+ (rel, name) => name.endsWith('.sql') && (rel.includes('migrations/') || rel.includes('supabase/migrations/')),
339
+ projectPath,
340
+ 100
341
+ );
342
+
343
+ const createSqlRegex = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:public\.)?["']?(\w+)["']?/gi;
344
+
345
+ for (const file of sqlFiles) {
346
+ try {
347
+ const content = await fs.readFile(file.abs, 'utf8');
348
+ const migrationName = path.basename(file.rel, '.sql');
349
+ createSqlRegex.lastIndex = 0;
350
+ let match;
351
+ while ((match = createSqlRegex.exec(content)) !== null) {
352
+ if (!tables.has(match[1])) {
353
+ tables.set(match[1], { table: match[1], migration: migrationName });
354
+ }
355
+ }
356
+ } catch { /* skip */ }
357
+ }
358
+
359
+ return Array.from(tables.values());
360
+ } catch {
361
+ return [];
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Detect stack from .csproj PackageReference and package.json dependencies.
367
+ *
368
+ * @param {string} projectPath
369
+ * @returns {Promise<{dotnet: Array<{name: string, version: string}>, npm: Array<{name: string, version: string}>}>}
370
+ */
371
+ export async function scanStackDependencies(projectPath) {
372
+ try {
373
+ const result = { dotnet: [], npm: [] };
374
+
375
+ // Scan .csproj files
376
+ const csprojFiles = await walkDir(
377
+ projectPath,
378
+ (rel, name) => name.endsWith('.csproj'),
379
+ projectPath,
380
+ 10
381
+ );
382
+
383
+ const packageRefRegex = /<PackageReference\s+Include="([^"]+)"\s+Version="([^"]+)"/g;
384
+
385
+ for (const file of csprojFiles) {
386
+ try {
387
+ const content = await fs.readFile(file.abs, 'utf8');
388
+ packageRefRegex.lastIndex = 0;
389
+ let match;
390
+ while ((match = packageRefRegex.exec(content)) !== null) {
391
+ result.dotnet.push({ name: match[1], version: match[2] });
392
+ }
393
+ } catch { /* skip */ }
394
+ }
395
+
396
+ // Scan package.json (root only)
397
+ const pkgPath = path.join(projectPath, 'package.json');
398
+ if (existsSync(pkgPath)) {
399
+ try {
400
+ const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
401
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
402
+ for (const [name, version] of Object.entries(allDeps)) {
403
+ result.npm.push({ name, version: String(version) });
404
+ }
405
+ } catch { /* skip */ }
406
+ }
407
+
408
+ // Also check web/ or frontend/ subdirectory package.json
409
+ for (const subdir of ['web', 'frontend', 'client', 'src']) {
410
+ const subPkgPath = path.join(projectPath, subdir, 'package.json');
411
+ if (existsSync(subPkgPath)) {
412
+ try {
413
+ const pkg = JSON.parse(await fs.readFile(subPkgPath, 'utf8'));
414
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
415
+ for (const [name, version] of Object.entries(allDeps)) {
416
+ // Avoid duplicates
417
+ if (!result.npm.some(d => d.name === name)) {
418
+ result.npm.push({ name, version: String(version) });
419
+ }
420
+ }
421
+ } catch { /* skip */ }
422
+ }
423
+ }
424
+
425
+ return result;
426
+ } catch {
427
+ return { dotnet: [], npm: [] };
428
+ }
429
+ }
430
+
431
+ /**
432
+ * Master scan — runs all scanners in parallel.
433
+ *
434
+ * @param {string} projectPath
435
+ * @returns {Promise<Object>} ArchitectureScanResult
436
+ */
437
+ export async function scanArchitecture(projectPath) {
438
+ const [entities, endpoints, nextjs, integrations, database, stack] = await Promise.all([
439
+ scanEntities(projectPath),
440
+ scanEndpoints(projectPath),
441
+ scanNextjsComponents(projectPath),
442
+ scanIntegrations(projectPath),
443
+ scanDatabaseSchema(projectPath),
444
+ scanStackDependencies(projectPath)
445
+ ]);
446
+
447
+ return {
448
+ timestamp: new Date().toISOString(),
449
+ entities,
450
+ endpoints,
451
+ nextjs,
452
+ integrations,
453
+ database,
454
+ stack
455
+ };
456
+ }
457
+
458
+ /**
459
+ * Resolve a template path: check .morph/framework/templates/ first, then framework/templates/,
460
+ * then fall back to the CLI package's own framework/templates/.
461
+ *
462
+ * @param {string} projectPath
463
+ * @param {string} templateRelPath
464
+ * @returns {string|null}
465
+ */
466
+ function resolveTemplatePath(projectPath, templateRelPath) {
467
+ // 1. Project-local override
468
+ const projectLocal = path.join(projectPath, '.morph/framework/templates', templateRelPath);
469
+ if (existsSync(projectLocal)) return projectLocal;
470
+
471
+ // 2. CLI package framework/ (for running from source or during init)
472
+ const cliFramework = path.join(
473
+ path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, '$1')),
474
+ '..', '..', '..', 'framework', 'templates', templateRelPath
475
+ );
476
+ if (existsSync(cliFramework)) return cliFramework;
477
+
478
+ return null;
479
+ }
480
+
481
+ /**
482
+ * Render architecture context to .morph/context/README.md
483
+ *
484
+ * @param {string} projectPath
485
+ * @param {Object} scanResult - Result from scanArchitecture()
486
+ * @param {Object} [options]
487
+ * @param {string} [options.projectName] - Override project name
488
+ * @returns {Promise<string>} Path to written file
489
+ */
490
+ export async function renderArchitectureContext(projectPath, scanResult, options = {}) {
491
+ const templatePath = resolveTemplatePath(projectPath, 'context/ARCHITECTURE.md');
492
+
493
+ // Determine project name
494
+ let projectName = options.projectName;
495
+ if (!projectName) {
496
+ // Try config.json
497
+ const configPath = path.join(projectPath, '.morph/config/config.json');
498
+ if (existsSync(configPath)) {
499
+ try {
500
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
501
+ projectName = config.project?.name;
502
+ } catch { /* ignore */ }
503
+ }
504
+ // Fallback to directory name
505
+ if (!projectName) {
506
+ projectName = path.basename(projectPath);
507
+ }
508
+ }
509
+
510
+ // Get morph-spec version
511
+ let morphVersion = 'unknown';
512
+ try {
513
+ const pkgPath = path.join(
514
+ path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, '$1')),
515
+ '..', '..', '..', 'package.json'
516
+ );
517
+ if (existsSync(pkgPath)) {
518
+ morphVersion = JSON.parse(readFileSync(pkgPath, 'utf8')).version;
519
+ }
520
+ } catch { /* ignore */ }
521
+
522
+ const variables = {
523
+ projectName,
524
+ timestamp: new Date().toISOString().replace('T', ' ').split('.')[0],
525
+ morphVersion,
526
+ // Entities
527
+ hasDotnet: scanResult.entities.length > 0 || scanResult.endpoints.length > 0,
528
+ entities: scanResult.entities,
529
+ entityCount: scanResult.entities.length,
530
+ endpoints: scanResult.endpoints,
531
+ endpointCount: scanResult.endpoints.length,
532
+ // Next.js
533
+ hasNextjs: scanResult.nextjs.pages.length > 0 || scanResult.nextjs.components.length > 0,
534
+ pages: scanResult.nextjs.pages,
535
+ pageCount: scanResult.nextjs.pages.length,
536
+ layouts: scanResult.nextjs.layouts,
537
+ components: scanResult.nextjs.components,
538
+ componentCount: scanResult.nextjs.components.length,
539
+ // Integrations
540
+ hasIntegrations: scanResult.integrations.length > 0,
541
+ integrations: scanResult.integrations,
542
+ // Database
543
+ hasTables: scanResult.database.length > 0,
544
+ tables: scanResult.database,
545
+ tableCount: scanResult.database.length,
546
+ // Stack
547
+ hasDotnetPackages: scanResult.stack.dotnet.length > 0,
548
+ dotnetPackages: scanResult.stack.dotnet,
549
+ hasNpmPackages: scanResult.stack.npm.length > 0,
550
+ npmPackages: scanResult.stack.npm
551
+ };
552
+
553
+ let content;
554
+ if (templatePath) {
555
+ content = renderTemplate(templatePath, variables);
556
+ } else {
557
+ // Fallback: generate minimal markdown without template
558
+ content = generateFallbackMarkdown(variables);
559
+ }
560
+
561
+ // Write to .morph/context/README.md
562
+ const contextDir = path.join(projectPath, '.morph/context');
563
+ await fs.mkdir(contextDir, { recursive: true });
564
+ const outputPath = path.join(contextDir, 'README.md');
565
+ await fs.writeFile(outputPath, content, 'utf8');
566
+
567
+ return outputPath;
568
+ }
569
+
570
+ /**
571
+ * Generate minimal markdown when template is not available.
572
+ * @param {Object} vars
573
+ * @returns {string}
574
+ */
575
+ function generateFallbackMarkdown(vars) {
576
+ const lines = [`# ${vars.projectName} — Architecture Context\n`];
577
+ lines.push(`> Auto-generated by morph-spec context refresh`);
578
+ lines.push(`> Last updated: ${vars.timestamp}\n`);
579
+
580
+ if (vars.hasDotnet) {
581
+ lines.push('## .NET Entities\n');
582
+ for (const e of vars.entities) {
583
+ lines.push(`- \`${e.name}\` (${e.type}) — ${e.path}`);
584
+ }
585
+ lines.push('\n## API Endpoints\n');
586
+ for (const ep of vars.endpoints) {
587
+ lines.push(`- ${ep.method ? ep.method + ' ' : ''}\`${ep.route}\` — ${ep.name} (${ep.path})`);
588
+ }
589
+ lines.push('');
590
+ }
591
+
592
+ if (vars.hasNextjs) {
593
+ lines.push('## Next.js Pages\n');
594
+ for (const p of vars.pages) {
595
+ lines.push(`- \`${p.route}\` — ${p.path}`);
596
+ }
597
+ if (vars.components.length > 0) {
598
+ lines.push('\n## Components\n');
599
+ for (const c of vars.components) {
600
+ lines.push(`- \`${c.name}\` — ${c.path}`);
601
+ }
602
+ }
603
+ lines.push('');
604
+ }
605
+
606
+ if (vars.hasIntegrations) {
607
+ lines.push('## Integrations\n');
608
+ for (const i of vars.integrations) {
609
+ lines.push(`- **${i.name}** (${i.type}) — ${i.configured ? 'configured' : 'not configured'}`);
610
+ }
611
+ lines.push('');
612
+ }
613
+
614
+ if (vars.hasTables) {
615
+ lines.push('## Database Tables\n');
616
+ for (const t of vars.tables) {
617
+ lines.push(`- \`${t.table}\` (from ${t.migration})`);
618
+ }
619
+ lines.push('');
620
+ }
621
+
622
+ if (vars.hasDotnetPackages) {
623
+ lines.push('## .NET Packages\n');
624
+ for (const p of vars.dotnetPackages) {
625
+ lines.push(`- \`${p.name}\` ${p.version}`);
626
+ }
627
+ lines.push('');
628
+ }
629
+
630
+ if (vars.hasNpmPackages) {
631
+ lines.push('## NPM Packages\n');
632
+ for (const p of vars.npmPackages) {
633
+ lines.push(`- \`${p.name}\` ${p.version}`);
634
+ }
635
+ lines.push('');
636
+ }
637
+
638
+ lines.push(`---\n*Generated by morph-spec v${vars.morphVersion}*`);
639
+ return lines.join('\n');
640
+ }
@@ -207,13 +207,13 @@ export async function generateProjectContext(projectPath) {
207
207
  // Build data object
208
208
  const data = {
209
209
  PROJECT_NAME: config?.project?.name || 'Unknown Project',
210
- PROJECT_TYPE: config?.project?.type || 'blazor-server',
210
+ PROJECT_TYPE: config?.project?.type || 'dotnet-nextjs',
211
211
  PROJECT_DESCRIPTION: projectDesc || config?.project?.description || '',
212
212
  REPOSITORY_URL: config?.project?.repository || '',
213
213
  TIMESTAMP: new Date().toISOString(),
214
214
 
215
215
  // Stack detection
216
- BLAZOR_ENABLED: config?.project?.type?.includes('blazor') ? true : false,
216
+ DOTNET_ENABLED: config?.project?.type?.includes('dotnet') ? true : false,
217
217
  NEXTJS_ENABLED: config?.project?.type?.includes('nextjs') ? true : false,
218
218
  DOTNET_VERSION: '10.0',
219
219
  EF_VERSION: '10.0',
@@ -342,7 +342,7 @@ export async function generateFeatureContext(projectPath, featureName) {
342
342
  let tasks = [];
343
343
 
344
344
  try {
345
- const decisionsPath = path.join(outputsDir, '1-design', 'decisions.md');
345
+ const decisionsPath = path.join(outputsDir, '2-design', 'decisions.md');
346
346
  const decisionsContent = await fs.readFile(decisionsPath, 'utf8');
347
347
  // Parse decisions.md (simplified - just extract ADR headers)
348
348
  const adrMatches = decisionsContent.matchAll(/##\s+ADR-(\d+):\s+(.+)/g);