@polymorphism-tech/morph-spec 8.38.0 → 8.41.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 (242) hide show
  1. package/CLAUDE.md +20 -9
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +60 -43
  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 +4 -3
  8. package/framework/MORPH.md +10 -9
  9. package/framework/agents.json +43 -26
  10. package/framework/docs/CLI.md +7 -5
  11. package/framework/evals/rubrics/code-quality.md +4 -4
  12. package/framework/evals/rubrics/condensed.md +2 -2
  13. package/framework/hooks/README.md +1 -1
  14. package/framework/hooks/claude-code/core/telemetry-log.js +39 -8
  15. package/framework/hooks/claude-code/core/worktree-create.js +22 -6
  16. package/framework/hooks/shared/git-paths.js +38 -5
  17. package/framework/mcp/registry.json +58 -66
  18. package/framework/rules/infrastructure-standards.md +0 -1
  19. package/framework/rules/morph-workflow.md +9 -4
  20. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  21. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  22. package/framework/skills/core/morph-apply/SKILL.md +59 -25
  23. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  24. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  25. package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
  26. package/framework/skills/core/morph-implement/SKILL.md +48 -24
  27. package/framework/skills/core/morph-media/SKILL.md +2 -2
  28. package/framework/skills/core/morph-plan/SKILL.md +10 -3
  29. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  30. package/framework/skills/core/morph-review/SKILL.md +1 -1
  31. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  32. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  33. package/framework/skills/core/morph-uiux/SKILL.md +7 -6
  34. package/framework/standards/PROVENANCE.json +4 -4
  35. package/framework/standards/STANDARDS.json +9 -9
  36. package/framework/standards/ai-agents/context-providers.md +16 -26
  37. package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
  38. package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
  39. package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
  40. package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
  41. package/framework/standards/ai-agents/modalities-vision.md +1 -18
  42. package/framework/standards/ai-agents/prompt-sources.md +0 -4
  43. package/framework/standards/architecture/code-comments.md +52 -16
  44. package/framework/standards/backend/dotnet/testing.md +4 -2
  45. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
  46. package/framework/standards/frontend/design-system/ai-image-generation.md +42 -28
  47. package/framework/standards/frontend/design-system/ai-video-generation.md +24 -9
  48. package/framework/standards/frontend/nextjs/testing.md +4 -2
  49. package/framework/standards/integration/mcp/mcp-tools.md +64 -126
  50. package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
  51. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
  52. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
  53. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
  54. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
  55. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
  56. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
  57. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
  58. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
  59. package/framework/templates/code/dotnet/database/migration.cs +49 -83
  60. package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
  61. package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
  62. package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
  63. package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
  64. package/framework/templates/code/dotnet/jobs/job.cs +2 -97
  65. package/framework/templates/code/dotnet/test.cs +2 -87
  66. package/framework/templates/code/typescript/contracts.ts +140 -167
  67. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
  68. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
  69. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
  70. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
  71. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
  72. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
  73. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
  74. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
  75. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
  76. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
  77. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
  78. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
  79. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
  80. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
  81. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
  82. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
  83. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
  84. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
  85. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
  86. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
  87. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
  88. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
  89. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
  90. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
  91. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
  92. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
  93. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
  94. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
  95. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
  96. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
  97. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
  98. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
  99. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
  100. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
  101. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
  102. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
  103. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
  104. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
  105. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
  106. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
  107. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
  108. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
  109. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
  110. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
  111. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
  112. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
  113. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
  114. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
  115. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
  116. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
  117. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
  118. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
  119. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
  120. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
  121. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
  122. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
  123. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
  124. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
  125. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
  126. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
  127. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
  128. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
  129. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
  130. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
  131. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
  132. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
  133. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
  134. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
  135. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
  136. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
  137. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
  138. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
  139. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
  140. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
  141. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
  142. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
  143. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
  144. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
  145. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
  146. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
  147. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
  148. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
  149. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
  150. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
  151. package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
  152. package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
  153. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
  154. package/framework/templates/integrations/asaas-client.cs +332 -387
  155. package/framework/templates/integrations/asaas-webhook.cs +290 -351
  156. package/framework/templates/integrations/neon-auth-config.cs +9 -105
  157. package/framework/templates/project/validate.js +97 -120
  158. package/package.json +2 -2
  159. package/src/commands/dashboard/dashboard.js +80 -53
  160. package/src/commands/mcp/mcp-setup.js +37 -9
  161. package/src/commands/project/doctor.js +12 -6
  162. package/src/commands/project/init.js +6 -3
  163. package/src/commands/project/install-plugin-cmd.js +1 -1
  164. package/src/commands/project/update.js +5 -0
  165. package/src/commands/project/worktree.js +60 -4
  166. package/src/commands/state/gate-check.js +5 -0
  167. package/src/commands/tasks/dag.js +27 -4
  168. package/src/commands/validation/verify.js +22 -38
  169. package/src/dashboard/app/App.jsx +80 -16
  170. package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
  171. package/src/dashboard/app/components/CommandPalette.jsx +155 -0
  172. package/src/dashboard/app/components/DetailPanel.jsx +47 -11
  173. package/src/dashboard/app/components/EntityLink.jsx +55 -0
  174. package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
  175. package/src/dashboard/app/components/FlowGraph.jsx +19 -17
  176. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
  177. package/src/dashboard/app/components/Markdown.jsx +47 -0
  178. package/src/dashboard/app/components/PhaseRail.jsx +111 -0
  179. package/src/dashboard/app/components/SearchFilter.jsx +40 -22
  180. package/src/dashboard/app/components/Sidebar.jsx +146 -37
  181. package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
  182. package/src/dashboard/app/hooks/usePref.js +38 -0
  183. package/src/dashboard/app/lib/format.js +69 -0
  184. package/src/dashboard/app/lib/phases.js +56 -0
  185. package/src/dashboard/app/lib/project.js +50 -0
  186. package/src/dashboard/app/pages/Activity.jsx +24 -0
  187. package/src/dashboard/app/pages/AgentMap.jsx +371 -370
  188. package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
  189. package/src/dashboard/app/pages/Features.jsx +400 -0
  190. package/src/dashboard/app/pages/Guide.jsx +146 -0
  191. package/src/dashboard/app/pages/HookInspector.jsx +357 -322
  192. package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
  193. package/src/dashboard/app/pages/Portfolio.jsx +156 -0
  194. package/src/dashboard/app/pages/Rubrics.jsx +101 -0
  195. package/src/dashboard/app/pages/RulesMap.jsx +300 -273
  196. package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
  197. package/src/dashboard/app/pages/Standards.jsx +407 -332
  198. package/src/dashboard/app/styles/index.css +187 -47
  199. package/src/dashboard/index.html +14 -12
  200. package/src/dashboard/package.json +32 -24
  201. package/src/dashboard/scanner/activity-scanner.js +351 -0
  202. package/src/dashboard/scanner/agents-scanner.js +59 -1
  203. package/src/dashboard/scanner/docs-scanner.js +59 -0
  204. package/src/dashboard/scanner/feature-status.js +10 -0
  205. package/src/dashboard/scanner/features-scanner.js +360 -0
  206. package/src/dashboard/scanner/index.js +14 -7
  207. package/src/dashboard/scanner/phases-scanner.js +14 -15
  208. package/src/dashboard/scanner/portfolio.js +153 -0
  209. package/src/dashboard/scanner/project-api.js +195 -0
  210. package/src/dashboard/scanner/rules-scanner.js +42 -37
  211. package/src/dashboard/scanner/skills-scanner.js +105 -43
  212. package/src/dashboard/scanner/standards-scanner.js +2 -0
  213. package/src/dashboard/vite-plugin-morph-data.js +134 -17
  214. package/src/lib/detectors/claude-config-detector.js +2 -7
  215. package/src/lib/detectors/index.js +5 -1
  216. package/src/lib/evaluator-report.js +22 -0
  217. package/src/lib/generators/settings-generator.js +208 -209
  218. package/src/lib/installers/mcp-installer.js +724 -719
  219. package/src/lib/installers/memory-installer.js +1 -2
  220. package/src/lib/projects/registry.js +154 -0
  221. package/src/lib/risk-detector.js +13 -0
  222. package/src/lib/tasks/tasks-json-parser.js +21 -0
  223. package/src/lib/validators/code-budget/code-budget-validator.js +32 -4
  224. package/src/lib/worktree/env-allocator.js +9 -3
  225. package/src/scripts/install-plugin.js +1 -1
  226. package/src/scripts/setup-infra.js +2 -4
  227. package/src/utils/agents-installer.js +108 -13
  228. package/src/utils/banner.js +1 -1
  229. package/src/commands/index.js +0 -13
  230. package/src/commands/project/checkpoint.js +0 -209
  231. package/src/core/state/phase-state-machine.js +0 -197
  232. package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
  233. package/src/dashboard/app/pages/LiveState.jsx +0 -206
  234. package/src/dashboard/scanner/squads-scanner.js +0 -13
  235. package/src/dashboard/scanner/state-scanner.js +0 -85
  236. package/src/lib/generators/index.js +0 -10
  237. package/src/lib/hooks/hook-executor.js +0 -167
  238. package/src/lib/validators/checkpoint-persistence.js +0 -39
  239. package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
  240. package/src/lib/validators/shared/index.js +0 -12
  241. package/src/lib/validators/shared/issue-counter.js +0 -14
  242. package/src/lib/validators/shared/result-formatter.js +0 -124
