@polymorphism-tech/morph-spec 8.37.1 → 8.40.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/CLAUDE.md +37 -13
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +75 -49
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +6 -5
  8. package/framework/MORPH.md +25 -22
  9. package/framework/agents.json +96 -36
  10. package/framework/docs/CLI.md +54 -17
  11. package/framework/evals/rubrics/code-quality.md +5 -0
  12. package/framework/evals/rubrics/condensed.md +6 -2
  13. package/framework/evals/rubrics/contracts.md +20 -0
  14. package/framework/evals/rubrics/test-coverage.md +17 -6
  15. package/framework/hooks/README.md +9 -3
  16. package/framework/hooks/claude-code/core/graph-refresh-kick.js +52 -0
  17. package/framework/hooks/claude-code/core/telemetry-log.js +172 -87
  18. package/framework/hooks/claude-code/core/validate-completion.js +16 -90
  19. package/framework/hooks/claude-code/core/worktree-context.js +10 -0
  20. package/framework/hooks/shared/command-tokens.js +96 -0
  21. package/framework/hooks/shared/git-exclude.js +44 -0
  22. package/framework/hooks/shared/git-paths.js +52 -0
  23. package/framework/hooks/shared/graph-freshness.js +380 -0
  24. package/framework/hooks/shared/graph-refresh.js +416 -0
  25. package/framework/hooks/shared/morph-cli.js +17 -5
  26. package/framework/hooks/shared/redact-secrets.js +55 -0
  27. package/framework/hooks/shared/telemetry-logger.js +25 -2
  28. package/framework/hooks/shared/transcript-usage.js +80 -0
  29. package/framework/mcp/registry.json +33 -2
  30. package/framework/rubrics/backend.json +2 -2
  31. package/framework/rubrics/default.json +2 -2
  32. package/framework/rubrics/frontend.json +2 -2
  33. package/framework/rules/csharp-standards.md +1 -0
  34. package/framework/rules/infrastructure-standards.md +0 -1
  35. package/framework/rules/morph-workflow.md +10 -4
  36. package/framework/rules/nextjs-standards.md +1 -0
  37. package/framework/schemas/config.schema.json +41 -1
  38. package/framework/schemas/feature.schema.json +5 -1
  39. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  40. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  41. package/framework/skills/core/morph-apply/SKILL.md +40 -11
  42. package/framework/skills/core/morph-brainstorming/SKILL.md +16 -7
  43. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  44. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  45. package/framework/skills/core/morph-implement/SKILL.md +44 -10
  46. package/framework/skills/core/morph-media/SKILL.md +1 -1
  47. package/framework/skills/core/morph-plan/SKILL.md +73 -15
  48. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  49. package/framework/skills/core/morph-review/SKILL.md +51 -13
  50. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  51. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  52. package/framework/skills/core/morph-uiux/SKILL.md +39 -13
  53. package/framework/standards/STANDARDS.json +189 -14
  54. package/framework/standards/ai-agents/context-providers.md +16 -26
  55. package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
  56. package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
  57. package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
  58. package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
  59. package/framework/standards/ai-agents/modalities-vision.md +1 -18
  60. package/framework/standards/ai-agents/prompt-sources.md +0 -4
  61. package/framework/standards/ai-agents/testing-ai.md +2 -1
  62. package/framework/standards/architecture/code-comments.md +100 -0
  63. package/framework/standards/backend/dotnet/testing.md +22 -0
  64. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
  65. package/framework/standards/frontend/admin-app/data-table.md +562 -0
  66. package/framework/standards/frontend/admin-app/feedback.md +236 -0
  67. package/framework/standards/frontend/admin-app/forms.md +232 -0
  68. package/framework/standards/frontend/admin-app/page-structure.md +203 -0
  69. package/framework/standards/frontend/design-system/ai-image-generation.md +5 -5
  70. package/framework/standards/frontend/nextjs/components.md +37 -58
  71. package/framework/standards/frontend/nextjs/data-fetching.md +1 -0
  72. package/framework/standards/frontend/nextjs/forms.md +31 -30
  73. package/framework/standards/frontend/nextjs/state-management.md +2 -1
  74. package/framework/standards/frontend/nextjs/testing.md +18 -0
  75. package/framework/standards/integration/mcp/mcp-tools.md +48 -117
  76. package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
  77. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
  78. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
  79. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
  80. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
  81. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
  82. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
  83. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
  84. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
  85. package/framework/templates/code/dotnet/database/migration.cs +49 -83
  86. package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
  87. package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
  88. package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
  89. package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
  90. package/framework/templates/code/dotnet/jobs/job.cs +2 -97
  91. package/framework/templates/code/dotnet/test.cs +2 -87
  92. package/framework/templates/code/typescript/contracts.ts +140 -167
  93. package/framework/templates/context/design-system.md +3 -3
  94. package/framework/templates/docs/proposal.md +11 -0
  95. package/framework/templates/docs/spec.md +15 -0
  96. package/framework/templates/docs/ui-mockups.md +115 -98
  97. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
  98. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
  99. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
  100. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
  101. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
  102. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
  103. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
  104. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
  105. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
  106. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
  107. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
  108. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
  109. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
  110. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
  111. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
  112. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
  113. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
  114. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
  115. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
  116. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
  117. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
  118. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
  119. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
  120. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
  121. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
  122. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
  123. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
  124. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
  125. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
  126. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
  127. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
  128. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
  129. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
  130. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
  131. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
  132. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
  133. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
  134. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
  135. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
  136. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
  137. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
  138. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
  139. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
  140. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
  141. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
  142. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
  143. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
  144. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
  145. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
  146. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
  147. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
  148. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
  149. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
  150. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
  151. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
  152. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
  153. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
  154. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
  155. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
  156. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
  157. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
  158. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
  159. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
  160. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
  161. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
  162. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
  163. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
  164. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
  165. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
  166. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
  167. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
  168. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
  169. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
  170. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
  171. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
  172. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
  173. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
  174. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
  175. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
  176. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
  177. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
  178. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
  179. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
  180. package/framework/templates/feature/decisions.md +124 -122
  181. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
  182. package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
  183. package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
  184. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
  185. package/framework/templates/integrations/asaas-client.cs +332 -387
  186. package/framework/templates/integrations/asaas-webhook.cs +290 -351
  187. package/framework/templates/integrations/neon-auth-config.cs +9 -105
  188. package/framework/templates/project/validate.js +97 -120
  189. package/package.json +2 -2
  190. package/src/commands/dashboard/dashboard.js +80 -53
  191. package/src/commands/graph/affected.js +4 -3
  192. package/src/commands/graph/build.js +109 -18
  193. package/src/commands/graph/explain.js +8 -5
  194. package/src/commands/graph/status.js +14 -8
  195. package/src/commands/mcp/mcp-setup.js +37 -9
  196. package/src/commands/observability/cost.js +148 -18
  197. package/src/commands/project/doctor.js +85 -15
  198. package/src/commands/project/init.js +6 -3
  199. package/src/commands/project/install-plugin-cmd.js +1 -1
  200. package/src/commands/project/update.js +30 -0
  201. package/src/commands/project/worktree.js +73 -90
  202. package/src/commands/state/advance.js +18 -0
  203. package/src/commands/state/approve.js +15 -0
  204. package/src/commands/state/archive.js +62 -9
  205. package/src/commands/state/finish.js +62 -14
  206. package/src/commands/state/gate-check.js +8 -1
  207. package/src/commands/state/gate-decision.js +8 -4
  208. package/src/commands/state/report-check.js +90 -9
  209. package/src/commands/state/usecase.js +66 -0
  210. package/src/commands/tasks/dag.js +17 -2
  211. package/src/commands/validation/validate-feature.js +6 -1
  212. package/src/commands/validation/verify.js +20 -1
  213. package/src/dashboard/app/App.jsx +80 -16
  214. package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
  215. package/src/dashboard/app/components/CommandPalette.jsx +155 -0
  216. package/src/dashboard/app/components/DetailPanel.jsx +47 -11
  217. package/src/dashboard/app/components/EntityLink.jsx +55 -0
  218. package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
  219. package/src/dashboard/app/components/FlowGraph.jsx +19 -17
  220. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
  221. package/src/dashboard/app/components/Markdown.jsx +47 -0
  222. package/src/dashboard/app/components/PhaseRail.jsx +111 -0
  223. package/src/dashboard/app/components/SearchFilter.jsx +40 -22
  224. package/src/dashboard/app/components/Sidebar.jsx +146 -37
  225. package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
  226. package/src/dashboard/app/hooks/usePref.js +38 -0
  227. package/src/dashboard/app/lib/format.js +69 -0
  228. package/src/dashboard/app/lib/phases.js +56 -0
  229. package/src/dashboard/app/lib/project.js +50 -0
  230. package/src/dashboard/app/pages/Activity.jsx +24 -0
  231. package/src/dashboard/app/pages/AgentMap.jsx +371 -370
  232. package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
  233. package/src/dashboard/app/pages/Features.jsx +400 -0
  234. package/src/dashboard/app/pages/Guide.jsx +146 -0
  235. package/src/dashboard/app/pages/HookInspector.jsx +357 -322
  236. package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
  237. package/src/dashboard/app/pages/Portfolio.jsx +156 -0
  238. package/src/dashboard/app/pages/Rubrics.jsx +101 -0
  239. package/src/dashboard/app/pages/RulesMap.jsx +300 -273
  240. package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
  241. package/src/dashboard/app/pages/Standards.jsx +407 -332
  242. package/src/dashboard/app/styles/index.css +187 -47
  243. package/src/dashboard/index.html +14 -12
  244. package/src/dashboard/package.json +32 -24
  245. package/src/dashboard/scanner/activity-scanner.js +351 -0
  246. package/src/dashboard/scanner/agents-scanner.js +63 -1
  247. package/src/dashboard/scanner/docs-scanner.js +59 -0
  248. package/src/dashboard/scanner/feature-status.js +10 -0
  249. package/src/dashboard/scanner/features-scanner.js +360 -0
  250. package/src/dashboard/scanner/hooks-scanner.js +8 -1
  251. package/src/dashboard/scanner/index.js +14 -7
  252. package/src/dashboard/scanner/phases-scanner.js +14 -15
  253. package/src/dashboard/scanner/portfolio.js +153 -0
  254. package/src/dashboard/scanner/project-api.js +195 -0
  255. package/src/dashboard/scanner/rules-scanner.js +42 -37
  256. package/src/dashboard/scanner/skills-scanner.js +105 -43
  257. package/src/dashboard/scanner/standards-scanner.js +4 -1
  258. package/src/dashboard/vite-plugin-morph-data.js +134 -17
  259. package/src/lib/ai/comment-stripper.js +101 -4
  260. package/src/lib/detectors/claude-config-detector.js +2 -7
  261. package/src/lib/detectors/index.js +5 -1
  262. package/src/lib/evaluator-report.js +47 -0
  263. package/src/lib/generators/handoff-generator.js +4 -18
  264. package/src/lib/generators/settings-generator.js +208 -209
  265. package/src/lib/git/default-branch.js +52 -0
  266. package/src/lib/git/tree-fingerprint.js +58 -0
  267. package/src/lib/git/tree-state.js +167 -0
  268. package/src/lib/graph/god-nodes.js +12 -3
  269. package/src/lib/graph/graph-health.js +48 -2
  270. package/src/lib/graph/graph-loader.js +99 -25
  271. package/src/lib/graph/graphifyignore.js +145 -28
  272. package/src/lib/graph/sanitize.js +117 -11
  273. package/src/lib/installers/mcp-installer.js +724 -719
  274. package/src/lib/installers/memory-installer.js +1 -2
  275. package/src/lib/projects/registry.js +154 -0
  276. package/src/lib/risk-detector.js +90 -0
  277. package/src/lib/specs/acervo-format.js +70 -0
  278. package/src/lib/specs/acervo-lifecycle.js +264 -0
  279. package/src/lib/specs/adr-index.js +169 -0
  280. package/src/lib/specs/revocations.js +156 -0
  281. package/src/lib/specs/screen-checklist.js +357 -0
  282. package/src/lib/specs/usecase-check.js +20 -16
  283. package/src/lib/specs/usecase-promoter.js +103 -38
  284. package/src/lib/specs/usecase-registry.js +7 -7
  285. package/src/lib/tasks/tasks-json-parser.js +2 -3
  286. package/src/lib/telemetry/metrics.js +386 -0
  287. package/src/lib/telemetry/telemetry-reader.js +161 -51
  288. package/src/lib/telemetry/verify-event.js +79 -0
  289. package/src/lib/validators/code-budget/code-budget-validator.js +200 -0
  290. package/src/lib/validators/code-budget/comment-scanner.js +75 -0
  291. package/src/lib/validators/code-budget/index.js +9 -0
  292. package/src/lib/validators/reuse/reuse-validator.js +4 -2
  293. package/src/lib/validators/reuse/symbol-scanner.js +27 -22
  294. package/src/lib/validators/validation-runner.js +21 -5
  295. package/src/lib/validators/verify-runner.js +11 -0
  296. package/src/lib/worktree/finish-backup.js +739 -0
  297. package/src/lib/worktree/ignored-triage.js +15 -8
  298. package/src/lib/worktree/worktree-audit.js +5 -0
  299. package/src/scripts/install-plugin.js +1 -1
  300. package/src/scripts/setup-infra.js +31 -22
  301. package/src/utils/agents-installer.js +338 -24
  302. package/src/utils/banner.js +1 -1
  303. package/src/utils/hooks-installer.js +75 -15
  304. package/src/commands/index.js +0 -13
  305. package/src/commands/project/checkpoint.js +0 -209
  306. package/src/core/state/phase-state-machine.js +0 -197
  307. package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
  308. package/src/dashboard/app/pages/LiveState.jsx +0 -206
  309. package/src/dashboard/scanner/squads-scanner.js +0 -13
  310. package/src/dashboard/scanner/state-scanner.js +0 -85
  311. package/src/lib/generators/index.js +0 -10
  312. package/src/lib/hooks/hook-executor.js +0 -167
  313. package/src/lib/validators/checkpoint-persistence.js +0 -39
  314. package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
  315. package/src/lib/validators/shared/index.js +0 -12
  316. package/src/lib/validators/shared/issue-counter.js +0 -14
  317. package/src/lib/validators/shared/result-formatter.js +0 -124
