@polymorphism-tech/morph-spec 8.40.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.
- package/CLAUDE.md +2 -2
- package/README.md +2 -2
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +1 -1
- package/framework/MORPH.md +9 -8
- package/framework/agents.json +8 -2
- package/framework/docs/CLI.md +5 -3
- package/framework/hooks/README.md +1 -1
- package/framework/hooks/claude-code/core/telemetry-log.js +39 -8
- package/framework/hooks/claude-code/core/worktree-create.js +22 -6
- package/framework/hooks/shared/git-paths.js +38 -5
- package/framework/mcp/registry.json +26 -65
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +55 -25
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
- package/framework/skills/core/morph-implement/SKILL.md +43 -24
- package/framework/skills/core/morph-media/SKILL.md +2 -2
- package/framework/skills/core/morph-plan/SKILL.md +7 -1
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +3 -3
- package/framework/standards/PROVENANCE.json +4 -4
- package/framework/standards/STANDARDS.json +6 -6
- package/framework/standards/backend/dotnet/testing.md +4 -2
- package/framework/standards/frontend/design-system/ai-image-generation.md +42 -28
- package/framework/standards/frontend/design-system/ai-video-generation.md +24 -9
- package/framework/standards/frontend/nextjs/testing.md +4 -2
- package/framework/standards/integration/mcp/mcp-tools.md +19 -12
- package/package.json +1 -1
- package/src/commands/project/doctor.js +9 -1
- package/src/commands/project/worktree.js +60 -4
- package/src/commands/state/gate-check.js +5 -0
- package/src/commands/tasks/dag.js +27 -4
- package/src/commands/validation/verify.js +22 -38
- package/src/lib/risk-detector.js +13 -0
- package/src/lib/tasks/tasks-json-parser.js +21 -0
- package/src/lib/worktree/env-allocator.js +9 -3
- package/src/utils/agents-installer.js +51 -1
|
@@ -4,7 +4,7 @@ description: Conduz a fase opcional de UI/UX — design system, mockups, compone
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao tomar o ramo uiux (morph-spec advance --design), depois do Gate 1 e antes do morph-plan
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "8.
|
|
7
|
+
cliVersion: "8.41.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-uiux
|
|
@@ -32,7 +32,7 @@ Ao tomar o ramo opcional uiux: feature/bug cuja mudança cria ou altera superfí
|
|
|
32
32
|
- **Sistemas, não telas isoladas** — mesmo critério da persona `ui-designer`: componentes reutilizáveis antes de composições de uso único.
|
|
33
33
|
- **Toda tela sai com o checklist de tela preenchido** (template no passo 3) — a superfície, o volume esperado de dados, paginação, ordenação, filtro, os três estados (vazio, carregando, erro), o comportamento no mobile e abas × página longa. "Não se aplica" vale, **desde que escrito**. É o checklist que faz a lista de 2 000 linhas paginar: o que o design não decide não vira `doneCriteria` (`morph-plan` §5d), e o que não é critério ninguém entrega. Tela de dados segue os standards de `admin-app` (lista/tabela, formulário, feedback, estrutura de página), que a persona cita na superfície `admin-app`.
|
|
34
34
|
|
|
35
|
-
3. **Despache `morph-ui-designer`** para o trabalho visual — dispatch é o default aqui (domínio diferente do Orchestrator, MORPH.md §6). `morph-ui-designer` tem o escopo das implementadoras (com Bash) + `Skill` + MCPs `stitch`/`
|
|
35
|
+
3. **Despache `morph-ui-designer`** para o trabalho visual — dispatch é o default aqui (domínio diferente do Orchestrator, MORPH.md §6). `morph-ui-designer` tem o escopo das implementadoras (com Bash) + `Skill` + MCPs `stitch`/`playwright`/`context7`/`graphify` + o MCP de mídia morph-assets (imagem e vídeo). Bash serve para `morph-spec standards --find`, `npx`, screenshot e leitura de `git`; build/test da suíte, `verify` e aprovação de gate continuam do orquestrador (cláusula 3, como nas outras personas).
|
|
36
36
|
|
|
37
37
|
**Persona universal** (`ui-designer.stack = ["*"]` em `agents.json`, mesmo mecanismo de `evaluator`/`scout`): UI/UX importa sempre que há superfície de UI, então o subagent está registrado em **todo** projeto — .NET/Blazor incluído, não só Next.js. **Fallback** (caso raro: instalação anterior a essa mudança, `.claude/agents/morph-ui-designer.md` ainda não regenerado): `general-purpose` colando o campo `persona` (Read em `.morph/framework/agents.json`) no prompt, registrando o fallback no relatório — resolvido rodando `morph-spec setup-infra --reinstall`.
|
|
38
38
|
|
|
@@ -46,7 +46,7 @@ Ao tomar o ramo opcional uiux: feature/bug cuja mudança cria ou altera superfí
|
|
|
46
46
|
Produza, NESTA ORDEM — design-system.md primeiro, os demais só como aplicável ao escopo:
|
|
47
47
|
1. `design-system.md` — tokens (cor, tipografia, espaço, motion) consistentes com o projeto
|
|
48
48
|
2. `mockups.md` — descrição estruturada de cada tela nova/alterada (+ imagem via MCP
|
|
49
|
-
|
|
49
|
+
stitch ou o MCP de mídia (imagem/vídeo) quando disponível; fallback: descrição textual detalhada).
|
|
50
50
|
Cada tela é um `## Tela: <nome>` e traz, dentro dela, o checklist abaixo, com cada `…`
|
|
51
51
|
trocado pela decisão — "não se aplica" vale se escrito; `…`, vazio ou "a definir" não.
|
|
52
52
|
O que cada item decide: Superfície (admin-app | marketing-site — ela escolhe que
|
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
"skills": []
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
|
-
"id": "
|
|
41
|
+
"id": "morph-image",
|
|
42
42
|
"repo": "morph-HUB",
|
|
43
|
-
"path": "skills/
|
|
43
|
+
"path": "skills/morph-image",
|
|
44
44
|
"envVar": "MORPH_HUB",
|
|
45
45
|
"defaultCheckout": "R:/Polymorphism Tech/repos/morph-HUB",
|
|
46
|
-
"sourceTree": "
|
|
47
|
-
"sourceCommit": "
|
|
46
|
+
"sourceTree": "e55dcf307278583120601c3c5d55118302335f7b",
|
|
47
|
+
"sourceCommit": "c4c9164",
|
|
48
48
|
"distilledOn": "2026-09-09",
|
|
49
49
|
"distilledFrom": [
|
|
50
50
|
"SKILL.md"
|
|
@@ -1551,7 +1551,7 @@
|
|
|
1551
1551
|
"errorbody": "#rule-2--error-path-tests-assert-the-response-body-not-just-the-status-code",
|
|
1552
1552
|
"test-budget": "#test-budget"
|
|
1553
1553
|
},
|
|
1554
|
-
"digest": "Inject TimeProvider (never DateTime.UtcNow / DateOnly.FromDateTime(DateTime.UtcNow) directly in a handler or validator) and fake it with FakeTimeProvider (Microsoft.Extensions.TimeProvider.Testing) in integration tests — deterministic, no midnight-UTC flake, boundary dates (today vs. yesterday) become facts about the fake clock instead of when the suite happens to run. xUnit + WebApplicationFactory error-path tests (400/404/409) must assert the ProblemDetails response BODY (title/detail/violated field or error code), never just the HTTP status code — a status-only assertion lets the wrong validation rule or the wrong error branch pass silently as long as the status matches. Test budget per task: one test per postcondition plus one regression test per bug, more only with a one-line justification in the task; scenarios of the same behavior (edge cases included) are rows of one [Theory], never copied [Fact]s; no test whose only assertion is a copy, config or prose string (that is an analyzer or a publish-time validation, outside the suite count); shared fixtures before new helpers;
|
|
1554
|
+
"digest": "Inject TimeProvider (never DateTime.UtcNow / DateOnly.FromDateTime(DateTime.UtcNow) directly in a handler or validator) and fake it with FakeTimeProvider (Microsoft.Extensions.TimeProvider.Testing) in integration tests — deterministic, no midnight-UTC flake, boundary dates (today vs. yesterday) become facts about the fake clock instead of when the suite happens to run. xUnit + WebApplicationFactory error-path tests (400/404/409) must assert the ProblemDetails response BODY (title/detail/violated field or error code), never just the HTTP status code — a status-only assertion lets the wrong validation rule or the wrong error branch pass silently as long as the status matches. Test budget per task: one test per postcondition plus one regression test per bug, more only with a one-line justification in the task; scenarios of the same behavior (edge cases included) are rows of one [Theory], never copied [Fact]s; no test whose only assertion is a copy, config or prose string (that is an analyzer or a publish-time validation, outside the suite count); shared fixtures before new helpers; bugs and pure logic get tests in any order — the implementer runs its own filter once, never red-green cycles nor production mutation (the Gate 3 mutation proof shows the test bites); a surviving mutant is killed by tightening an assertion or adding a row, not by a new test.",
|
|
1555
1555
|
"verifiedAgainst": ".NET 10 + xUnit 2 + `Microsoft.Extensions.TimeProvider.Testing`.",
|
|
1556
1556
|
"provedBy": []
|
|
1557
1557
|
},
|
|
@@ -1850,8 +1850,8 @@
|
|
|
1850
1850
|
"favicon-og": "#the-two-assets-everyone-forgets",
|
|
1851
1851
|
"variations": "#variations-and-cost"
|
|
1852
1852
|
},
|
|
1853
|
-
"digest": "Guidelines for generating project images with the
|
|
1854
|
-
"verifiedAgainst": "
|
|
1853
|
+
"digest": "Guidelines for generating project images with the morph-assets MCP (OpenRouter; default openai/gpt-image-2.5-flare, drafts on mai-image-2.6-flash, gemini-3.1-flash-image for 2K/4K or 6-14 refs; models: [...] compares): when to use AI versus stock versus SVG, prompt patterns, aspect ratios, iterative editing and assets.md format. Core principle is brand fidelity over invention: always pass the client's real assets as references (or @brand with a project) via edit_image rather than generating in a vacuum, never AI-recreate a property developer's architectural render or a physical product (animate or reference the real one), and chain derivatives from an approved hero so the whole set shares light, palette and identity. Frame anything destined to become video WITHOUT a face, because video moderation is stricter (E005), and generate scrub frames at the video's exact ratio with the subject opposite the copy. Always produce the two forgotten assets: a favicon derived from the real logo (never a new one) and a 1200x630 OG share image. Offer two to three genuinely different variations per image (variants returns a contact sheet), varying angle, light or composition since seed is not supported by the GPT Image/Gemini models, and never advance with a weak image because every derivative inherits it. Copy to render goes between double quotes; CONFIRMATION_REQUIRED needs human approval and a confirmToken.",
|
|
1854
|
+
"verifiedAgainst": "morph-assets MCP (OpenRouter; default model `openai/gpt-image-2.5-flare`) + Next.js 15 `next/image`.",
|
|
1855
1855
|
"provedBy": []
|
|
1856
1856
|
},
|
|
1857
1857
|
{
|
|
@@ -1889,8 +1889,8 @@
|
|
|
1889
1889
|
"cost-ladder": "#section-4-the-cost-ladder",
|
|
1890
1890
|
"prompt-skeleton": "#section-5-prompt-skeleton"
|
|
1891
1891
|
},
|
|
1892
|
-
"digest": "The decisive rule: a perfect loop has an end frame identical to its start, which is right for a self-playing ambient background and WRONG for scroll scrubbing, because start and end look the same and the page reads as frozen while the user scrolls. Scroll-driven footage needs directional movement via generate_video with an endImage (push-in/dolly is the most reliable, then reveal, pull-back, slow tilt); avoid orbits and any movement that returns to its origin. Client architectural renders are animated with img2vid, never AI-recreated. Human faces repeatedly trip moderation (E005), so crop the face out of frame (neck down, hands, product) and reserve faces for static image generation. Always climb the cost ladder:
|
|
1893
|
-
"verifiedAgainst": "
|
|
1892
|
+
"digest": "The decisive rule: a perfect loop has an end frame identical to its start, which is right for a self-playing ambient background and WRONG for scroll scrubbing, because start and end look the same and the page reads as frozen while the user scrolls. Scroll-driven footage needs directional movement via generate_video with an endImage (push-in/dolly is the most reliable, then reveal, pull-back, slow tilt); avoid orbits and any movement that returns to its origin. Client architectural renders are animated with img2vid, never AI-recreated. Human faces repeatedly trip moderation (E005), so crop the face out of frame (neck down, hands, product) and reserve faces for static image generation. Always climb the cost ladder: seedance-1-5-pro 480p draft to judge framing and movement, then promoteFrom to seedance-2.0 1080p only after approval (same prompt, frames and seed), then re-extract frames with a bumped cache-bust. seedance-2.0-mini does NOT respect endImage and Veo 3.1 Lite/Fast are inconclusive, so scrub only on Seedance 1.5 Pro/2.0 and check the last-frame SSIM QA. Video has no ZDR on OpenRouter: nothing sensitive in the frames. Prompts read like director notes with no hype words, and must reserve a calm dark region for the page copy with the subject on the opposite side. Generation is async via the morph-assets MCP (OpenRouter): a running job is polled with get_video (several ids at once), never by resending generate_video; library reads local history and spends no credits; media_tool extracts scrub frames and posters.",
|
|
1893
|
+
"verifiedAgainst": "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`.",
|
|
1894
1894
|
"provedBy": []
|
|
1895
1895
|
},
|
|
1896
1896
|
{
|
|
@@ -2371,7 +2371,7 @@
|
|
|
2371
2371
|
"e2e": "#test-file-co-location",
|
|
2372
2372
|
"test-budget": "#test-budget"
|
|
2373
2373
|
},
|
|
2374
|
-
"digest": "Vitest for unit tests, React Testing Library for component tests. Mock next/navigation with vi.mock. Test Server Actions with direct function calls. Use MSW for API mocking in component tests. Playwright for E2E tests against running app. Never test implementation details — test user behavior and output. Test budget per task: one test per postcondition plus one regression test per bug, more only with a one-line justification in the task; scenarios of the same behavior are rows of one it.each, never copied it() blocks; no test whose only assertion is a copy string (lint or publish-time validation instead); shared wrappers, MSW handlers and factories before new helpers;
|
|
2374
|
+
"digest": "Vitest for unit tests, React Testing Library for component tests. Mock next/navigation with vi.mock. Test Server Actions with direct function calls. Use MSW for API mocking in component tests. Playwright for E2E tests against running app. Never test implementation details — test user behavior and output. Test budget per task: one test per postcondition plus one regression test per bug, more only with a one-line justification in the task; scenarios of the same behavior are rows of one it.each, never copied it() blocks; no test whose only assertion is a copy string (lint or publish-time validation instead); shared wrappers, MSW handlers and factories before new helpers; bugs and pure logic get tests in any order — the implementer runs its own filter once, never red-green cycles nor production mutation.",
|
|
2375
2375
|
"verifiedAgainst": "Vitest 3 + React Testing Library 16 + MSW 2.",
|
|
2376
2376
|
"provedBy": []
|
|
2377
2377
|
},
|
|
@@ -128,8 +128,10 @@ regression test per bug fixed**. Going above it takes a one-line justification i
|
|
|
128
128
|
- **Shared fixture by default.** Reuse the project's `WebApplicationFactory` fixture, fakes and
|
|
129
129
|
builders. Before writing a new helper, look for the existing one (`morph-spec graph explain
|
|
130
130
|
<Name>`; fallback without a graph: `grep -rn`).
|
|
131
|
-
- **
|
|
132
|
-
|
|
131
|
+
- **Order is free, proof is not the implementer's:** a bug gets a regression test, and pure logic
|
|
132
|
+
gets its tests, in whatever order the code comes out. The implementer does not run red→green
|
|
133
|
+
cycles nor mutate production code to watch a test fail; it runs its own filter once before the
|
|
134
|
+
report, and the Gate 3 mutation proof is what shows the test bites.
|
|
133
135
|
- **A surviving mutant** of the Gate 3 mutation proof is killed by tightening an existing assertion
|
|
134
136
|
or adding a table row, not by one new test per mutant.
|
|
135
137
|
|
|
@@ -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:**
|
|
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** |
|
|
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 `
|
|
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
|
|
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
|
-
##
|
|
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
|
-
|
|
47
|
-
prompt: string, //
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
66
|
-
|
|
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
|
-
//
|
|
72
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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 `
|
|
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:**
|
|
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:**
|
|
68
|
-
2. **Final:** only after the draft is approved,
|
|
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`
|
|
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
|
|
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
|
|
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
|
-
- **
|
|
120
|
-
reducers, schema)
|
|
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
|
|
|
@@ -80,37 +80,44 @@ const docs = await mcp__context7__query-docs({
|
|
|
80
80
|
|
|
81
81
|
Fallback: WebSearch for component documentation, WebFetch for specific component API pages.
|
|
82
82
|
|
|
83
|
-
###
|
|
83
|
+
### morph-assets MCP — AI Image and Video Generation
|
|
84
84
|
|
|
85
85
|
> Available in: **UI/UX** (preview generation), **Implement** (production assets)
|
|
86
86
|
|
|
87
|
+
One MCP for image, video and local post-production, all models through OpenRouter. Default image model `openai/gpt-image-2.5-flare`; default video model `bytedance/seedance-1-5-pro` (draft/loop), final `bytedance/seedance-2.0` 1080p via `promoteFrom`. `model` accepts any OpenRouter model by id or alias; `models: [...]` compares the same request across models.
|
|
88
|
+
|
|
87
89
|
| Tool | Use Case | Example |
|
|
88
90
|
|------|----------|---------|
|
|
89
|
-
| `generate_image` | Create new image from text prompt | `
|
|
90
|
-
| `edit_image` | Modify existing image | `
|
|
91
|
-
| `continue_editing` | Iterate on last generated/edited image | `
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
91
|
+
| `generate_image` | Create new image from text prompt | `mcp__morph-assets__generate_image({ prompt, aspectRatio, outputPath })` |
|
|
92
|
+
| `edit_image` | Modify existing image | `mcp__morph-assets__edit_image({ image, prompt, references })` |
|
|
93
|
+
| `continue_editing` | Iterate on last generated/edited image | `mcp__morph-assets__continue_editing({ prompt })` |
|
|
94
|
+
| `generate_video` / `generate_loop_video` | Directional footage (`image` + `endImage`) / ambient loop | `mcp__morph-assets__generate_video({ prompt, image, endImage })` |
|
|
95
|
+
| `get_video` | Poll one or more video jobs (never resend `generate_video`) | `mcp__morph-assets__get_video({ ids })` |
|
|
96
|
+
| `media_tool` | ffmpeg post-production: scrub frames, poster, concat, compress, approval picker | `mcp__morph-assets__media_tool({ op: "scrub_frames", input })` |
|
|
97
|
+
| `library` | History and metadata (replaces `list_images` / `get_image_info` / `list_videos`) | `mcp__morph-assets__library({ op: "list" })` |
|
|
98
|
+
| `list_models` / `usage` | Pick a model from the curation / spend and credit balance | `mcp__morph-assets__usage({ op: "balance" })` |
|
|
94
99
|
|
|
95
100
|
Read `design-system.md` first to get the project colors and aesthetic direction. Keep edits short and targeted, and iterate with `continue_editing` one change at a time.
|
|
96
101
|
|
|
97
102
|
```javascript
|
|
98
|
-
const image = await
|
|
103
|
+
const image = await mcp__morph-assets__generate_image({
|
|
99
104
|
prompt: "A floating tablet showing a modern dashboard interface on a dark charcoal background with copper and gold accent lighting. Premium luxury aesthetic, photorealistic, cinematic lighting with warm rim light.",
|
|
100
105
|
aspectRatio: "16:9",
|
|
101
106
|
outputPath: "public/images/my-feature/hero-mockup.png"
|
|
102
107
|
});
|
|
103
108
|
|
|
104
|
-
await
|
|
105
|
-
|
|
109
|
+
await mcp__morph-assets__edit_image({
|
|
110
|
+
image: "public/images/my-feature/hero-mockup.png",
|
|
106
111
|
prompt: "Make the lighting warmer and add a subtle copper glow around the tablet edges."
|
|
107
112
|
});
|
|
108
113
|
|
|
109
|
-
await
|
|
114
|
+
await mcp__morph-assets__continue_editing({
|
|
110
115
|
prompt: "More contrast in the shadows."
|
|
111
116
|
});
|
|
112
117
|
```
|
|
113
118
|
|
|
119
|
+
Paid per generation: above US$2, over the project budget or with an unknown price the tool returns `CONFIRMATION_REQUIRED` + `confirmToken` (get human approval, then repeat the same call with the token); `dryRun: true` only estimates.
|
|
120
|
+
|
|
114
121
|
Fallback: manual image creation, stock photos, or placeholder divs.
|
|
115
122
|
|
|
116
123
|
**Use cases by phase:**
|
|
@@ -121,7 +128,7 @@ Fallback: manual image creation, stock photos, or placeholder divs.
|
|
|
121
128
|
| Implement (7) | Generate production assets from assets.md specs | `generate_image` + `edit_image` |
|
|
122
129
|
| Implement (7) | Iterate on generated images for quality | `continue_editing` |
|
|
123
130
|
|
|
124
|
-
> **Standard:** See `frontend/design-system/ai-image-generation.md` for prompt engineering patterns, aspect ratio guide, and `assets.md` format.
|
|
131
|
+
> **Standard:** See `frontend/design-system/ai-image-generation.md` for prompt engineering patterns, aspect ratio guide, and `assets.md` format, and `frontend/design-system/ai-video-generation.md` for directional footage and the video cost ladder.
|
|
125
132
|
|
|
126
133
|
---
|
|
127
134
|
|
|
@@ -271,7 +278,7 @@ PRs/issues: sempre `gh` CLI (Bash). Deployments fallback: `vercel` CLI.
|
|
|
271
278
|
| Container ops | Docker CLI (`docker ps`, `docker logs`, `docker compose`) | — |
|
|
272
279
|
| Cloud resources | Azure CLI (`az resource`, `az deployment`) | — |
|
|
273
280
|
| Vercel deployments | Vercel MCP | Bash `vercel` CLI |
|
|
274
|
-
| AI image generation |
|
|
281
|
+
| AI image and video generation | morph-assets MCP | Manual creation or stock photos |
|
|
275
282
|
|
|
276
283
|
**Rule:** MCP tools provide structured data (JSON responses). Native tools require manual parsing. **Always prefer MCP when available** — fall back to native when not. **Exceptions:** Neon, Docker, Azure **and GitHub** always use their respective CLIs directly — never MCP (`gh` já retorna JSON estruturado via `--json`, sem Docker nem PAT em env var).
|
|
277
284
|
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
getInstalledCLIVersion
|
|
17
17
|
} from '../../utils/version-checker.js';
|
|
18
18
|
import { resetMorphSettings } from '../../utils/claude-settings-manager.js';
|
|
19
|
-
import { checkMissingAgents, checkAgentsDrift, maxTurnsError, surfacesError } from '../../utils/agents-installer.js';
|
|
19
|
+
import { checkMissingAgents, checkAgentsDrift, maxTurnsError, surfacesError, effortError } from '../../utils/agents-installer.js';
|
|
20
20
|
import { getProjectTags } from '../../lib/stack-filter.js';
|
|
21
21
|
import { auditGraph } from '../../lib/graph/graph-health.js';
|
|
22
22
|
import { auditFeatureLifecycle } from '../../core/state/lifecycle-audit.js';
|
|
@@ -208,6 +208,14 @@ export function validateAgentsJson(agentsData) {
|
|
|
208
208
|
? { name: 'agents.json surfaces', status: 'error', msg: surfacesErrors.join(' | ') }
|
|
209
209
|
: { name: 'agents.json surfaces', status: 'ok' });
|
|
210
210
|
|
|
211
|
+
// 6: effort — same installer rule, same severity.
|
|
212
|
+
const effortErrors = agentEntries
|
|
213
|
+
.map(([id, data]) => effortError(id, data?.effort))
|
|
214
|
+
.filter(Boolean);
|
|
215
|
+
checks.push(effortErrors.length > 0
|
|
216
|
+
? { name: 'agents.json effort', status: 'error', msg: effortErrors.join(' | ') }
|
|
217
|
+
: { name: 'agents.json effort', status: 'ok' });
|
|
218
|
+
|
|
211
219
|
if (missingV5Fields.length > 0 && isV5) {
|
|
212
220
|
const sample = missingV5Fields.slice(0, 3).join(', ');
|
|
213
221
|
checks.push({
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
import { execSync, execFileSync } from 'child_process';
|
|
38
|
-
import { existsSync, mkdirSync, symlinkSync, readFileSync, writeFileSync, lstatSync, unlinkSync, rmdirSync, cpSync } from 'fs';
|
|
38
|
+
import { existsSync, mkdirSync, symlinkSync, readFileSync, writeFileSync, lstatSync, unlinkSync, rmdirSync, cpSync, copyFileSync, readdirSync } from 'fs';
|
|
39
39
|
import { join, resolve, dirname, basename } from 'path';
|
|
40
40
|
import chalk from 'chalk';
|
|
41
41
|
import { detectDefaultBranch } from '../../lib/git/default-branch.js';
|
|
@@ -612,6 +612,43 @@ export function buildTaskBranchName(name) {
|
|
|
612
612
|
return `morph-task/${name}`;
|
|
613
613
|
}
|
|
614
614
|
|
|
615
|
+
const ROOT_ENV_FILE_RE = /^\.env(\..+)?$/;
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Copy the root's `.env` / `.env.*` into a new tree: a sub-agent that builds
|
|
619
|
+
* or runs the app needs the same secrets, and they are never committed. Only
|
|
620
|
+
* a REAL file (never a link — nothing in the tree may reach the root through
|
|
621
|
+
* one), only when the tree lacks it (never overwrite), and only when git
|
|
622
|
+
* IGNORES it there: an untracked-but-visible copy would dirty the tree, and
|
|
623
|
+
* `finish`/`worktree remove --if-clean` refuse a dirty tree. At teardown the
|
|
624
|
+
* copy is byte-identical to the root's, which `triageIgnoredPaths` classifies
|
|
625
|
+
* as REPLICABLE — deleted with nothing lost.
|
|
626
|
+
*
|
|
627
|
+
* @param {string} worktreePath
|
|
628
|
+
* @param {string} root
|
|
629
|
+
* @returns {string[]} names copied
|
|
630
|
+
*/
|
|
631
|
+
export function copyRootEnvFiles(worktreePath, root) {
|
|
632
|
+
let names;
|
|
633
|
+
try {
|
|
634
|
+
names = readdirSync(root).filter((n) => ROOT_ENV_FILE_RE.test(n));
|
|
635
|
+
} catch {
|
|
636
|
+
return [];
|
|
637
|
+
}
|
|
638
|
+
const copied = [];
|
|
639
|
+
for (const name of names) {
|
|
640
|
+
const dst = join(worktreePath, name);
|
|
641
|
+
try {
|
|
642
|
+
if (!lstatSync(join(root, name)).isFile()) continue;
|
|
643
|
+
try { lstatSync(dst); continue; } catch { /* absent — not even a dangling link */ }
|
|
644
|
+
execFileSync('git', ['check-ignore', '-q', '--', name], { cwd: worktreePath, stdio: 'pipe' });
|
|
645
|
+
copyFileSync(join(root, name), dst);
|
|
646
|
+
copied.push(name);
|
|
647
|
+
} catch { /* not ignored, unreadable or unwritable — leave it out */ }
|
|
648
|
+
}
|
|
649
|
+
return copied;
|
|
650
|
+
}
|
|
651
|
+
|
|
615
652
|
/**
|
|
616
653
|
* Non-exiting core of `worktree provision` — the tree `claude -w`,
|
|
617
654
|
* `EnterWorktree` and `Agent isolation:"worktree"` ask for when the name is
|
|
@@ -668,7 +705,11 @@ export async function runWorktreeProvision(name, options = {}) {
|
|
|
668
705
|
execFileSync('git', ['worktree', 'add', worktreePath, branch], { cwd: root, stdio: 'pipe' });
|
|
669
706
|
|
|
670
707
|
setupInfraLinks(worktreePath, root);
|
|
671
|
-
const
|
|
708
|
+
const envFiles = copyRootEnvFiles(worktreePath, root);
|
|
709
|
+
// A tree born from `morph/<f>` works FOR that feature: MORPH_FEATURE says
|
|
710
|
+
// so, while the port block stays the task's own (`task/<name>`).
|
|
711
|
+
const forFeature = String(options.base || '').match(/^morph\/(.+)$/)?.[1] ?? null;
|
|
712
|
+
const envBlock = await allocateWorktreeEnv(worktreePath, root, `task/${name}`, { morphFeature: forFeature });
|
|
672
713
|
const materialized = await ensureLocalState(worktreePath);
|
|
673
714
|
const graph = seedWorktreeGraph(worktreePath, root);
|
|
674
715
|
|
|
@@ -681,6 +722,8 @@ export async function runWorktreeProvision(name, options = {}) {
|
|
|
681
722
|
branchedFrom,
|
|
682
723
|
kind: 'task-isolation',
|
|
683
724
|
name,
|
|
725
|
+
...(forFeature ? { feature: forFeature } : {}),
|
|
726
|
+
...(envFiles.length ? { envFiles } : {}),
|
|
684
727
|
materialized: materialized.materialized,
|
|
685
728
|
...(envBlock ? { env: { base: envBlock.base, range: envBlock.range } } : {}),
|
|
686
729
|
...(graph.seeded ? { graph: 'seeded' } : {}),
|
|
@@ -797,12 +840,25 @@ export function envKeyForWorktree(worktreePath, root) {
|
|
|
797
840
|
* Shared by `setup` (feature key), `provision` (task key) and `link --all`
|
|
798
841
|
* (regeneration), so the manifest has exactly one writer.
|
|
799
842
|
*
|
|
843
|
+
* `morphFeature` (task trees born from `morph/<f>`) only renames the
|
|
844
|
+
* `MORPH_FEATURE` line. Left undefined — the `link --all` regeneration, which
|
|
845
|
+
* derives the key from the branch and cannot see the base — the value the
|
|
846
|
+
* file already carries is kept, so a repair never forgets the feature.
|
|
847
|
+
*
|
|
800
848
|
* @param {string} worktreePath
|
|
801
849
|
* @param {string} root - primary root
|
|
802
850
|
* @param {string} envKey - `<feature>` or `task/<name>`
|
|
851
|
+
* @param {{ morphFeature?: string|null }} [opts]
|
|
803
852
|
* @returns {Promise<{ index: number, base: number, range: [number, number] }|null>}
|
|
804
853
|
*/
|
|
805
|
-
export async function allocateWorktreeEnv(worktreePath, root, envKey) {
|
|
854
|
+
export async function allocateWorktreeEnv(worktreePath, root, envKey, { morphFeature } = {}) {
|
|
855
|
+
const envPath = join(worktreePath, '.morph', 'worktree.env');
|
|
856
|
+
if (morphFeature === undefined && envKey.startsWith('task/')) {
|
|
857
|
+
try {
|
|
858
|
+
const kept = readFileSync(envPath, 'utf8').match(/^MORPH_FEATURE=(.+)$/m)?.[1]?.trim();
|
|
859
|
+
morphFeature = kept && kept !== envKey ? kept : null;
|
|
860
|
+
} catch { morphFeature = null; }
|
|
861
|
+
}
|
|
806
862
|
try {
|
|
807
863
|
const { allocateEnvBlock, resolveFleetConfig, buildEnvFileContent } =
|
|
808
864
|
await import('../../lib/worktree/env-allocator.js');
|
|
@@ -816,7 +872,7 @@ export async function allocateWorktreeEnv(worktreePath, root, envKey) {
|
|
|
816
872
|
stack = cfg?.project?.stack || '';
|
|
817
873
|
} catch { /* fail-open — stackless env still carries PORT */ }
|
|
818
874
|
mkdirSync(join(worktreePath, '.morph'), { recursive: true });
|
|
819
|
-
writeFileSync(
|
|
875
|
+
writeFileSync(envPath, buildEnvFileContent(envKey, block, stack, { morphFeature }), 'utf8');
|
|
820
876
|
excludeWorktreePaths(root, [join('.morph', 'worktree.env')]);
|
|
821
877
|
return block;
|
|
822
878
|
} catch {
|
|
@@ -9,6 +9,7 @@ import { persistGateDecision, VALID_GATES, readTrustFromConfig } from './gate-de
|
|
|
9
9
|
import { minimatch } from 'minimatch';
|
|
10
10
|
import { detectDefaultBranch, getHeadSha, getDiffStat, changedPathsAgainst } from '../../lib/git/default-branch.js';
|
|
11
11
|
import { readMorphConfig } from '../../lib/tasks/test-runner.js';
|
|
12
|
+
import { oversizedTasks } from '../../lib/tasks/tasks-json-parser.js';
|
|
12
13
|
import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -196,6 +197,10 @@ function collectSignals(featureName, feature, cwd, gateName) {
|
|
|
196
197
|
designSystemDivergence: false,
|
|
197
198
|
};
|
|
198
199
|
|
|
200
|
+
// Fato de disco, fora de OVERRIDABLE_SIGNAL_KEYS: task que não cabe num
|
|
201
|
+
// dispatch se fatia no plano, não depois de um sub-agent bater no teto.
|
|
202
|
+
if (gateName === 'plan') signals.oversizedTasks = oversizedTasks({ tasks });
|
|
203
|
+
|
|
199
204
|
// Só onde o checklist decide algo: no fecho do design e no ship. Fato de
|
|
200
205
|
// disco, fora de OVERRIDABLE_SIGNAL_KEYS como `mutationProof`.
|
|
201
206
|
if (gateName === 'uiux' || gateName === 'review') {
|