@@ -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
@@ -2,10 +2,10 @@
2
2
 
3
3
  > **Scope:** nextjs
4
4
  > **Layer:** 1 (load when relevant)
5
- > **Keywords:** image, gemini, asset, ai-generation, placeholder, mcp, next/image
5
+ > **Keywords:** image, gpt-image, gemini, openrouter, morph-assets, asset, ai-generation, placeholder, mcp, next/image
6
6
  > **Load When:** feature has UI components requiring image assets
7
7
 
8
- **Verified against:** Gemini Image MCP + Next.js 15 `next/image`. Last-verified: 2026-05-20.
8
+ **Verified against:** morph-assets MCP (OpenRouter; default model `openai/gpt-image-2.5-flare`) + Next.js 15 `next/image`. Last-verified: 2026-09-27.
9
9
 
10
10
  ---
11
11
 
@@ -13,7 +13,7 @@
13
13
 
14
14
  | Use Case | Approach | Examples |
15
15
  |----------|----------|---------|
16
- | **AI generation** | Gemini Image MCP | Hero images, brand illustrations, feature-specific visuals, empty states, background panels |
16
+ | **AI generation** | morph-assets MCP | Hero images, brand illustrations, feature-specific visuals, empty states, background panels |
17
17
  | **Stock photos** | External sources | Generic content (people in offices, nature scenes, stock portraits) |
