@polymorphism-tech/morph-spec 8.40.0 → 8.42.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 (59) hide show
  1. package/CLAUDE.md +7 -5
  2. package/README.md +3 -3
  3. package/claude-plugin.json +1 -1
  4. package/docs/CHEATSHEET.md +1 -1
  5. package/docs/QUICKSTART.md +1 -1
  6. package/framework/CLAUDE.md +2 -2
  7. package/framework/MORPH.md +9 -8
  8. package/framework/agents.json +14 -8
  9. package/framework/docs/CLI.md +5 -3
  10. package/framework/hooks/README.md +2 -1
  11. package/framework/hooks/claude-code/core/chain.js +2 -0
  12. package/framework/hooks/claude-code/core/comment-guard.js +107 -0
  13. package/framework/hooks/claude-code/core/telemetry-log.js +39 -8
  14. package/framework/hooks/claude-code/core/worktree-create.js +22 -6
  15. package/framework/hooks/shared/comment-rules.js +171 -0
  16. package/{src/lib/validators/code-budget → framework/hooks/shared}/comment-scanner.js +2 -2
  17. package/{src/lib/ai → framework/hooks/shared}/comment-stripper.js +2 -1
  18. package/framework/hooks/shared/git-paths.js +38 -5
  19. package/framework/hooks/shared/source-files.js +34 -0
  20. package/framework/mcp/registry.json +26 -65
  21. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  22. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  23. package/framework/skills/core/morph-apply/SKILL.md +58 -26
  24. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  25. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  26. package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
  27. package/framework/skills/core/morph-implement/SKILL.md +46 -25
  28. package/framework/skills/core/morph-media/SKILL.md +2 -2
  29. package/framework/skills/core/morph-plan/SKILL.md +7 -1
  30. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  31. package/framework/skills/core/morph-review/SKILL.md +1 -1
  32. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  33. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  34. package/framework/skills/core/morph-uiux/SKILL.md +3 -3
  35. package/framework/standards/PROVENANCE.json +4 -4
  36. package/framework/standards/STANDARDS.json +7 -7
  37. package/framework/standards/architecture/code-comments.md +9 -2
  38. package/framework/standards/backend/dotnet/testing.md +4 -2
  39. package/framework/standards/frontend/design-system/ai-image-generation.md +42 -28
  40. package/framework/standards/frontend/design-system/ai-video-generation.md +24 -9
  41. package/framework/standards/frontend/nextjs/testing.md +4 -2
  42. package/framework/standards/integration/mcp/mcp-tools.md +19 -12
  43. package/package.json +1 -1
  44. package/src/commands/project/doctor.js +9 -1
  45. package/src/commands/project/worktree.js +60 -4
  46. package/src/commands/state/gate-check.js +5 -0
  47. package/src/commands/tasks/dag.js +27 -4
  48. package/src/commands/validation/verify.js +22 -38
  49. package/src/lib/ai/code-audit.js +1 -1
  50. package/src/lib/ai/registry-audit.js +1 -1
  51. package/src/lib/risk-detector.js +13 -0
  52. package/src/lib/tasks/dotnet-test-introspection.js +1 -1
  53. package/src/lib/tasks/tasks-json-parser.js +21 -0
  54. package/src/lib/validators/code-budget/code-budget-validator.js +4 -6
  55. package/src/lib/validators/code-budget/index.js +1 -1
  56. package/src/lib/validators/reuse/symbol-scanner.js +1 -28
  57. package/src/lib/worktree/env-allocator.js +9 -3
  58. package/src/utils/agents-installer.js +51 -1
  59. package/src/utils/hooks-installer.js +3 -2
@@ -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.40.0"
7
+ cliVersion: "8.42.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`/`gemini-image`/`playwright`/`context7`/`seedance`/`graphify`. 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).
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
- gemini-image/stitch quando disponível; fallback: descrição textual detalhada).
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": "gemini-image",
41
+ "id": "morph-image",
42
42
  "repo": "morph-HUB",
43
- "path": "skills/gemini-image",
43
+ "path": "skills/morph-image",
44
44
  "envVar": "MORPH_HUB",
45
45
  "defaultCheckout": "R:/Polymorphism Tech/repos/morph-HUB",
46
- "sourceTree": "ec0abd827badfd5cd6d4c5f8b1843f81a3d183fe",
47
- "sourceCommit": "b6bbfff",
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; TDD for bugs (regression first) and pure logic; a surviving mutant is killed by tightening an assertion or adding a row, not by a new test.",
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 Gemini Image MCP: 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 referenceImages 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, varying angle, light or composition rather than the seed, and never advance with a weak image because every derivative inherits it.",
1854
- "verifiedAgainst": "Gemini Image MCP + Next.js 15 `next/image`.",
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: fast/480p/4s draft to judge framing and movement, 1080p only after approval, then re-extract frames with a bumped cache-bust. 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: generate_video returns a request_id polled via get_video; list_videos reads local history and spends no credits.",
1893
- "verifiedAgainst": "seedance MCP (generate_video, generate_loop_video, get_video, list_videos) over Replicate.",
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; TDD for bugs and pure logic, component tests may come after the code.",
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
  },
@@ -2848,7 +2848,7 @@
2848
2848
  "not-worth": "#o-que-não-merece",
2849
2849
  "size": "#tamanho"
2850
2850
  },