@@ -7,16 +7,10 @@ using Xunit;
7
7
 
8
8
  namespace Morph.AiKit.Tests;
9
9
 
10
- /// <summary>
11
- /// Canarios do <see cref="UsageCostMiddleware"/> (T11). A afirmacao central da task e uma
12
- /// so: <b><c>cost_usd</c> nao sai nulo, e o zero nunca e ambiguo</b> — ou e medicao, ou e
13
- /// dispensa escrita, ou e lacuna declarada.
14
- /// </summary>
15
10
  public sealed class UsageCostMiddlewareTests
16
11
  {
17
12
  private static readonly DateTimeOffset FixedNow = new(2026, 9, 8, 12, 0, 0, TimeSpan.Zero);
18
13
 
19
- /// <summary>Tarifa ilustrativa com desconto de cache — a que exercita a aritmetica inteira.</summary>
20
14
  private static ModelPricing Tariff()
21
15
  => new(InputPerMTok: 1.25m, OutputPerMTok: 10.0m, CachedInputPerMTok: 0.125m);
22
16
 
@@ -42,22 +36,11 @@ public sealed class UsageCostMiddlewareTests
42
36
 
43
37
  var sink = new FakeUsageCostSink();
44
38
 
45
- // A extensao publica e o caminho de registro: se ela quebrar, o teste quebra junto.
46
39
  var client = inner.AsBuilder().UseUsageCost(alias, sink, new FakeClock(FixedNow)).Build();
47
40
 
48
41
  return (client, inner, sink);
49
42
  }