18
18
  | **SVG/CSS** | Code-based | Icons, patterns, gradients, geometric shapes, loading animations |
19
19
  | **Skip** | Other tools | Screenshots (Playwright), diagrams (Mermaid), logos (vector tools) |
@@ -22,7 +22,7 @@
22
22
 
23
23
  ## Brand Fidelity: Reference, Don't Invent
24
24
 
25
- The advantage of an image MCP over generating from scratch is staying consistent with the client's **real** assets. Pass them as reference (`edit_image` with `referenceImages`) rather than generating in a vacuum. A generated look-alike is a different product, and the client sees it immediately.
25
+ The advantage of an image MCP over generating from scratch is staying consistent with the client's **real** assets. Pass them as reference (`edit_image` / `references`, or `@brand` with a `project`) rather than generating in a vacuum. A generated look-alike is a different product, and the client sees it immediately.
26
26
 
27
27
  Two cases where this is a hard rule:
28
28
 
@@ -34,46 +34,60 @@ Two cases where this is a hard rule:
34
34
  Generate the **hero first**. Once approved, use it as the reference for every derived image (detail shots, cards, scene variants), so the whole set shares light, palette and identity instead of looking like separate photo shoots.
35
35
 
36
36
  ```
37
- hero -> [approved] -> derivatives with the approved hero as referenceImages -> [approved per image]
37
+ hero -> [approved] -> derivatives with the approved hero as references -> [approved per image]
38
38
  ```
