@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
@@ -58,17 +58,11 @@ public sealed class TextToSpeechService
58
58
 
59
59
  public TextToSpeechService(IConfiguration config)
60
60
  {
61
- // tts-1: mais rápido, menor qualidade. tts-1-hd: mais lento, maior qualidade.
62
61
  var model = config["TTS:Model"] ?? "tts-1";
63
62
  var apiKey = config["OpenAI:ApiKey"]!;
64
63
  _client = new AudioClient(model, apiKey);
65
64
  }
66
65
 
67
- /// <summary>
68
- /// Converte texto em áudio e retorna bytes MP3.
69
- /// Cachear o resultado é altamente recomendado para textos repetitivos — gerar o mesmo texto
70
- /// duas vezes é desperdício de custo e latência.
71
- /// </summary>
72
66
  public async Task<BinaryData> SynthesizeAsync(
73
67
  string text,
74
68
  GeneratedSpeechVoice? voice = null,
@@ -91,14 +85,13 @@ public sealed class TextToSpeechService
91
85
  }
92
86
  ```
93
87
 
94
- **Registro DI:**
88
+ **Registro DI** (`Program.cs`):
95
89
 
96
90
  ```csharp
97
- // Program.cs
98
91
  builder.Services.AddSingleton<TextToSpeechService>();
99
92
  ```
100
93
 
101
- **Servindo como download ou stream:**
94
+ **Servindo como download ou stream** — verifique o cache antes de gerar (ver Anti-patterns):
102
95
 
103
96
  ```csharp
104
97
  public class AudioController(TextToSpeechService tts) : ControllerBase
@@ -106,14 +99,12 @@ public class AudioController(TextToSpeechService tts) : ControllerBase
106
99
  [HttpPost("synthesize")]
107
100
  public async Task<IActionResult> Synthesize([FromBody] SynthesizeRequest req)
108
101
  {
109
- // Verificar cache antes de gerar (ver Anti-patterns)
110
102
  BinaryData audio = await tts.SynthesizeAsync(
111
103
  text: req.Text,
112
104
  voice: GeneratedSpeechVoice.Nova,
113
105
  format: GeneratedSpeechFormat.Mp3,
114
106
  speedRatio: 1.1f);
115
107
 
116
- // the storage layer may assign the final name; use a timestamp-based name here
117
108
  var fileName = $"tts-{DateTime.UtcNow:yyyyMMddHHmmss}.mp3";
118
109
  return File(audio.ToStream(), "audio/mpeg", fileName);
119
110
  }
@@ -31,7 +31,6 @@ Vision em MAF usa `ChatMessage` multimodal — a mesma abstração `IChatClient`
31
31
  ```csharp
32
32
  using Microsoft.Extensions.AI;
33
33
 
34
- // Constrói um ChatMessage com texto + imagem
35
34
  ChatMessage message = new(ChatRole.User, [
36
35
  new TextContent("O que aparece nesta imagem?"),
37
36
  new UriContent(new Uri("https://example.com/foto.jpg"), "image/jpeg")
@@ -59,7 +58,7 @@ O `IChatClient` (e portanto `AIAgent`) aceita `IEnumerable<ChatMessage>` — voc
59
58
 
60
59
  ## Exemplo C# completo
61
60
 
62
- Agente de visão que recebe URL de imagem e retorna descrição. Para extração de dados tipados de um documento, combine com `RunAsync<T>` (ver comentário inline).
61
+ Agente de visão que recebe URL de imagem e retorna descrição. Três entradas: URL pública, arquivo local via `DataContent.LoadFromAsync` (o padrão canônico MAF, preferido sempre que houver o caminho do arquivo) e bytes já em memória (ex: upload de usuário já lido). Para extração de dados tipados de um documento, combine com `RunAsync<T>` em vez de `RunAsync` — `AgentResponse<InvoiceData> r = await _agent.RunAsync<InvoiceData>(message);` e `return r.Result;` —, com o padrão completo em `ai-agents-structured-output`.
63
62
 
64
63
  ```csharp