50
43
 
51
- // ---------------------------------------------------------------------------------
52
- // doneCriteria 1: Usage conhecido + ModelPricing conhecido => CostUsd EXATO
53
- // ---------------------------------------------------------------------------------
54
-
55
- /// <summary>
56
- /// O valor esperado esta escrito a mao, com a semantica correta de token cacheado:
57
- /// <b>cacheado e SUBCONJUNTO da entrada</b>. 1000 de entrada com 400 servidos do cache
58
- /// custam 600 na tarifa cheia mais 400 na de cache — nunca 1000 mais 400. E por isso que
59
- /// este caso, e nao um sem cache, e o que denuncia uma segunda aritmetica.
60
- /// </summary>
61
44
  [Fact]
62
45
  public async Task AKnownUsageAgainstAKnownTariffProducesTheExactCostInUsd()
63
46
  {
@@ -83,13 +66,6 @@ public sealed class UsageCostMiddlewareTests
83
66
  Assert.Equal(FixedNow, record.At);
84
67
  }
85
68
 
86
- /// <summary>
87
- /// O canario que torna "nao existe uma segunda conta do custo" verificavel em vez de
88
- /// prometido: o custo publicado tem de ser <b>identico</b> ao de
89
- /// <see cref="ModelPricing.ComputeCostUsd"/> — inclusive nos casos que separam as duas
90
- /// formulas plausiveis (cacheado sem tarifa de cache, cacheado igual a entrada, saida
91
- /// zerada).
92
- /// </summary>
93
69
  [Theory]