39
39
 
40
40
  Skipping this chain is the most common cause of a page whose images do not feel like one set.
41
41
 
42
- ## Gemini Image MCP API
42
+ ## morph-assets MCP API
43
+
44
+ One MCP for image and video, all models through OpenRouter. `model` accepts any OpenRouter image model by id, alias (`flare`, `mai flash`, `nano banana 2`, `nano banana pro`) or approximate name.
45
+
46
+ | Role | Model |
47
+ |------|-------|
48
+ | Default (editing with refs, text) | `openai/gpt-image-2.5-flare` |
49
+ | Draft | `microsoft/mai-image-2.6-flash` (max 5 refs) |
50
+ | 2K/4K output or 6-14 references | `google/gemini-3.1-flash-image` |
43
51
 
44
52
  ```javascript
45
53
  // Generate a new image
46
- mcp__gemini_image_mcp__generate_image({
47
- prompt: string, // Text description (max 5000 chars, narrative style)
54
+ mcp__morph-assets__generate_image({
55
+ prompt: string, // Narrative description; copy to render goes between double quotes
56
+ model?: string, // Default: openai/gpt-image-2.5-flare
57
+ models?: string[], // 2-4 models: same request in each, cost per model (to choose)
48
58
  aspectRatio?: "1:1" | "2:3" | "3:2" | "3:4" | "4:3" | "9:16" | "16:9",
59
+ resolution?: "1K" | "2K" | "4K", // Only on models that support it
60
+ variants?: number, // 1-4, returns a contact sheet for the review
61
+ references?: string[], // Paths, URLs, library ids, "@brand", "@brand:name"
62
+ project?: string, // Client project: output dir, brand refs, budget
49
63
  outputPath?: string, // File path to save (e.g., "public/images/feature/hero.png")
50
- model?: string, // Default: gemini-2.5-flash-image
64
+ dryRun?: boolean, // Estimate cost only
65
+ confirmToken?: string, // Repeat a CONFIRMATION_REQUIRED call after approval
51
66
  })
52
67
 
53
- // Edit an existing image
54
- mcp__gemini_image_mcp__edit_image({
55
- imageSource: string, // Path to image or image ID
68
+ // Edit an existing image (the base image goes as the 1st reference)
69
+ mcp__morph-assets__edit_image({
70
+ image: string, // Path to image or library id
56
71
  prompt: string, // Short, targeted edit instructions (1-2 sentences)
72
+ references?: string[],
57
73
  outputPath?: string,
58
74
  })
59
75
 
60
- // Continue editing last image
61
- mcp__gemini_image_mcp__continue_editing({
76
+ // Continue editing the last image of the project
77
+ mcp__morph-assets__continue_editing({
62
78
  prompt: string, // One adjustment at a time
79
+ imageId?: string, // Default: the last image
63
80
  })
64
81
 
65
- // List generated images
66
- mcp__gemini_image_mcp__list_images({
67
- type?: "generated" | "edited" | "all",
68
- limit?: number,
69
- })
82
+ // History and metadata (replaces list_images / get_image_info)
83
+ mcp__morph-assets__library({ op: "list" | "search" | "info" | "tag" | "project_get" | "project_set", ... })
70
84
 
71
- // Get image details
72
- mcp__gemini_image_mcp__get_image_info({
73
- imageId?: string, // Defaults to last image
74
- })
85
+ // Pick a model from the curation (verified vs broken)
86
+ mcp__morph-assets__list_models({ kind: "image", needs?: "refs>=5", task?: string })
75
87
  ```
76
88
 
89
+ **Cost gate:** every call estimates cost first. Above US$2, over the project budget or with an unknown price, the tool returns `CONFIRMATION_REQUIRED` + `confirmToken`; get human approval and repeat the same call with the token. Use `dryRun: true` before large batches or 4K.
90
+
77
91
  ## Prompt Engineering Patterns