2851
- "digest": "Default is NO comment: code explains itself through names, types and tests. Before commenting, rename, extract a function or named constant, encode the state in the type, or write a test; comment only what none of these can express: an invariant the type does not express, an order that matters, an external quirk (third-party API, library bug with its link), or the contract of a package published for consumers outside the repo. Never process history (task, UC, ADR, FR, Gate, evaluator, spec.md, mockup: commit message and decisions.md), never restate the code, no XML doc/JSDoc on application members that repeats the signature, no file headers, section banners, #region, commented-out code or loose TODOs, no claims about behavior (make them test assertions). One line is normal, ~3 is the ceiling; more than one comment line per ten code lines means the names are not doing the work. The verify validators node (code-budget) warns on added comments that cite process and on added comment density.",
2851
+ "digest": "Default is NO comment: code explains itself through names, types and tests. Before commenting, rename, extract a function or named constant, encode the state in the type, or write a test; comment only what none of these can express: an invariant the type does not express, an order that matters, an external quirk (third-party API, library bug with its link), or the contract of a package published for consumers outside the repo. Never process history (task, UC, ADR, FR, Gate, evaluator, spec.md, mockup: commit message and decisions.md; in a test the link to the postcondition goes in the test NAME), never restate the code, no XML doc/JSDoc on application members that repeats the signature, no file headers, section banners, #region, commented-out code or loose TODOs, no claims about behavior (make them test assertions). One line is normal, ~3 is the ceiling; more than one comment line per ten code lines means the names are not doing the work. A heavily commented neighbour file is not a model. The comment-guard hook blocks an edit that ADDS process history, a banner/#region, a block over 3 lines or a doc-comment outside a published package; the verify validators node (code-budget) warns on added comments that cite process and on added comment density.",
2852
2852
  "verifiedAgainst": "C#, TypeScript e JavaScript (convenção de linguagem, sem pacote externo).",
2853
2853
  "provedBy": []
2854
2854
  }
@@ -43,7 +43,8 @@ Só o **porquê que o código não mostra**:
43
43
 
44
44
  - **História de processo.** Task, UC, ADR, FR, Gate, avaliador, `spec.md`, mockup, "confirmado ao
45
45
  implementar a T11", "o Gate 3 pediu". Isso vai na mensagem de commit e no `decisions.md`, onde tem
46
- data e autor e não envelhece dentro do arquivo.
46
+ data e autor e não envelhece dentro do arquivo. Em teste, o elo com a pós-condição vai no **nome**
47
+ do teste (`P5_estado_do_lead_espelha_o_crm`, `DisplayName`), nunca em `// UC-672 P5`.
47
48
  - **Repetição do código.** `// incrementa o contador` sobre `count++`, `// valida o request` sobre
48
49
  `validator.Validate(request)`. Nome bom substitui comentário.
49
50
  - **Doc-comment que repete a assinatura.** `/// <summary>Obtém o pedido.</summary>` sobre
@@ -63,7 +64,8 @@ Só o **porquê que o código não mostra**:
63
64
  Uma linha é o normal; teto de ~3. Acima disso o comentário justifica o volume (algoritmo não
64
65
  óbvio, armadilha de terceiro com a reprodução). Bloco longo narrando decisões é `decisions.md` com
65
66
  outro nome. Em volume, código de aplicação fica perto de zero: mais de uma linha de comentário a
66
- cada dez de código é sinal de que os nomes não estão fazendo o trabalho.
67
+ cada dez de código é sinal de que os nomes não estão fazendo o trabalho. Arquivo vizinho muito
68
+ comentado não é modelo: código novo segue este padrão, não a densidade do legado.
67
69
 
68
70
  ## Exemplo
69
71
 
@@ -90,6 +92,11 @@ await upsert({ key: doc.sourceId, hash: sha256(doc.body) });
90
92
 
91
93
  ## Na revisão
92
94
 
95
+ Na hora da edição, o hook `comment-guard` bloqueia o comentário **adicionado** que é história de
96
+ processo, banner/`#region`, bloco de mais de 3 linhas ou `///`/JSDoc em projeto que não é pacote
97
+ publicado. Comentário que o arquivo já tinha nunca é acusado. Repetir o código em uma linha não é
98
+ detectável por forma: fica com a revisão abaixo.
99
+
93
100
  Comentário que o código dispensava é achado da rubrica `code-quality` no Gate 3. O nó `validators`
94
101
  do `morph-spec verify` mede, nas linhas que a feature adicionou (validador `code-budget`), quantas
95
102
  linhas de comentário citam processo e a densidade de comentário sobre o código adicionado, e avisa
@@ -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
- - **TDD where it pays:** a bug gets its regression test first (red, then the fix), and so does pure
132
- logic. Integration and endpoint tests may come after the code.
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:** 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
 
@@ -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
- ### Gemini Image MCP — AI Image Generation
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 | `mcp__gemini-image-mcp__generate_image({ prompt, aspectRatio, outputPath })` |
90
- | `edit_image` | Modify existing image | `mcp__gemini-image-mcp__edit_image({ imageSource, prompt })` |
91
- | `continue_editing` | Iterate on last generated/edited image | `mcp__gemini-image-mcp__continue_editing({ prompt })` |
92
- | `list_images` | Browse image generation history | `mcp__gemini-image-mcp__list_images({ type: "all" })` |
93
- | `get_image_info` | Get metadata about a specific image | `mcp__gemini-image-mcp__get_image_info({ imageId })` |
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 mcp__gemini-image-mcp__generate_image({
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 mcp__gemini-image-mcp__edit_image({
105
- imageSource: "public/images/my-feature/hero-mockup.png",
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 mcp__gemini-image-mcp__continue_editing({
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 | Gemini Image MCP | Manual creation or stock photos |
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polymorphism-tech/morph-spec",
3
- "version": "8.40.0",
3
+ "version": "8.42.0",
4
4
  "description": "MORPH-SPEC: NLH (Natural Language Harness) for spec-driven development with Claude Code",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -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({