94
70
  [InlineData(1000, 200, 400L)]
95
71
  [InlineData(1000, 200, null)]
@@ -111,11 +87,6 @@ public sealed class UsageCostMiddlewareTests
111
87
  Assert.Equal(pricing.ComputeCostUsd(input, output, cached), sink.Records[0].CostUsd);
112
88
  }
113
89
 
114
- /// <summary>
115
- /// Tarifa sem desconto de cache declarado: nulo em <c>CachedInputPerMTok</c> significa
116
- /// "sem desconto", <b>nunca</b> "de graca" — a entrada inteira e cobrada cheia mesmo com
117
- /// tokens vindos do cache.
118
- /// </summary>
119
90
  [Fact]
120
91
  public async Task WithoutADeclaredCacheTariffTheWholeInputIsChargedAsInput()
121
92
  {
@@ -127,19 +98,10 @@ public sealed class UsageCostMiddlewareTests
127
98
  [new ChatMessage(ChatRole.User, "oi")],
128
99
  cancellationToken: TestContext.Current.CancellationToken);
129
100
 
130
- // (1000 * 1.25) + (200 * 10.0) = 3250 por 1M tokens. O cacheado nao rende desconto.
101
+ // (1000 * 1.25) + (200 * 10.0) = 3250 por 1M tokens: o cacheado nao rende desconto.
131
102
  Assert.Equal(0.00325m, sink.Records[0].CostUsd);
132
103
  }
133
104
 
134
- // ---------------------------------------------------------------------------------
135
- // doneCriteria 2: alias sem Pricing => CostUsd == 0, ausencia sinalizada, SEM excecao
136
- // ---------------------------------------------------------------------------------
137
-
138
- /// <summary>
139
- /// Alias sem tarifa e caminho previsto, nao erro. E o zero nao e anonimo: o motivo
140
- /// escrito no <c>pricingWaiver</c> do registry viaja no record. Nao ha excecao — e nao
141
- /// ha nulo em <c>CostUsd</c>.
142
- /// </summary>
143
105
  [Fact]
144
106
  public async Task AnAliasWithoutATariffRecordsTokensWithZeroCostAndSaysWhy()
145
107
  {
@@ -156,19 +118,14 @@ public sealed class UsageCostMiddlewareTests
156
118
 
157
119
  var record = Assert.Single(sink.Records);
158
120
 
159
- Assert.Equal(0m, record.CostUsd); // zero, jamais nulo
121
+ Assert.Equal(0m, record.CostUsd);
160
122
  Assert.Equal(UsageCostStatus.Waived, record.Status);
161
- Assert.False(record.IsMeasuredCost); // logo, nao entra na soma
123
+ Assert.False(record.IsMeasuredCost);
162
124
  Assert.Equal(alias.PricingWaiver, record.PricingWaiver);
163
- Assert.Equal(900, record.InputTokens); // os tokens continuam medidos
125
+ Assert.Equal(900, record.InputTokens);
164
126
  Assert.Equal(100, record.OutputTokens);
165
127
  }
166
128
 
167
- /// <summary>
168
- /// A regra que sustenta a anterior mora no registry (T8): um alias sem tarifa <b>e</b>
169
- /// sem dispensa nao carrega. Por isso um record <c>Waived</c> sempre tem motivo — a
170
- /// ausencia por esquecimento morre antes de chegar ao middleware.
171
- /// </summary>
172
129
  [Fact]
173
130
  public void AWaivedRecordAlwaysHasAReasonBecauseTheRegistryRefusesAnAliasWithNeither()