78
92
 
79
93
  ### Design-System-Aware Prompts
@@ -101,7 +115,7 @@ Describe what the image will BE in the UI, not just the subject:
101
115
 
102
116
  ### Narrative Style
103
117
 
104
- Gemini responds best to coherent paragraphs. Build prompts by layering:
118
+ GPT Image, Gemini and MAI all respond best to coherent paragraphs. Build prompts by layering:
105
119
 
106
120
  1. **Subject** — hyper-specific description
107
121
  2. **Environment** — foreground, mid-ground, background (layered for depth)
@@ -148,9 +162,9 @@ One change at a time:
148
162
  - "More serious expression"
149
163
 
150
164
  ### Text Rendering
151
- The model frequently misspells words. After generating any image with text:
165
+ Put the exact copy between double quotes in the prompt (`the headline "Summer Sale"`) so the model renders it literally; GPT Image (the default) is the strongest at text. Models can still misspell words. After generating any image with text:
152
166
  1. View the result
153
- 2. If text is wrong: `continue_editing` with "Fix the text — it should read '{correct text}' exactly. Keep everything else."
167
+ 2. If text is wrong: `continue_editing` with "Fix the text — it should read "{correct text}" exactly. Keep everything else."
154
168
  3. Expect 2-3 iterations for correct text
155
169
 
156
170
  ## Aspect Ratio Guide
@@ -267,7 +281,7 @@ For a frame that will drive a scroll scrub, also generate at the exact ratio the
267
281
 
268
282
  ## Variations and Cost
269
283
 
270
- Generate **two or three variations per image**, varying angle, light or composition, **not just the seed**. Seed-only variation produces near-identical options and wastes the review.
284
+ Generate **two or three variations per image** (`variants: 2-3` returns a contact sheet for the review), varying angle, light or composition. Seed is not supported by the GPT Image / Gemini models, so variation has to come from the prompt; near-identical options waste the review. To choose between models or directions, `models: [...]` runs the same request on 2-4 models with the cost of each.
271
285
 
272
286
  If none are good, regenerate at the same step with the prompt adjusted by the feedback. **Do not advance with a weak image**: every derivative, any footage built from it, and the final page all inherit its quality.
273
287
 
@@ -282,7 +296,7 @@ If none are good, regenerate at the same step with the prompt adjusted by the fe
282
296
  - [ ] `priority` prop set for above-the-fold images
283
297
  - [ ] Build compiles after integration (`npm run build`)
284
298
  - [ ] Image visible in dev server at correct route
285
- - [ ] Client assets passed as `referenceImages`; nothing generated in a vacuum
299
+ - [ ] Client assets passed as `references` (or `@brand`); nothing generated in a vacuum
286
300
  - [ ] Real renders and real product photos used as-is, never AI-recreated
287
301
  - [ ] Hero approved first, then used as reference for derivatives
288
302
  - [ ] Images headed for video framed without faces
@@ -2,10 +2,10 @@
2
2
 
3
3
  > **Scope:** frontend-design-system
4
4
  > **Layer:** 3
5
- > **Keywords:** video generation, seedance, img2vid, endImage, directional footage, loop video, E005 moderation, cost ladder
5
+ > **Keywords:** video generation, seedance, morph-assets, openrouter, img2vid, endImage, directional footage, loop video, E005 moderation, cost ladder, promoteFrom
6
6
  > **Load When:** producing footage for a scroll-driven page or animating a client render
7
7
 
8
- **Verified against:** seedance MCP (generate_video, generate_loop_video, get_video, list_videos) over Replicate. Last-verified: 2026-08-21.
8
+ **Verified against:** morph-assets MCP (generate_video, generate_loop_video, get_video, library, media_tool) over OpenRouter; models `bytedance/seedance-1-5-pro` and `bytedance/seedance-2.0`. Last-verified: 2026-09-27.
9
9
 
10
10
  ---
11
11
 
@@ -38,6 +38,13 @@ Give a start image and an end image, and let the model interpolate a camera trav
38
38
  1. Pick or generate the **start frame**: the wide shot, the resting state.