65
64
  using Microsoft.Agents.AI;
@@ -84,7 +83,6 @@ public sealed class VisionAgent
84
83
  });
85
84
  }
86
85
 
87
- /// <summary>Analisa uma imagem por URL pública e retorna descrição em texto.</summary>
88
86
  public async Task<string> AnalyzeImageUrlAsync(Uri imageUri, string question, CancellationToken ct = default)
89
87
  {
90
88
  ChatMessage message = new(ChatRole.User, [
@@ -96,14 +94,6 @@ public sealed class VisionAgent
96
94
  return response.Text;
97
95
  }
98
96
 
99
- /// <summary>
100
- /// Analisa imagem a partir de um arquivo local ou caminho de upload.
101
- /// Usa DataContent.LoadFromAsync — padrão canônico MAF para carregar imagem de arquivo.
102
- /// Para extrair dados tipados, use RunAsync&lt;T&gt; em vez de RunAsync:
103
- /// AgentResponse&lt;InvoiceData&gt; r = await _agent.RunAsync&lt;InvoiceData&gt;(message);
104
- /// return r.Result;
105
- /// Veja ai-agents-structured-output para o padrão completo.
106
- /// </summary>
107
97
  public async Task<string> AnalyzeImageFileAsync(string filePath, string question, CancellationToken ct = default)
108
98
  {
109
99
  DataContent imageContent = await DataContent.LoadFromAsync(filePath);
@@ -116,10 +106,6 @@ public sealed class VisionAgent
116
106
  return response.Text;
117
107
  }
118
108
 
119
- /// <summary>
120
- /// Analisa imagem a partir de bytes já em memória (ex: upload de usuário já lido).
121
- /// Use AnalyzeImageFileAsync quando tiver o caminho do arquivo — é o padrão preferido.
122
- /// </summary>
123
109
  public async Task<string> AnalyzeImageBytesAsync(BinaryData imageData, string mediaType, string question, CancellationToken ct = default)
124
110
  {
125
111
  ChatMessage message = new(ChatRole.User, [
@@ -138,17 +124,14 @@ public sealed class VisionAgent
138
124
  ```csharp
139
125
  var agent = new VisionAgent(modelRegistry);
140
126
 
141
- // Por URL pública
142
127
  string desc = await agent.AnalyzeImageUrlAsync(
143
128
  new Uri("https://cdn.example.com/invoice-2024.jpg"),
144
129
  "Extraia os campos: número da nota, valor total e data de emissão.");
145
130
 
146
- // Por arquivo local — padrão canônico com DataContent.LoadFromAsync
147
131
  string desc2 = await agent.AnalyzeImageFileAsync(
148
132
  "nota-fiscal.png",
149
133
  "Liste todos os itens e valores desta nota fiscal.");
150
134
 
151
- // Por bytes em memória (ex: upload já lido de IFormFile)
152
135
  BinaryData data = await BinaryData.FromStreamAsync(formFile.OpenReadStream());
153
136
  string desc3 = await agent.AnalyzeImageBytesAsync(data, "image/png",
154
137
  "Descreva o conteúdo da imagem enviada.");
@@ -113,10 +113,6 @@ Interface contract:
113
113
  ```csharp
114
114
  public interface IPromptRepository
115
115
  {
116
- /// <summary>
117
- /// Returns the content of the currently active prompt for the given agent key.
118
- /// Throws <see cref="InvalidOperationException"/> if no active prompt exists.
119
- /// </summary>
120
116
  Task<string> GetActivePromptAsync(string agentKey, CancellationToken ct = default);
121
117
  }
122
118
  ```
@@ -12,7 +12,8 @@
12
12
  ## Fronteira com o testing genérico
13
13
 
14
14
  `backend-dotnet-testing` continua dono do que é de teste em .NET: xUnit, `FakeTimeProvider`,
15
- isolamento entre casos, nomes de teste, organização de fixtures. **Nada disso se repete aqui.**
15
+ isolamento entre casos, nomes de teste, organização de fixtures e o orçamento de teste por task.
16
+ **Nada disso se repete aqui.**
16
17
 
17
18
  Este standard cobre **só o que é de agente** — e o que é de agente cabe em uma frase: o único ponto
18
19
  em que o seu código toca um provider é um `IChatClient`. Todo o resto deste arquivo é consequência
@@ -0,0 +1,100 @@
1
+ # Comentários no código
2
+
3
+ > **Scope:** universal
4
+ > **Layer:** 0 (always load)
5
+ > **Keywords:** comentário, comentario, comment, docstring, jsdoc, xml doc, história da task, código autoexplicativo
6
+ > **Load When:** escrevendo ou revisando código em qualquer stack
7
+
8
+ **Verified against:** C#, TypeScript e JavaScript (convenção de linguagem, sem pacote externo). Last-verified: 2026-09-24.
9
+
10
+ ---
11
+
12
+ **O padrão é nenhum comentário.** O código se explica pelos nomes, pelos tipos e pelos testes. Um
13
+ comentário é relido a cada leitura do arquivo, por quem mantém e pelo agente que abre o arquivo na
14
+ próxima task, e ninguém o atualiza quando o código muda. Ele só entra quando diz algo que o código
15
+ **não consegue** dizer, e isso é raro: a maioria dos arquivos não tem nenhum.
16
+
17
+ ## Antes de escrever um comentário
18
+
19
+ Tente, nesta ordem, e pare no primeiro que resolve:
20
+
21
+ | Em vez de comentar... | Faça |
22
+ |---|---|
23
+ | o que uma variável ou função é | renomeie: `retryDelayMs`, `isEligibleForRefund()` |
24
+ | o que um trecho faz | extraia uma função com esse nome |
25
+ | um número ou string mágica | extraia uma constante nomeada |
26
+ | um estado que não pode acontecer | expresse no tipo (enum, união, record, `required`) |
27
+ | o que o código garante | escreva um teste que falha quando deixar de valer |
28
+
29
+ Só sobrando algo que nenhum desses expressa, o comentário entra.
30
+
31
+ ## O que merece comentário
32
+
33
+ Só o **porquê que o código não mostra**:
34
+
35
+ | Merece | Exemplo |
36
+ |---|---|
37
+ | Invariante que o tipo não expressa | "a chave é a identidade e o hash é a versão" |
38
+ | Ordem que importa | "invalidar o cache ANTES de publicar: o consumidor relê na hora" |
39
+ | Peculiaridade externa | API de terceiro que devolve 200 com erro no corpo; bug de biblioteca, com o link |
40
+ | Contrato de pacote publicado | `///` ou JSDoc só em API de pacote consumido **fora** deste repositório (NuGet, npm) |
41
+
42
+ ## O que não merece
43
+
44
+ - **História de processo.** Task, UC, ADR, FR, Gate, avaliador, `spec.md`, mockup, "confirmado ao
45
+ implementar a T11", "o Gate 3 pediu". Isso vai na mensagem de commit e no `decisions.md`, onde tem
46
+ data e autor e não envelhece dentro do arquivo.
47
+ - **Repetição do código.** `// incrementa o contador` sobre `count++`, `// valida o request` sobre
48
+ `validator.Validate(request)`. Nome bom substitui comentário.
49
+ - **Doc-comment que repete a assinatura.** `/// <summary>Obtém o pedido.</summary>` sobre
50
+ `GetOrder()`, JSDoc com `@param id o id`. Membro de aplicação (handler, endpoint, componente,
51
+ serviço interno) não leva `///` nem JSDoc, mesmo sendo `public`.
52
+ - **Estrutura e decoração.** Cabeçalho de arquivo, banner de seção (`// ---- Handlers ----`,
53
+ `#region`), comentário que só rotula o bloco seguinte.
54
+ - **Código comentado e TODO solto.** O git guarda o código antigo; pendência vira issue, e o
55
+ comentário, se ficar, cita o número dela.
56
+ - **Estado do mundo fora do arquivo.** "Public Sans é a fonte da interface" passa a mentir na
57
+ primeira troca de fonte, e nada avisa.
58
+ - **Afirmação sobre comportamento.** "nunca retorna vazio", "ordenado por sobrenome": isso é
59
+ asserção de teste. A prosa não falha quando deixa de ser verdade; o teste falha.
60
+
61
+ ## Tamanho
62
+
63
+ Uma linha é o normal; teto de ~3. Acima disso o comentário justifica o volume (algoritmo não
64
+ óbvio, armadilha de terceiro com a reprodução). Bloco longo narrando decisões é `decisions.md` com
65
+ outro nome. Em volume, código de aplicação fica perto de zero: mais de uma linha de comentário a
66
+ cada dez de código é sinal de que os nomes não estão fazendo o trabalho.
67
+
68
+ ## Exemplo
69
+
70
+ ```ts
71
+ // Ruim: rotula, conta a task, repete o código e afirma comportamento que nenhum teste cobra.
72
+ /**
73
+ * Salva o documento.
74
+ * @param doc o documento
75
+ */
76
+ // T8 + T11: mutação obrigatória do relatório, item c. Faz upsert e nunca duplica.
77
+ async function save(doc: Doc) {
78
+ await upsert({ key: doc.sourceId, hash: sha256(doc.body) });
79
+ }
80
+
81
+ // Bom: sem comentário nenhum, porque o nome e a chamada já dizem tudo...
82
+ async function save(doc: Doc) {
83
+ await upsert({ key: doc.sourceId, hash: sha256(doc.body) });
84
+ }
85
+
86
+ // ...e um comentário só quando há um porquê invisível.
87
+ // A chave é a identidade e o hash é a versão: hash novo na mesma chave substitui.
88
+ await upsert({ key: doc.sourceId, hash: sha256(doc.body) });
89
+ ```
90
+
91
+ ## Na revisão
92
+
93
+ Comentário que o código dispensava é achado da rubrica `code-quality` no Gate 3. O nó `validators`
94
+ do `morph-spec verify` mede, nas linhas que a feature adicionou (validador `code-budget`), quantas
95
+ linhas de comentário citam processo e a densidade de comentário sobre o código adicionado, e avisa
96
+ com os números e os arquivos mais comentados.
97
+
98
+ ---
99
+
100
+ *MORPH-SPEC by Polymorphism Tech*
@@ -113,6 +113,28 @@ respectively and the test suite stays green.
113
113
 
114
114
  ---
115
115
 
116
+ ## Test budget
117
+
118
+ The plan (`morph-plan`) gives each task a budget: **one test per postcondition, plus one
119
+ regression test per bug fixed**. Going above it takes a one-line justification in the task.
120
+
121
+ - **Table before copy.** Scenarios of the same behavior — including its edge cases (null, empty,
122
+ boundary) — are rows of one `[Theory]` (`[InlineData]`/`[MemberData]`), never N `[Fact]` methods
123
+ that differ by one value.
124
+ - **No test that only pins text.** An assertion that is only the presence or absence of a string in
125
+ UI copy, tenant config or prose proves the file's version, not behavior. Enforce it with an
126
+ analyzer, a lint rule or a validation at publish time, outside the suite count. The versioned
127
+ prompt case (at most one structural test) is `ai-agents-testing-ai`.
128
+ - **Shared fixture by default.** Reuse the project's `WebApplicationFactory` fixture, fakes and
129
+ builders. Before writing a new helper, look for the existing one (`morph-spec graph explain
130
+ <Name>`; fallback without a graph: `grep -rn`).
131
+ - **TDD where it pays:** a bug gets its regression test first (red, then the fix), and so does pure
132
+ logic. Integration and endpoint tests may come after the code.
133
+ - **A surviving mutant** of the Gate 3 mutation proof is killed by tightening an existing assertion
134
+ or adding a table row, not by one new test per mutant.
135
+
136
+ ---
137
+
116
138
  ## Anti-Patterns
117
139
 
118
140
  | Do NOT | Do Instead |
@@ -125,16 +125,19 @@ real sign-in attempt, not after debugging a mysterious "session never sticks" bu
125
125
 
126
126
  ### SDK Initialization
127
127
 
128
+ `lib/auth/client.ts` — `createAuthClient` takes the public auth URL as a positional argument, and the
129
+ React hooks are re-exported from the client instance for ergonomic imports:
130
+
128
131
  ```typescript
129
- // lib/auth/client.ts
130
132
  'use client';
131
133
  import { createAuthClient } from '@neondatabase/auth/next';
132
- // createAuthClient takes the public auth URL as a positional argument
133
134
  export const authClient = createAuthClient(process.env.NEXT_PUBLIC_AUTH_URL!);
134
- // Re-export the React hooks from the client instance for ergonomic imports
135
135
  export const { useSession } = authClient;
136
+ ```
137
+
138
+ `lib/auth/server.ts`:
136
139
 
137
- // lib/auth/server.ts
140
+ ```typescript
138
141
  import { createNeonAuth } from '@neondatabase/auth/next/server';
139
142
  export const auth = createNeonAuth({
140
143
  baseUrl: process.env.NEON_AUTH_BASE_URL!,
@@ -152,8 +155,9 @@ Import the Tailwind styles for the UI components in `globals.css`:
152
155
  @import '@neondatabase/auth-ui/tailwind';
153
156
  ```
154
157
 
158
+ `app/providers.tsx`:
159
+
155
160
  ```tsx
156
- // app/providers.tsx
157
161
  'use client';
158
162
  import { NeonAuthUIProvider } from '@neondatabase/auth-ui';
159
163
  import { authClient } from '@/lib/auth/client';
@@ -169,8 +173,9 @@ export function Providers({ children }: { children: React.ReactNode }) {
169
173
 
170
174
  ### Auth API Route
171
175
 
176
+ `app/api/auth/[...path]/route.ts`:
177
+
172
178
  ```typescript
173
- // app/api/auth/[...path]/route.ts
174
179
  import { auth } from '@/lib/auth/server';
175
180
 
176
181
  export const { GET, POST } = auth.handler();
@@ -182,8 +187,9 @@ export const { GET, POST } = auth.handler();
182
187
 
183
188
  ### Sign In / Sign Up Pages
184
189
 
190
+ `app/auth/sign-in/page.tsx`:
191
+
185
192
  ```tsx
186
- // app/auth/sign-in/page.tsx
187
193
  import { AuthView } from '@neondatabase/auth-ui';
188
194
 
189
195
  export default function SignInPage() {
@@ -193,8 +199,11 @@ export default function SignInPage() {
193
199
  </div>
194
200
  );
195
201
  }
202
+ ```
203
+
204
+ `app/auth/sign-up/page.tsx`:
196
205
 
197
- // app/auth/sign-up/page.tsx
206
+ ```tsx
198
207
  import { AuthView } from '@neondatabase/auth-ui';
199
208
 
200
209
  export default function SignUpPage() {
@@ -208,8 +217,9 @@ export default function SignUpPage() {
208
217
 
209
218
  ### Server-Side Auth Check
210
219
 
220
+ `app/dashboard/page.tsx`:
221
+
211
222
  ```typescript
212
- // app/dashboard/page.tsx
213
223
  import { auth } from '@/lib/auth/server';
214
224
  import { redirect } from 'next/navigation';
215
225
 
@@ -228,8 +238,9 @@ export default async function DashboardPage() {
228
238
 
229
239
  ### Client Component with User Data
230
240
 
241
+ `components/UserProfile.tsx`:
242
+
231
243
  ```tsx
232
- // components/UserProfile.tsx
233
244
  'use client';
234
245
  import { useSession } from '@/lib/auth/client';
235
246
 
@@ -249,14 +260,15 @@ export function UserProfile() {
249
260
 
250
261
  ### Route Protection (Middleware)
251
262
 
263
+ `middleware.ts`. The `runtime = 'nodejs'` line is **mandatory** for any `middleware.ts` that touches
264
+ Neon Auth: Next.js middleware runs on the Edge runtime by default, and the network call the Neon Auth
265
+ SDK makes to mint the session-data cookie times out inside the Edge sandbox on `next dev` — the request
266
+ silently bounces back to sign-in even with a fully valid session, with no error surfaced.
267
+
252
268
  ```typescript
253
- // middleware.ts
254
269
  import { auth } from '@/lib/auth/server';
255
270
 
256
- // MANDATORY for any middleware.ts that touches Neon Auth. Next.js middleware runs on the Edge
257
- // runtime by default, and the network call the Neon Auth SDK makes to mint the session-data cookie
258
- // times out inside the Edge sandbox on `next dev` — the request silently bounces back to sign-in
259
- // even with a fully valid session, with no error surfaced. Force the Node.js runtime instead.
271
+ // Mandatory: on the Edge runtime the SDK's session-cookie call times out and silently bounces to sign-in.
260
272
  export const runtime = 'nodejs';
261
273
 
262
274
  export default auth.middleware({
@@ -294,7 +306,6 @@ export default function ProtectedPage() {
294
306
  ## OAuth (Social Login)
295
307
 
296
308
  ```typescript
297
- // Trigger OAuth sign-in
298
309
  await authClient.signIn.social({
299
310
  provider: 'google',
300
311
  callbackURL: 'http://localhost:3000/auth/callback',
@@ -307,8 +318,9 @@ Configure OAuth providers in the Neon Console under Auth > Configuration.
307
318
 
308
319
  ## API Route Protection
309
320
 
321
+ `app/api/protected/route.ts`:
322
+
310
323
  ```typescript
311
- // app/api/protected/route.ts
312
324
  import { auth } from '@/lib/auth/server';
313
325
 
314
326
  export const dynamic = 'force-dynamic';
@@ -348,34 +360,38 @@ backend's real prefix and the proxy's public prefix don't happen to match charac
348
360
  because this is a runtime routing mismatch (404, or the proxy calling itself), it **never shows up at
349
361
  build time**, only when a human clicks the page.
350
362
 
363
+ `lib/api/prefixes.ts` — `BACKEND_API_PREFIX` is used ONLY by Server Components / Route Handlers
364
+ calling the .NET backend directly (e.g. `https://api.example.com`; the backend's own routes have NO
365
+ `/api` prefix); `PROXY_API_PREFIX` is used ONLY by Client Components calling the same-origin Next.js
366
+ proxy, and matches `app/api/proxy/[...path]/route.ts`:
367
+
351
368
  ```typescript
352
- // lib/api/prefixes.ts
353
- // BACKEND_API_PREFIX: used ONLY by Server Components / Route Handlers calling the .NET backend directly.
354
- export const BACKEND_API_PREFIX = process.env.API_URL; // e.g. https://api.example.com — backend's own routes have NO /api prefix
369
+ export const BACKEND_API_PREFIX = process.env.API_URL;
355
370
 
356
- // PROXY_API_PREFIX: used ONLY by Client Components calling the same-origin Next.js proxy.
357
- export const PROXY_API_PREFIX = '/api/proxy'; // matches app/api/proxy/[...path]/route.ts
371
+ export const PROXY_API_PREFIX = '/api/proxy';
358
372
  ```
359
373
 
374
+ The two call sites below — `app/(dashboard)/catalog/page.tsx`, a Server Component calling the backend
375
+ directly, and `features/catalog/hooks/use-items.ts`, a Client Component going through the same-origin
376
+ proxy — MUST resolve to the same backend route:
377
+
360
378
  ```typescript
361
- // app/(dashboard)/catalog/page.tsx — Server Component, direct backend call
362
379
  import { BACKEND_API_PREFIX } from '@/lib/api/prefixes';
363
380
 
364
381
  async function getItems() {
365
- // cross-reference: PROXY_API_PREFIX below MUST resolve to the same backend route
366
382
  const res = await fetch(`${BACKEND_API_PREFIX}/catalog/items`, { next: { revalidate: 60 } });
367
383
  return res.json();
368
384
  }
369
385
  ```
370
386
 
387
+ `features/catalog/hooks/use-items.ts`:
388
+
371
389
  ```typescript
372
- // features/catalog/hooks/use-items.ts — Client Component, via same-origin proxy
373
390
  import { PROXY_API_PREFIX } from '@/lib/api/prefixes';
374
391
 
375
392
  export function useItems() {
376
393
  return useQuery({
377
394
  queryKey: ['catalog', 'items'],
378
- // cross-reference: BACKEND_API_PREFIX above MUST resolve to the same backend route
379
395
  queryFn: async () => (await fetch(`${PROXY_API_PREFIX}/catalog/items`)).json(),
380
396
  });
381
397
  }
@@ -435,31 +451,30 @@ Before marking any task that adds or touches an endpoint as done:
435
451
 
436
452
  ### JWT Bearer Configuration
437
453
 
454
+ `Infrastructure/Auth/NeonAuthOptions.cs` — `BaseUrl` is the Neon Auth Base URL, e.g.
455
+ `https://ep-xxx.neonauth.us-east-2.aws.neon.build/neondb/auth`:
456
+
438
457
  ```csharp
439
- // Infrastructure/Auth/NeonAuthOptions.cs
440
458
  namespace {ProjectName}.Infrastructure.Auth;
441
459
 
442
460
  public sealed class NeonAuthOptions
443
461
  {
444
462
  public const string SectionName = "NeonAuth";
445
463
 
446
- /// <summary>Neon Auth Base URL, e.g. https://ep-xxx.neonauth.us-east-2.aws.neon.build/neondb/auth</summary>
447
464
  public string BaseUrl { get; set; } = string.Empty;
448
465
  }
449
466
  ```
450
467
 
468
+ `Infrastructure/Auth/NeonAuthJwksProvider.cs` fetches and caches Neon Auth's raw JWKS
469
+ (`/.well-known/jwks.json` — there is no OIDC discovery document) for 15 minutes; an unknown `kid`
470
+ forces one refresh, to survive key rotation without waiting out the TTL:
471
+
451
472
  ```csharp
452
- // Infrastructure/Auth/NeonAuthJwksProvider.cs
453
473
  namespace {ProjectName}.Infrastructure.Auth;
454
474
 
455
475
  using Microsoft.Extensions.Options;
456
476
  using Microsoft.IdentityModel.Tokens;
457
477
 
458
- /// <summary>
459
- /// Fetches and caches Neon Auth's raw JWKS (/.well-known/jwks.json — there is no OIDC discovery
460
- /// document). Cached for 15 minutes; a forced refresh happens once when an unknown `kid` is seen,
461
- /// to survive key rotation without waiting out the TTL.
462
- /// </summary>
463
478
  public sealed class NeonAuthJwksProvider(HttpClient httpClient, IOptions<NeonAuthOptions> options)
464
479
  {
465
480
  private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(15);
@@ -473,7 +488,7 @@ public sealed class NeonAuthJwksProvider(HttpClient httpClient, IOptions<NeonAut
473
488
  var key = keySet.Keys.FirstOrDefault(k => k.Kid == kid);
474
489
  if (key is not null) return key;
475
490
 
476
- // Unknown kid — could be key rotation. Force a single refresh before giving up.
491
+ // Unknown kid may be key rotation: refresh once before giving up.
477
492
  keySet = await GetKeySetAsync(forceRefresh: true, cancellationToken);
478
493
  return keySet.Keys.FirstOrDefault(k => k.Kid == kid);
479
494
  }
@@ -503,8 +518,9 @@ public sealed class NeonAuthJwksProvider(HttpClient httpClient, IOptions<NeonAut
503
518
  }
504
519
  ```
505
520
 
521
+ `Infrastructure/Auth/NeonAuthServiceExtensions.cs`:
522
+
506
523
  ```csharp
507
- // Infrastructure/Auth/NeonAuthServiceExtensions.cs
508
524
  namespace {ProjectName}.Infrastructure.Auth;
509
525
 
510
526
  using System.Text;
@@ -527,9 +543,7 @@ public static class NeonAuthServiceExtensions
527
543
  services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
528
544
  .AddJwtBearer(options =>
529
545
  {
530
- // Do NOT set options.Authority — Neon Auth serves no /.well-known/openid-configuration,
531
- // so Authority-driven metadata refresh has nothing valid to fetch. ValidIssuer is set
532
- // explicitly below instead.
546
+ // No options.Authority: Neon Auth serves no OIDC discovery document, so ValidIssuer is explicit.
533
547
  options.TokenValidationParameters = new TokenValidationParameters
534
548
  {
535
549
  ValidateIssuer = true,
@@ -537,14 +551,12 @@ public static class NeonAuthServiceExtensions
537
551
  ValidateAudience = false,
538
552
  ValidateLifetime = true,
539
553
  NameClaimType = "sub",
540
- // Trust in the signing key is established entirely inside SignatureValidator
541
- // below (fetched from our own cached JWKS) — this flag doesn't gate that path.
554
+ // Key trust is established inside SignatureValidator below; this flag doesn't gate that path.
542
555
  ValidateIssuerSigningKey = false,
543
556
  };
544
557
  });
545
558
 
546
- // A second Configure<TDep> pass with DI available — AddJwtBearer's own setup has already run,
547
- // so this only *adds* the SignatureValidator to the TokenValidationParameters built above.
559
+ // Runs after AddJwtBearer's own setup, with DI available: it only adds the SignatureValidator.
548
560
  services.AddOptions<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme)
549
561
  .Configure<NeonAuthJwksProvider>((options, jwks) =>
550
562
  {
@@ -561,9 +573,7 @@ public static class NeonAuthServiceExtensions
561
573
  if (string.IsNullOrEmpty(kid))
562
574
  throw new SecurityTokenInvalidSignatureException("Token is missing 'kid'.");
563
575
 
564
- // Blocking call: JWKS is cached in-memory (see NeonAuthJwksProvider), so this is
565
- // I/O-free on the hot path. SignatureValidator has no async overload in this
566
- // library version — only a cold-start / key-rotation miss ever awaits real I/O here.
576
+ // Blocking is safe: SignatureValidator has no async overload and the JWKS is cached in memory.
567
577
  var jwk = jwks.GetKeyAsync(kid, CancellationToken.None).GetAwaiter().GetResult()
568
578
  ?? throw new SecurityTokenInvalidSignatureException($"No JWKS key found for kid '{kid}'.");
569
579
 
@@ -585,10 +595,7 @@ public static class NeonAuthServiceExtensions
585
595
  if (!verifier.VerifySignature(signature))
586
596
  throw new SecurityTokenInvalidSignatureException("Ed25519 signature verification failed.");
587
597
 
588
- // MUST return Microsoft.IdentityModel.JsonWebTokens.JsonWebToken — ASP.NET Core 8+
589
- // uses JsonWebTokenHandler internally. Returning a
590
- // System.IdentityModel.Tokens.Jwt.JwtSecurityToken here fails with IDX10506
591
- // ("SignatureValidator returned a token of an unexpected type").
598
+ // Must be JsonWebToken: ASP.NET Core 8+ rejects a JwtSecurityToken here with IDX10506.
592
599
  return jwt;
593
600
  };
594
601
  });
@@ -600,6 +607,12 @@ public static class NeonAuthServiceExtensions
600
607
  }
601
608
  ```
602
609
 
610
+ Two details of the validator above: it returns `Microsoft.IdentityModel.JsonWebTokens.JsonWebToken`
611
+ because ASP.NET Core 8+ uses `JsonWebTokenHandler` internally — returning a
612
+ `System.IdentityModel.Tokens.Jwt.JwtSecurityToken` fails with IDX10506 ("SignatureValidator returned a
613
+ token of an unexpected type"); and the blocking `GetResult()` is I/O-free on the hot path, since only a
614
+ cold-start or key-rotation miss ever awaits real I/O.
615
+
603
616
  ### Protected Endpoints
604
617
 
605
618
  ```csharp