174
131
  {
@@ -180,13 +137,9 @@ public sealed class UsageCostMiddlewareTests
180
137
  var refusal = Assert.Throws<ModelRegistryException>(() => ModelAlias.ValidatePricingDecision(orphan));
181
138
  Assert.Contains("sem-decisao", refusal.Message, StringComparison.Ordinal);
182
139
 
183
- ModelAlias.ValidatePricingDecision(WaivedAlias()); // com dispensa escrita, passa
140
+ ModelAlias.ValidatePricingDecision(WaivedAlias());
184
141
  }
185
142
 
186
- /// <summary>
187
- /// A soma so junta o que foi medido. Uma volta tarifada mais uma dispensada somam o
188
- /// custo da primeira — o zero da dispensa nao "abaixa a media" nem some do relatorio.
189
- /// </summary>
190
143
  [Fact]
191
144
  public async Task OnlyMeasuredCostsEnterTheSum()
192
145
  {
@@ -210,15 +163,6 @@ public sealed class UsageCostMiddlewareTests
210
163
  Assert.Equal(0.0028m, sink.MeasuredTotalUsd);
211
164
  }
212
165
 
213
- // ---------------------------------------------------------------------------------
214
- // O terceiro zero: o provider nao reportou uso
215
- // ---------------------------------------------------------------------------------
216
-
217
- /// <summary>
218
- /// Resposta sem <c>Usage</c>: a linha sai marcada como lacuna. Nao publicar nada faria o
219
- /// custo desaparecer do relatorio, que e a mesma dor por outro angulo; publicar como
220
- /// <c>Computed</c> mentiria que a volta custou zero.
221
- /// </summary>
222
166
  [Fact]
223
167
  public async Task AResponseWithoutUsageIsRecordedAsAGapNotAsFree()
224
168
  {
@@ -236,11 +180,6 @@ public sealed class UsageCostMiddlewareTests
236
180
  Assert.Equal(0m, sink.MeasuredTotalUsd);
237
181
  }
238
182
 
239
- /// <summary>
240
- /// <c>cached &gt; input</c> e o provider se contradizendo. A conta do T8 recusa esse par
241
- /// com excecao — e recusar aqui derrubaria a resposta do usuario por causa de um
242
- /// contador. O middleware degrada para lacuna declarada, sem lancar.
243
- /// </summary>
244
183
  [Fact]
245
184
  public async Task InconsistentTokenCountsBecomeADeclaredGapInsteadOfAThrownRequest()
246
185
  {
@@ -259,26 +198,6 @@ public sealed class UsageCostMiddlewareTests
259
198
  Assert.Equal(UsageCostStatus.UsageMissing, Assert.Single(sink.Records).Status);
260
199
  }
261
200
 
262
- // ---------------------------------------------------------------------------------
263
- // Uma linha por volta ao modelo — inclusive no streaming
264
- // ---------------------------------------------------------------------------------
265
-
266
- /// <summary>
267
- /// Duas <b>chamadas</b> a <c>GetResponseAsync</c> publicam <b>duas</b> linhas: uma por
268
- /// ida ao modelo, cada uma com o seu proprio numero.
269
- /// </summary>
270
- /// <remarks>
271
- /// O que este teste prova, e so isto: <b>uma linha por chamada</b>, sem acumulo de
272
- /// estado entre chamadas. Ele NAO prova nada sobre a posicao do middleware no pipeline
273
- /// — sao duas chamadas separadas, nao uma chamada com volta de tool, e aqui o
274
- /// middleware e montado sozinho, sem <c>FunctionInvokingChatClient</c> nenhum. A
275
- /// posicao real e o oposto do que uma leitura apressada deste teste sugeriria: o
276
- /// middleware fica <b>ACIMA</b> da invocacao de funcoes, e quem prova isso e
277
- /// <c>AgentFactoryTests.ThePipelineOrderIsUsageCostAboveFunctionInvocationAndTelemetryBelowIt</c>
278
- /// (com <c>...SeesTheUsageOfEveryToolRoundSummedOnce</c> mostrando o uso ja somado das
279
- /// duas voltas). Acima do FICC, uma conversa com tool call e <b>uma</b> chamada com o
280
- /// uso ja totalizado — nao duas.
281
- /// </remarks>
282
201
  [Fact]
283
202
  public async Task EveryTripToTheModelPublishesItsOwnLine()
284
203
  {
@@ -301,10 +220,6 @@ public sealed class UsageCostMiddlewareTests
301
220
  Assert.Equal(0.0035m, sink.Records[1].CostUsd); // (2000*1.25 + 100*10) / 1M
302
221
  }
303
222
 
304
- /// <summary>
305
- /// No streaming o uso chega como <c>UsageContent</c> avulso. A linha sai <b>uma</b> vez,
306
- /// ao fim da enumeracao, com o mesmo numero do caminho nao-streaming.
307
- /// </summary>
308
223
  [Fact]
309
224
  public async Task StreamingPublishesOneLineAtTheEndWithTheSameNumber()
310
225
  {
@@ -325,11 +240,6 @@ public sealed class UsageCostMiddlewareTests
325
240
  Assert.Equal(0.0028m, Assert.Single(sink.Records).CostUsd);
326
241
  }
327
242
 
328
- // ---------------------------------------------------------------------------------
329
- // Higiene de construcao
330
- // ---------------------------------------------------------------------------------
331
-
332
- /// <summary>Alias ou sink nulos sao defeito de composicao e falham na construcao.</summary>
333
243
  [Fact]