39
39
  2. Pick or generate the **end frame** (`endImage`): the destination of the movement (a detail in close-up, a revealed facade).
40
40
  3. Call `generate_video { prompt, image: start, endImage: end }`. The prompt describes the camera movement between the two.
41
+ 4. Check the **last-frame QA** in the response (SSIM of the real last frame against `endImage`): ✅ respected, ⚠️ does not match, ≈ inconclusive. A ⚠️ means the model ignored `endImage` and the take will not scrub as designed.
42
+
43
+ **Models that respect `endImage` (verified 2026-09-27):** Seedance 1.5 Pro and Seedance 2.0. Use them for scrub.
44
+
45
+ - `seedance-2.0-mini` does **not** respect `endImage` (it overshoots the end frame). Do not use it for scrub.
46
+ - Veo 3.1 Lite/Fast came out **inconclusive** on the last-frame check. Do not rely on them for scrub until re-verified.
47
+ - `aspectRatio: "adaptive"` does not exist on OpenRouter: omit `aspectRatio` to inherit the ratio of the start frame.
41
48
 
42
49
  **Movements that scrub well:**
43
50
 
@@ -52,11 +59,12 @@ Give a start image and an end image, and let the model interpolate a camera trav
52
59
 
53
60
  ## Section 3: Faces Trip Moderation
54
61
 
55
- Img2vid with a **human face** repeatedly fails moderation (error E005). The workaround that works is to **crop the face out of frame** before generating.
62
+ Img2vid with a **human face** repeatedly fails moderation (it surfaced as error E005 on Replicate and still trips moderation through OpenRouter). The workaround that works is to **crop the face out of frame** before generating.
56
63
 
57
64
  - Frame from the neck down (garment, hands, fabric) rather than the whole person. For tailoring, craft and manufacturing this usually improves the shot, because the product becomes the subject.
58
65
  - Hands working (at a cuff, a button, the material) pass reliably and carry the craft story.
59
66
  - If the client insists on a face, generate the **static image** with it (image generation is more permissive) and reserve video for the face-free shots.
67
+ - **Video has no ZDR (zero data retention) on OpenRouter:** the provider may retain the frames. Nothing sensitive in them (documents, screens with client data, identifiable people).
60
68
 
61
69
  ---
62
70
 
@@ -64,8 +72,10 @@ Img2vid with a **human face** repeatedly fails moderation (error E005). The work
64
72
 
65
73
  Every generation costs money and you rarely nail the movement on the first attempt.
66
74
 
67
- 1. **Draft:** fast model, 480p, 4s. Enough to judge framing and movement.
68
- 2. **Final:** only after the draft is approved, regenerate at 1080p. Then re-extract frames and swap them into the build **with a cache-bust** (`?v=N`), or the browser serves the old ones. See `frontend/scroll-driven/frame-scrub.md`.
75
+ 1. **Draft:** the default model (`bytedance/seedance-1-5-pro`), 480p (the default), 4-5s. About US$0.05 per take; enough to judge framing and movement. Ambient loops can stay on this model.
76
+ 2. **Final:** only after the draft is approved, promote it: `generate_video { promoteFrom: "<draft id>", model: "seedance", resolution: "1080p" }` renders `bytedance/seedance-2.0` at 1080p with the **same prompt, frames and seed** (about US$1.50 for 4s). Then re-extract frames and swap them into the build **with a cache-bust** (`?v=N`), or the browser serves the old ones. See `frontend/scroll-driven/frame-scrub.md`.
77
+
78
+ To choose between models before drafting, `models: ["a", "b"]` runs the same request on 2-3 models with the cost of each. Any generation above US$2, over the project budget or with an unknown price returns `CONFIRMATION_REQUIRED` + `confirmToken`: get human approval, then repeat the same call with the token. `dryRun: true` only estimates.
69
79
 
70
80
  Never open at 1080p.
71
81
 
@@ -95,7 +105,11 @@ TECHNICAL: img2vid with image (start) + endImage (end). No audio. No watermark.
95
105
 