334
244
  public void TheMiddlewareRefusesToBeBuiltWithoutAnAliasOrASink()
335
245
  {
@@ -342,11 +252,6 @@ public sealed class UsageCostMiddlewareTests
342
252
  () => new UsageCostMiddleware(inner, PricedAlias(), null!));
343
253
  }
344
254
 
345
- /// <summary>
346
- /// O instante do record vem do <see cref="TimeProvider"/> injetado, nao do relogio do
347
- /// processo — sem isso o campo <c>At</c> seria intestavel (standard
348
- /// <c>backend-dotnet-testing</c>).
349
- /// </summary>
350
255
  [Fact]
351
256
  public async Task TheTimestampComesFromTheInjectedClock()
352
257
  {
@@ -1,122 +1,124 @@
1
- # Architecture Decision Records - {{titleCase FEATURE_NAME}}
2
-
3
- ## ADR-001: {Decision Title}
4
-
5
- **Date:** {{DATE}}
6
- **Status:** Proposed | Accepted | Deprecated | Superseded
7
- **Deciders:** MORPH Agents, Developer
8
-
9
- ### Context
10
-
11
- {Describe the context and problem that led to this decision}
12
-
13
- ### Decision
14
-
15
- {Describe the decision made}
16
-
17
- ### Consequences
18
-
19
- **Pros:**
20
- - {Positive consequence 1}
21
- - {Positive consequence 2}
22
-
23
- **Cons:**
24
- - {Negative consequence 1}
25
-
26
- **Risks:**
27
- - {Identified risk}
28
-
29
- ### Alternatives Considered
30
-
31
- #### Option A: {Name}
32
- - Pros: {advantages}
33
- - Cons: {disadvantages}
34
- - **Rejected because:** {reason}
35
-
36
- #### Option B: {Name}
37
- - Pros: {advantages}
38
- - Cons: {disadvantages}
39
- - **Rejected because:** {reason}
40
-
41
- ---
42
-
43
- {{#if (eq STACK "nextjs-neon")}}
44
- ## ADR-002: Database Access Strategy
45
-
46
- **Date:** {{DATE}}
47
- **Status:** Accepted
48
-
49
- ### Context
50
-
51
- Need to decide how the .NET API accesses Neon PostgreSQL.
52
-
53
- ### Decision
54
-
55
- Use Dapper with Npgsql for direct SQL queries. No EF Core.
56
-
57
- ### Consequences
58
-
59
- **Pros:**
60
- - Full control over SQL queries
61
- - Better performance (no ORM overhead)
62
- - Direct access to PostgreSQL features (jsonb, pgvector, functions)
63
- - RLS policies apply naturally through connection
64
-
65
- **Cons:**
66
- - No automatic migrations (use Neon CLI / dotnet ef instead)
67
- - Manual SQL query writing
68
- - No change tracking
69
-
70
- ---
71
-
72
- ## ADR-003: Auth Strategy
73
-
74
- **Date:** {{DATE}}
75
- **Status:** Accepted
76
-
77
- ### Context
78
-
79
- Need authentication for both Next.js frontend and .NET API.
80
-
81
- ### Decision
82
-
83
- Use Neon Auth (Better Auth) for authentication. Auth data lives in the Neon database. JWT validated by .NET API middleware.
84
-
85
- ### Consequences
86
-
87
- **Pros:**
88
- - Auth data in same database — no external provider, no sync delays
89
- - RLS policies work with Neon Auth JWT via auth.user_id() / pg_session_jwt
90
- - Built-in social login, email/password via @neondatabase/auth SDK
91
- - Branch isolation — each Neon branch gets isolated auth data
92
- - No webhooks needed — query neon_auth schema directly
93
-
94
- **Cons:**
95
- - Neon Auth is currently in Beta
96
- - JWT must be forwarded to .NET API for backend auth
97
- {{/if}}
98
-
99
- ---
100
-
101
- ## Template for New ADRs
102
-
103
- ```markdown
104
- ## ADR-XXX: {Title}
105
-
106
- **Date:**
107
- **Status:** Proposed | Accepted | Deprecated | Superseded
108
-
109
- ### Context
110
- {Why is this decision needed?}
111
-
112
- ### Decision
113
- {What was decided?}
114
-
115
- ### Consequences
116
- **Pros:** {list}
117
- **Cons:** {list}
118
- ```
119
-
120
- ---
121
-
122
- *Generated by MORPH Framework*
1
+ # Architecture Decision Records - {{titleCase FEATURE_NAME}}
2
+
3
+ ## ADR-001: {Decision Title}
4
+
5
+ **Date:** {{DATE}}
6
+ **Status:** Proposed | Accepted | Deprecated | Superseded
7
+ **Deciders:** MORPH Agents, Developer
8
+ **Alcance:** feature
9
+
10
+ ### Context
11
+
12
+ {Describe the context and problem that led to this decision}
13
+
14
+ ### Decision
15
+
16
+ {Describe the decision made}
17
+
18
+ ### Consequences
19
+
20
+ **Pros:**
21
+ - {Positive consequence 1}
22
+ - {Positive consequence 2}
23
+
24
+ **Cons:**
25
+ - {Negative consequence 1}
26
+
27
+ **Risks:**
28
+ - {Identified risk}
29
+
30
+ ### Alternatives Considered
31
+
32
+ #### Option A: {Name}
33
+ - Pros: {advantages}
34
+ - Cons: {disadvantages}
35
+ - **Rejected because:** {reason}
36
+
37
+ #### Option B: {Name}
38
+ - Pros: {advantages}
39
+ - Cons: {disadvantages}
40
+ - **Rejected because:** {reason}
41
+
42
+ ---
43
+
44
+ {{#if (eq STACK "nextjs-neon")}}
45
+ ## ADR-002: Database Access Strategy
46
+
47
+ **Date:** {{DATE}}
48
+ **Status:** Accepted
49
+
50
+ ### Context
51
+
52
+ Need to decide how the .NET API accesses Neon PostgreSQL.
53
+
54
+ ### Decision
55
+
56
+ Use Dapper with Npgsql for direct SQL queries. No EF Core.
57
+
58
+ ### Consequences
59
+
60
+ **Pros:**
61
+ - Full control over SQL queries
62
+ - Better performance (no ORM overhead)
63
+ - Direct access to PostgreSQL features (jsonb, pgvector, functions)
64
+ - RLS policies apply naturally through connection
65
+
66
+ **Cons:**
67
+ - No automatic migrations (use Neon CLI / dotnet ef instead)
68
+ - Manual SQL query writing
69
+ - No change tracking
70
+
71
+ ---
72
+
73
+ ## ADR-003: Auth Strategy
74
+
75
+ **Date:** {{DATE}}
76
+ **Status:** Accepted
77
+
78
+ ### Context
79
+
80
+ Need authentication for both Next.js frontend and .NET API.
81
+
82
+ ### Decision
83
+
84
+ Use Neon Auth (Better Auth) for authentication. Auth data lives in the Neon database. JWT validated by .NET API middleware.
85
+
86
+ ### Consequences
87
+
88
+ **Pros:**
89
+ - Auth data in same database — no external provider, no sync delays
90
+ - RLS policies work with Neon Auth JWT via auth.user_id() / pg_session_jwt
91
+ - Built-in social login, email/password via @neondatabase/auth SDK
92
+ - Branch isolation — each Neon branch gets isolated auth data
93
+ - No webhooks needed — query neon_auth schema directly
94
+
95
+ **Cons:**
96
+ - Neon Auth is currently in Beta
97
+ - JWT must be forwarded to .NET API for backend auth
98
+ {{/if}}
99
+
100
+ ---
101
+
102
+ ## Template for New ADRs
103
+
104
+ ```markdown
105
+ ## ADR-XXX: {Title}
106
+
107
+ **Date:**
108
+ **Status:** Proposed | Accepted | Deprecated | Superseded
109
+ **Alcance:** feature | projeto — `projeto` when the rule holds beyond this feature: `morph-spec finish` copies it to `.morph/specs/adrs/`, the folder brainstorming and plan read instead of archived decisions.md
110
+
111
+ ### Context
112
+ {Why is this decision needed?}
113
+
114
+ ### Decision
115
+ {What was decided?}
116
+
117
+ ### Consequences
118
+ **Pros:** {list}
119
+ **Cons:** {list}
120
+ ```
121
+
122
+ ---
123
+
124
+ *Generated by MORPH Framework*
@@ -1,43 +1,39 @@
1
- # Multi-stage Next.js production build for Coolify
2
- # Stage 1: Dependencies
3
- FROM node:20-alpine AS deps
4
- RUN apk add --no-cache libc6-compat
5
- WORKDIR /app
6
-
7
- COPY package.json package-lock.json* ./
8
- RUN npm ci --only=production && npm cache clean --force
9
-
10
- # Stage 2: Build
11
- FROM node:20-alpine AS builder
12
- WORKDIR /app
13
-
14
- COPY --from=deps /app/node_modules ./node_modules
15
- COPY . .
16
-
17
- # Build args for NEXT_PUBLIC_ variables (must be available at build time)
18
- ARG NEXT_PUBLIC_APP_URL
19
- ENV NEXT_PUBLIC_APP_URL=$NEXT_PUBLIC_APP_URL
20
-
21
- RUN npm run build
22
-
23
- # Stage 3: Production runner
24
- FROM node:20-alpine AS runner
25
- WORKDIR /app
26
-
27
- ENV NODE_ENV=production
28
- ENV NEXT_TELEMETRY_DISABLED=1
29
-
30
- RUN addgroup --system --gid 1001 nodejs
31
- RUN adduser --system --uid 1001 nextjs
32
-
33
- COPY --from=builder /app/public ./public
34
- COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
35
- COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
36
-
37
- USER nextjs
38
-
39
- EXPOSE 3000
40
- ENV PORT=3000
41
- ENV HOSTNAME="0.0.0.0"
42
-
43
- CMD ["node", "server.js"]
1
+ FROM node:20-alpine AS deps
2
+ RUN apk add --no-cache libc6-compat
3
+ WORKDIR /app
4
+
5
+ COPY package.json package-lock.json* ./
6
+ RUN npm ci --only=production && npm cache clean --force
7
+
8
+ FROM node:20-alpine AS builder
9
+ WORKDIR /app
10
+
11
+ COPY --from=deps /app/node_modules ./node_modules
12
+ COPY . .
13
+
14
+ # NEXT_PUBLIC_ vars are inlined by next build, so they must exist at build time
15
+ ARG NEXT_PUBLIC_APP_URL
16
+ ENV NEXT_PUBLIC_APP_URL=$NEXT_PUBLIC_APP_URL
17
+
18
+ RUN npm run build
19
+
20
+ FROM node:20-alpine AS runner
21
+ WORKDIR /app
22
+
23
+ ENV NODE_ENV=production
24
+ ENV NEXT_TELEMETRY_DISABLED=1
25
+
26
+ RUN addgroup --system --gid 1001 nodejs
27
+ RUN adduser --system --uid 1001 nextjs
28
+
29
+ COPY --from=builder /app/public ./public
30
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
31
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
32
+
33
+ USER nextjs
34
+
35
+ EXPOSE 3000
36
+ ENV PORT=3000
37
+ ENV HOSTNAME="0.0.0.0"
38
+
39
+ CMD ["node", "server.js"]
@@ -1,32 +1,29 @@
1
- // env.mjs — Zod-validated environment variables
2
- // Crashes at startup if required vars are missing — no silent undefined
3
- import { z } from 'zod';
4
-
5
- const serverSchema = z.object({
6
- API_URL: z.string().url('API_URL must be a valid URL'),
7
- NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
8
- });
9
-
10
- const clientSchema = z.object({
11
- NEXT_PUBLIC_APP_URL: z.string().url().optional(),
12
- });
13
-
14
- // Validate server-side env (only available on server)
15
- const serverEnv = serverSchema.safeParse(process.env);
16
- if (!serverEnv.success) {
17
- console.error('❌ Invalid server environment variables:');
18
- console.error(serverEnv.error.flatten().fieldErrors);
19
- throw new Error('Invalid environment variables');
20
- }
21
-
22
- // Validate client-side env
23
- const clientEnv = clientSchema.safeParse({
24
- NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
25
- });
26
- if (!clientEnv.success) {
27
- console.error('❌ Invalid client environment variables:');
28
- console.error(clientEnv.error.flatten().fieldErrors);
29
- throw new Error('Invalid client environment variables');
30
- }
31
-
32
- export const env = { ...serverEnv.data, ...clientEnv.data };
1
+ import { z } from 'zod';
2
+
3
+ const serverSchema = z.object({
4
+ API_URL: z.string().url('API_URL must be a valid URL'),
5
+ NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
6
+ });
7
+
8
+ const clientSchema = z.object({
9
+ NEXT_PUBLIC_APP_URL: z.string().url().optional(),
10
+ });
11
+
12
+ const serverEnv = serverSchema.safeParse(process.env);
13
+ if (!serverEnv.success) {
14
+ console.error('❌ Invalid server environment variables:');
15
+ console.error(serverEnv.error.flatten().fieldErrors);
16
+ throw new Error('Invalid environment variables');
17
+ }
18
+
19
+ // Listed one by one: Next.js only inlines NEXT_PUBLIC_ vars read by their literal name.
20
+ const clientEnv = clientSchema.safeParse({
21
+ NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
22
+ });
23
+ if (!clientEnv.success) {
24
+ console.error('❌ Invalid client environment variables:');
25
+ console.error(clientEnv.error.flatten().fieldErrors);
26
+ throw new Error('Invalid client environment variables');
27
+ }
28
+
29
+ export const env = { ...serverEnv.data, ...clientEnv.data };
@@ -1,22 +1,21 @@
1
- // app/(dashboard)/{{kebabCase featureName}}/page.tsx
2
- // Server Component — fetches initial data, renders feature component
3
- import { {{pascalCase featureName}}List } from '@/features/{{kebabCase featureName}}';
4
-
5
- async function get{{pascalCase featureName}}List() {
6
- const res = await fetch(`${process.env.API_URL}/api/{{kebabCase featureName}}`, {
7
- next: { revalidate: 30 },
8
- headers: { 'Content-Type': 'application/json' },
9
- });
10
- if (!res.ok) throw new Error('Failed to fetch {{featureName}}');
11
- return res.json();
12
- }
13
-
14
- export default async function {{pascalCase featureName}}Page() {
15
- const data = await get{{pascalCase featureName}}List();
16
- return (
17
- <div className="container py-6">
18
- <h1 className="text-2xl font-bold mb-6">{{pascalCase featureName}}</h1>
19
- <{{pascalCase featureName}}List initialData={data} />
20
- </div>
21
- );
22
- }
1
+ // app/(dashboard)/{{kebabCase featureName}}/page.tsx
2
+ import { {{pascalCase featureName}}List } from '@/features/{{kebabCase featureName}}';
3
+
4
+ async function get{{pascalCase featureName}}List() {
5
+ const res = await fetch(`${process.env.API_URL}/api/{{kebabCase featureName}}`, {
6
+ next: { revalidate: 30 },
7
+ headers: { 'Content-Type': 'application/json' },
8
+ });
9
+ if (!res.ok) throw new Error('Failed to fetch {{featureName}}');
10
+ return res.json();
11
+ }
12
+
13
+ export default async function {{pascalCase featureName}}Page() {
14
+ const data = await get{{pascalCase featureName}}List();
15
+ return (
16
+ <div className="container py-6">
17
+ <h1 className="text-2xl font-bold mb-6">{{pascalCase featureName}}</h1>
18
+ <{{pascalCase featureName}}List initialData={data} />
19
+ </div>
20
+ );
21
+ }