96
106
  ## Section 6: Async Contract
97
107
 
98
- `generate_video` and `generate_loop_video` return a `request_id` rather than a file. Poll with `get_video { requestId, outputPath? }`, which downloads the `.mp4` once the job reports COMPLETED. `list_videos { limit? }` reads local job history and spends no credits, which also makes it the safe health check for the MCP.
108
+ `generate_video` and `generate_loop_video` wait up to `waitSeconds` (default 60) and, if the job is still running, return "⏳ generating" with the job id rather than a file. Poll with `get_video { ids? }` (several ids at once; no ids = every pending job), which downloads the `.mp4` once the job completes. **Never resend `generate_video` while a job is running**: an identical request within 15 minutes returns the same job, but a different one is billed again.
109
+
110
+ `library { op: "pending" | "list" | "info" }` reads local history and spends no credits (it replaces the old `list_videos`). `usage { op: "balance" }` checks the OpenRouter key without spending credits, which makes it the safe health check for the MCP.
111
+
112
+ Post-production runs locally with no cost through `media_tool` (ffmpeg): `scrub_frames` (frames + `frames.json`, each re-extraction creates a new `v{N}` folder, which doubles as the cache-bust), `poster` (static image for mobile), `flip` (hflip when the subject collides with the copy), `concat`, `compress` (web background) and `picker` (the side-by-side approval page for the gate).
99
113
 
100
114
  ---
101
115
 
@@ -104,15 +118,16 @@ TECHNICAL: img2vid with image (start) + endImage (end). No audio. No watermark.
104
118
  - [ ] Directional footage (start differs from end) for anything scroll-driven
105
119
  - [ ] Seamless loop used only for a self-playing ambient plane
106
120
  - [ ] Client renders animated, never AI-recreated
107
- - [ ] Faces cropped out of frame before img2vid
121
+ - [ ] Faces cropped out of frame before img2vid; nothing sensitive in the frames (no ZDR)
122
+ - [ ] Scrub footage generated on a model that respects `endImage` (Seedance 1.5 Pro / 2.0, never `seedance-2.0-mini`) and the last-frame QA came back ✅
108
123
  - [ ] Movement chosen from the scrub-friendly list, no orbits or returns
109
124
  - [ ] Prompt reserves a calm region for copy, subject opposite the text
110
- - [ ] Draft at 480p/4s approved before any 1080p render
125
+ - [ ] Draft at 480p approved before any 1080p render, and the final made with `promoteFrom`
111
126
  - [ ] Frames re-extracted with a bumped cache-bust after the final render
112
127
 
113
128
  ## Boundary: Runtime Generation Is Another Standard
114
129
 
115
- This standard is **authoring time**: a human driving the seedance MCP in a session, footage that ends
130
+ This standard is **authoring time**: a human driving the morph-assets MCP in a session, footage that ends
116
131
  up committed as a page asset. When the **product itself** generates video at runtime — a .NET worker
117
132
  creating a paid task, polling an operation, downloading bytes into its own blob store — the standard
118
133
  is `ai-agents/media-video.md` (Veo via `Google.GenAI`, the HTTP-only providers, cost per second, the
@@ -116,8 +116,10 @@ happy path + one error path per component above is that budget applied to a comp
116
116
  - **Shared helpers by default.** Reuse the existing wrappers (`test/helpers.tsx`), MSW handlers and
117
117
  factories. Before writing a new one, look for it (`morph-spec graph explain <Name>`; fallback
118
118
  without a graph: `grep -rn`).
119
- - **TDD where it pays:** a bug gets its regression test first, and so does pure logic (formatters,
120
- reducers, schema). Component and integration tests may come after the code.
119
+ - **Order is free, proof is not the implementer's:** a bug gets a regression test, and pure logic
120
+ (formatters, reducers, schema) gets its tests, in whatever order the code comes out. The
121
+ implementer does not run red→green cycles nor mutate production code to watch a test fail; it
122
+ runs its own filter once before the report, and the Gate 3 mutation proof shows the test bites.
121
123
 
122
124
  ## Common Mistakes
123
125