@polymorphism-tech/morph-spec 8.34.2 → 8.35.1

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 (328) hide show
  1. package/CLAUDE.md +22 -5
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +18 -4
  4. package/bin/validate.js +379 -379
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +5 -4
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/MORPH.md +16 -17
  9. package/framework/agents.json +197 -44
  10. package/framework/ai-pin.json +22 -0
  11. package/framework/commands/morph-media.md +45 -0
  12. package/framework/docs/CLI.md +236 -11
  13. package/framework/evals/proof-rubrics/README.md +76 -0
  14. package/framework/evals/proof-rubrics/eval-de-modelo.md +64 -0
  15. package/framework/evals/proof-rubrics/fiacao.md +54 -0
  16. package/framework/evals/proof-rubrics/guard-cego.md +52 -0
  17. package/framework/evals/proof-rubrics/media-claims.md +109 -0
  18. package/framework/evals/proof-rubrics/mutation-proof.md +77 -0
  19. package/framework/evals/proof-rubrics/observabilidade-genai.md +119 -0
  20. package/framework/evals/proof-rubrics/verde-nao-e-coberto.md +48 -0
  21. package/framework/evals/rubrics/architecture.md +30 -0
  22. package/framework/evals/rubrics/code-quality.md +26 -0
  23. package/framework/evals/rubrics/test-coverage.md +20 -0
  24. package/framework/hooks/claude-code/core/protect-spec-files.js +41 -4
  25. package/framework/hooks/claude-code/core/worktree-env-inject.js +43 -0
  26. package/framework/hooks/claude-code/statusline.sh +11 -11
  27. package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
  28. package/framework/hooks/git/pre-commit/agents.sh +25 -25
  29. package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
  30. package/framework/hooks/git/pre-commit/specs.sh +50 -50
  31. package/framework/hooks/git/pre-push/run-tests.sh +44 -44
  32. package/framework/hooks/shared/e2e-check.js +135 -22
  33. package/framework/hooks/shared/payload-utils.js +15 -2
  34. package/framework/hooks/shared/stdin-reader.js +26 -26
  35. package/framework/rules/ai-agents-standards.md +14 -2
  36. package/framework/schemas/ai-pin.schema.json +97 -0
  37. package/framework/schemas/config.schema.json +107 -0
  38. package/framework/schemas/feature.schema.json +23 -4
  39. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  40. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  41. package/framework/skills/core/morph-apply/SKILL.md +26 -5
  42. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  43. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  44. package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
  45. package/framework/skills/core/morph-implement/SKILL.md +18 -2
  46. package/framework/skills/core/morph-media/SKILL.md +215 -0
  47. package/framework/skills/core/morph-plan/SKILL.md +33 -2
  48. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  49. package/framework/skills/core/morph-review/SKILL.md +166 -9
  50. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  51. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  52. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  53. package/framework/standards/PROVENANCE.json +81 -3
  54. package/framework/standards/STANDARDS.json +1483 -839
  55. package/framework/standards/ai-agents/agent-archetypes.md +160 -0
  56. package/framework/standards/ai-agents/agent-session.md +164 -68
  57. package/framework/standards/ai-agents/agent-spec.md +196 -0
  58. package/framework/standards/ai-agents/batching.md +61 -37
  59. package/framework/standards/ai-agents/context-providers.md +97 -24
  60. package/framework/standards/ai-agents/conversational-agent-with-phases.md +239 -0
  61. package/framework/standards/ai-agents/cost-and-budget.md +256 -0
  62. package/framework/standards/ai-agents/durable-workflows-hitl.md +98 -5
  63. package/framework/standards/ai-agents/evals-with-cache.md +289 -0
  64. package/framework/standards/ai-agents/guardrails.md +222 -0
  65. package/framework/standards/ai-agents/llm-runtime-defaults.md +226 -0
  66. package/framework/standards/ai-agents/mcp-server.md +119 -18
  67. package/framework/standards/ai-agents/mcp-tools.md +85 -32
  68. package/framework/standards/ai-agents/media-pipeline.md +352 -0
  69. package/framework/standards/ai-agents/media-video.md +267 -0
  70. package/framework/standards/ai-agents/middleware-patterns.md +39 -5
  71. package/framework/standards/ai-agents/modalities-image-gen.md +354 -105
  72. package/framework/standards/ai-agents/modalities-speech-to-text.md +8 -2
  73. package/framework/standards/ai-agents/modalities-text-to-speech.md +10 -2
  74. package/framework/standards/ai-agents/modalities-vision.md +6 -2
  75. package/framework/standards/ai-agents/multi-agent-patterns.md +61 -5
  76. package/framework/standards/ai-agents/observability-patterns.md +227 -17
  77. package/framework/standards/ai-agents/production.md +38 -9
  78. package/framework/standards/ai-agents/prompt-sources.md +389 -0
  79. package/framework/standards/ai-agents/providers/model-registry.md +174 -39
  80. package/framework/standards/ai-agents/rag-custom-pgvector.md +69 -4
  81. package/framework/standards/ai-agents/service-tier-flex.md +27 -19
  82. package/framework/standards/ai-agents/setup.md +166 -50
  83. package/framework/standards/ai-agents/structured-output.md +34 -3
  84. package/framework/standards/ai-agents/sweet-spot.md +23 -3
  85. package/framework/standards/ai-agents/testing-ai.md +236 -0
  86. package/framework/standards/ai-agents/vector-data-extensions.md +50 -20
  87. package/framework/standards/ai-agents/workflows.md +27 -7
  88. package/framework/standards/frontend/design-system/ai-image-generation.md +12 -0
  89. package/framework/standards/frontend/design-system/ai-video-generation.md +13 -0
  90. package/framework/standards/infrastructure/docker/local-compose-isolation.md +3 -1
  91. package/framework/templates/.idea/morph-templates.xml +92 -0
  92. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  93. package/framework/templates/IDE-SNIPPETS.md +262 -262
  94. package/framework/templates/REGISTRY.json +13 -0
  95. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  96. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
  97. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
  98. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
  99. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +1 -1
  100. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
  101. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
  102. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
  103. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
  104. package/framework/templates/code/dotnet/database/migration.cs +83 -83
  105. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  106. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  107. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  108. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
  109. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +1 -1
  110. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  111. package/framework/templates/code/typescript/contracts.ts +167 -167
  112. package/framework/templates/docs/clarifications.md +253 -253
  113. package/framework/templates/docs/schema-analysis.md +119 -119
  114. package/framework/templates/docs/ui-components.md +52 -52
  115. package/framework/templates/docs/ui-design-system.md +76 -76
  116. package/framework/templates/docs/ui-flows.md +167 -167
  117. package/framework/templates/docs/ui-mockups.md +98 -98
  118. package/framework/templates/dotnet/ai-kit/Directory.Build.props +25 -0
  119. package/framework/templates/dotnet/ai-kit/Directory.Packages.props +26 -0
  120. package/framework/templates/dotnet/ai-kit/Morph.AiKit.sln +56 -0
  121. package/framework/templates/dotnet/ai-kit/NuGet.config +12 -0
  122. package/framework/templates/dotnet/ai-kit/README.md +128 -0
  123. package/framework/templates/dotnet/ai-kit/global.json +9 -0
  124. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +65 -0
  125. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +289 -0
  126. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +91 -0
  127. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +91 -0
  128. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +181 -0
  129. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +14 -0
  130. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +174 -0
  131. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +54 -0
  132. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +75 -0
  133. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +71 -0
  134. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +246 -0
  135. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +57 -0
  136. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +78 -0
  137. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +225 -0
  138. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +463 -0
  139. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +32 -0
  140. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +49 -0
  141. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +214 -0
  142. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +168 -0
  143. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +142 -0
  144. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +349 -0
  145. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +42 -0
  146. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +77 -0
  147. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +28 -0
  148. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +107 -0
  149. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +253 -0
  150. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +103 -0
  151. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +259 -0
  152. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +21 -0
  153. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +47 -0
  154. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +102 -0
  155. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +44 -0
  156. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +192 -0
  157. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +94 -0
  158. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +99 -0
  159. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +97 -0
  160. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +133 -0
  161. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +93 -0
  162. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +112 -0
  163. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +288 -0
  164. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +158 -0
  165. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +29 -0
  166. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +208 -0
  167. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +95 -0
  168. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Morph.AiKit.csproj +38 -0
  169. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +119 -0
  170. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +82 -0
  171. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +33 -0
  172. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +267 -0
  173. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +474 -0
  174. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/model-registry.v2.json +87 -0
  175. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +169 -0
  176. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +49 -0
  177. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +495 -0
  178. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +260 -0
  179. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +405 -0
  180. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +379 -0
  181. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +197 -0
  182. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +139 -0
  183. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +157 -0
  184. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +178 -0
  185. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +153 -0
  186. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +194 -0
  187. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +86 -0
  188. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +213 -0
  189. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +95 -0
  190. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +225 -0
  191. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +215 -0
  192. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +33 -0
  193. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +89 -0
  194. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +55 -0
  195. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +60 -0
  196. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +131 -0
  197. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +133 -0
  198. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +127 -0
  199. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +216 -0
  200. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +264 -0
  201. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +148 -0
  202. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +184 -0
  203. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +89 -0
  204. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +98 -0
  205. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +451 -0
  206. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +76 -0
  207. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +33 -0
  208. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +261 -0
  209. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +374 -0
  210. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/.morph-eval-cache.json +12 -0
  211. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/contents.data +13 -0
  212. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/entry.json +7 -0
  213. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/contents.data +13 -0
  214. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/entry.json +7 -0
  215. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/contents.data +13 -0
  216. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/entry.json +7 -0
  217. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/contents.data +13 -0
  218. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/entry.json +7 -0
  219. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/orcamento/acme/a-resposta-estruturada.json +24 -0
  220. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/a-saudacao.json +23 -0
  221. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/b-escalonamento.json +24 -0
  222. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/globex/a-saudacao.json +23 -0
  223. package/framework/templates/feature/decisions.md +122 -122
  224. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
  225. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  226. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
  227. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  228. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
  229. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  230. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
  231. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  232. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  233. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  234. package/framework/templates/infrastructure/github/README.md +56 -56
  235. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  236. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  237. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  238. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  239. package/framework/templates/integrations/asaas-client.cs +387 -387
  240. package/framework/templates/integrations/asaas-webhook.cs +351 -351
  241. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  242. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  243. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  244. package/framework/templates/project/validate.js +120 -120
  245. package/framework/templates/state.template.json +17 -17
  246. package/framework/templates/ui/design-system.css +226 -226
  247. package/package.json +3 -3
  248. package/src/commands/dashboard/dashboard.js +53 -53
  249. package/src/commands/e2e/e2e.js +15 -2
  250. package/src/commands/index.js +13 -13
  251. package/src/commands/project/checkpoint.js +209 -209
  252. package/src/commands/project/doctor.js +148 -6
  253. package/src/commands/project/update.js +469 -153
  254. package/src/commands/project/worktree.js +76 -4
  255. package/src/commands/state/advance.js +24 -2
  256. package/src/commands/state/approve.js +33 -1
  257. package/src/commands/state/create.js +72 -11
  258. package/src/commands/state/finish.js +379 -24
  259. package/src/commands/state/gate-check.js +190 -6
  260. package/src/commands/state/score.js +36 -1
  261. package/src/commands/validation/verify.js +134 -25
  262. package/src/core/state/phase-state-machine.js +197 -197
  263. package/src/core/state/state-manager.js +9 -1
  264. package/src/core/templates/template-renderer.js +477 -477
  265. package/src/dashboard/app/components/DetailPanel.jsx +11 -11
  266. package/src/dashboard/app/components/FlowGraph.jsx +17 -17
  267. package/src/dashboard/app/components/SearchFilter.jsx +22 -22
  268. package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
  269. package/src/dashboard/app/main.jsx +13 -13
  270. package/src/dashboard/app/pages/AgentMap.jsx +370 -370
  271. package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
  272. package/src/dashboard/app/pages/RulesMap.jsx +273 -273
  273. package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
  274. package/src/dashboard/app/pages/Standards.jsx +332 -332
  275. package/src/dashboard/app/styles/index.css +47 -47
  276. package/src/dashboard/index.html +12 -12
  277. package/src/dashboard/package.json +24 -24
  278. package/src/dashboard/scanner/phases-scanner.js +15 -15
  279. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  280. package/src/dashboard/scanner/rules-scanner.js +37 -37
  281. package/src/dashboard/scanner/skills-scanner.js +43 -43
  282. package/src/dashboard/scanner/squads-scanner.js +13 -13
  283. package/src/dashboard/vite-plugin-morph-data.js +17 -17
  284. package/src/dashboard/vite.config.js +24 -24
  285. package/src/lib/ai/ai-checks.js +182 -0
  286. package/src/lib/ai/ai-project-scan.js +347 -0
  287. package/src/lib/ai/code-audit.js +181 -0
  288. package/src/lib/ai/comment-stripper.js +184 -0
  289. package/src/lib/ai/pin-drift.js +138 -0
  290. package/src/lib/ai/registry-audit.js +285 -0
  291. package/src/lib/detectors/design-system-detector.js +188 -188
  292. package/src/lib/detectors/index.js +217 -217
  293. package/src/lib/e2e/compose-env.js +50 -4
  294. package/src/lib/evaluator-report.js +269 -15
  295. package/src/lib/generators/design-system-generator.js +508 -508
  296. package/src/lib/generators/settings-generator.js +209 -209
  297. package/src/lib/git/default-branch.js +92 -4
  298. package/src/lib/git/pr-files.js +56 -0
  299. package/src/lib/hooks/hook-executor.js +167 -167
  300. package/src/lib/installers/effects.js +193 -0
  301. package/src/lib/installers/mcp-installer.js +719 -719
  302. package/src/lib/installers/templates-reconcile.js +3 -3
  303. package/src/lib/risk-detector.js +284 -0
  304. package/src/lib/standards/verified-against.js +276 -0
  305. package/src/lib/tasks/eval-node.js +485 -0
  306. package/src/lib/tasks/linux-runner.js +297 -0
  307. package/src/lib/tasks/task-classifier.js +30 -30
  308. package/src/lib/tasks/task-parser.js +209 -209
  309. package/src/lib/tasks/test-nodes.js +486 -0
  310. package/src/lib/tasks/test-runner.js +410 -33
  311. package/src/lib/validators/checkpoint-persistence.js +39 -39
  312. package/src/lib/validators/css/css-validator.js +340 -340
  313. package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
  314. package/src/lib/validators/shared/issue-counter.js +14 -14
  315. package/src/lib/validators/verify-runner.js +583 -28
  316. package/src/lib/worktree/docker-audit.js +287 -0
  317. package/src/lib/worktree/env-allocator.js +24 -0
  318. package/src/lib/worktree/worktree-audit.js +30 -1
  319. package/src/scripts/setup-infra.js +24 -11
  320. package/src/utils/agent-id-mapper.js +11 -11
  321. package/src/utils/agents-installer.js +18 -8
  322. package/src/utils/claude-md-injector.js +10 -5
  323. package/src/utils/claude-settings-manager.js +300 -300
  324. package/src/utils/file-copier.js +11 -4
  325. package/src/utils/hooks-installer.js +29 -26
  326. package/src/utils/skills-installer.js +36 -29
  327. package/src/utils/version-checker.js +68 -3
  328. package/framework/standards/ai-agents/prompts-in-db.md +0 -214
@@ -1,209 +1,209 @@
1
- /**
2
- * MORPH-SPEC Checkpoint Save/Restore Command
3
- *
4
- * Save and restore full context at phase boundaries.
5
- *
6
- * Usage:
7
- * morph-spec checkpoint save <feature-name>
8
- * morph-spec checkpoint restore <feature-name>
9
- * morph-spec checkpoint list <feature-name>
10
- */
11
-
12
- import fs from 'fs';
13
- import path from 'path';
14
- import chalk from 'chalk';
15
- import { loadState, saveState, getFeature } from '../../core/state/state-manager.js';
16
-
17
- /**
18
- * Get checkpoints directory for a feature
19
- */
20
- function getCheckpointsDir(featureName) {
21
- return path.join(process.cwd(), '.morph/checkpoints', featureName);
22
- }
23
-
24
- /**
25
- * Save checkpoint command
26
- */
27
- export async function checkpointSaveCommand(featureName, options = {}) {
28
- if (!featureName) {
29
- console.error(chalk.red('Error: Feature name required'));
30
- process.exit(1);
31
- }
32
-
33
- const feature = getFeature(featureName);
34
- if (!feature) {
35
- console.error(chalk.red(`Error: Feature '${featureName}' not found`));
36
- process.exit(1);
37
- }
38
-
39
- const outputDir = path.join(process.cwd(), '.morph/features', featureName);
40
- if (!fs.existsSync(outputDir)) {
41
- console.error(chalk.red(`Error: No outputs found for '${featureName}'`));
42
- process.exit(1);
43
- }
44
-
45
- // Create checkpoint directory with timestamp
46
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
47
- const label = options.label || feature.phase;
48
- const checkpointName = `${label}_${timestamp}`;
49
- const checkpointDir = path.join(getCheckpointsDir(featureName), checkpointName);
50
- fs.mkdirSync(checkpointDir, { recursive: true });
51
-
52
- // Copy all output files
53
- const files = fs.readdirSync(outputDir);
54
- let fileCount = 0;
55
- for (const file of files) {
56
- const src = path.join(outputDir, file);
57
- const stat = fs.statSync(src);
58
- if (stat.isFile()) {
59
- fs.copyFileSync(src, path.join(checkpointDir, file));
60
- fileCount++;
61
- }
62
- }
63
-
64
- // Save state snapshot
65
- const stateSnapshot = {
66
- feature: { ...feature },
67
- timestamp: new Date().toISOString(),
68
- phase: feature.phase,
69
- label,
70
- files: files.filter(f => fs.statSync(path.join(outputDir, f)).isFile())
71
- };
72
- fs.writeFileSync(
73
- path.join(checkpointDir, '_checkpoint.json'),
74
- JSON.stringify(stateSnapshot, null, 2)
75
- );
76
-
77
- console.log(chalk.green(`\n✓ Checkpoint saved: ${checkpointName}`));
78
- console.log(chalk.gray(` Phase: ${feature.phase}`));
79
- console.log(chalk.gray(` Files: ${fileCount}`));
80
- console.log(chalk.gray(` Path: ${checkpointDir}\n`));
81
- }
82
-
83
- /**
84
- * Restore checkpoint command
85
- */
86
- export async function checkpointRestoreCommand(featureName, checkpointName, options = {}) {
87
- if (!featureName) {
88
- console.error(chalk.red('Error: Feature name required'));
89
- process.exit(1);
90
- }
91
-
92
- const checkpointsDir = getCheckpointsDir(featureName);
93
- if (!fs.existsSync(checkpointsDir)) {
94
- console.error(chalk.red(`Error: No checkpoints found for '${featureName}'`));
95
- process.exit(1);
96
- }
97
-
98
- // Find checkpoint to restore
99
- let targetDir;
100
- if (checkpointName) {
101
- targetDir = path.join(checkpointsDir, checkpointName);
102
- if (!fs.existsSync(targetDir)) {
103
- console.error(chalk.red(`Error: Checkpoint '${checkpointName}' not found`));
104
- process.exit(1);
105
- }
106
- } else {
107
- // Restore latest
108
- const checkpoints = fs.readdirSync(checkpointsDir)
109
- .filter(d => fs.statSync(path.join(checkpointsDir, d)).isDirectory())
110
- .sort()
111
- .reverse();
112
-
113
- if (checkpoints.length === 0) {
114
- console.error(chalk.red(`Error: No checkpoints found for '${featureName}'`));
115
- process.exit(1);
116
- }
117
-
118
- targetDir = path.join(checkpointsDir, checkpoints[0]);
119
- checkpointName = checkpoints[0];
120
- }
121
-
122
- // Read checkpoint metadata
123
- const metaPath = path.join(targetDir, '_checkpoint.json');
124
- if (!fs.existsSync(metaPath)) {
125
- console.error(chalk.red('Error: Checkpoint metadata not found'));
126
- process.exit(1);
127
- }
128
-
129
- const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
130
-
131
- // Restore output files
132
- const outputDir = path.join(process.cwd(), '.morph/features', featureName);
133
- fs.mkdirSync(outputDir, { recursive: true });
134
-
135
- const files = fs.readdirSync(targetDir).filter(f => f !== '_checkpoint.json');
136
- for (const file of files) {
137
- fs.copyFileSync(path.join(targetDir, file), path.join(outputDir, file));
138
- }
139
-
140
- // Restore state
141
- const state = loadState();
142
- if (state.features[featureName] && meta.feature) {
143
- // Preserve some current fields, restore phase and outputs
144
- state.features[featureName].phase = meta.feature.phase;
145
- state.features[featureName].outputs = meta.feature.outputs;
146
- state.features[featureName].tasks = meta.feature.tasks;
147
- state.features[featureName].approvalGates = meta.feature.approvalGates;
148
- state.features[featureName].updatedAt = new Date().toISOString();
149
- saveState(state);
150
- }
151
-
152
- console.log(chalk.green(`\n✓ Checkpoint restored: ${checkpointName}`));
153
- console.log(chalk.gray(` Phase: ${meta.phase}`));
154
- console.log(chalk.gray(` Files restored: ${files.length}`));
155
- console.log(chalk.gray(` Original date: ${meta.timestamp}\n`));
156
- }
157
-
158
- /**
159
- * List checkpoints command
160
- */
161
- export async function checkpointListCommand(featureName, options = {}) {
162
- if (!featureName) {
163
- console.error(chalk.red('Error: Feature name required'));
164
- process.exit(1);
165
- }
166
-
167
- const checkpointsDir = getCheckpointsDir(featureName);
168
- if (!fs.existsSync(checkpointsDir)) {
169
- console.log(chalk.gray(`No checkpoints found for '${featureName}'.\n`));
170
- return;
171
- }
172
-
173
- const checkpoints = fs.readdirSync(checkpointsDir)
174
- .filter(d => fs.statSync(path.join(checkpointsDir, d)).isDirectory())
175
- .sort()
176
- .reverse();
177
-
178
- if (checkpoints.length === 0) {
179
- console.log(chalk.gray(`No checkpoints found for '${featureName}'.\n`));
180
- return;
181
- }
182
-
183
- // JSON output
184
- if (options.json) {
185
- const list = checkpoints.map(cp => {
186
- const metaPath = path.join(checkpointsDir, cp, '_checkpoint.json');
187
- const meta = fs.existsSync(metaPath) ? JSON.parse(fs.readFileSync(metaPath, 'utf8')) : {};
188
- return { name: cp, phase: meta.phase, timestamp: meta.timestamp, files: meta.files?.length || 0 };
189
- });
190
- console.log(JSON.stringify({ feature: featureName, checkpoints: list }, null, 2));
191
- return;
192
- }
193
-
194
- // Visual output
195
- console.log(chalk.cyan(`\nCheckpoints for '${featureName}':\n`));
196
-
197
- for (const cp of checkpoints) {
198
- const metaPath = path.join(checkpointsDir, cp, '_checkpoint.json');
199
- let meta = {};
200
- if (fs.existsSync(metaPath)) {
201
- meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
202
- }
203
-
204
- const date = meta.timestamp ? new Date(meta.timestamp).toLocaleString() : 'unknown';
205
- console.log(chalk.white(` ${cp}`));
206
- console.log(chalk.gray(` Phase: ${meta.phase || 'unknown'} | Files: ${meta.files?.length || '?'} | ${date}`));
207
- }
208
- console.log('');
209
- }
1
+ /**
2
+ * MORPH-SPEC Checkpoint Save/Restore Command
3
+ *
4
+ * Save and restore full context at phase boundaries.
5
+ *
6
+ * Usage:
7
+ * morph-spec checkpoint save <feature-name>
8
+ * morph-spec checkpoint restore <feature-name>
9
+ * morph-spec checkpoint list <feature-name>
10
+ */
11
+
12
+ import fs from 'fs';
13
+ import path from 'path';
14
+ import chalk from 'chalk';
15
+ import { loadState, saveState, getFeature } from '../../core/state/state-manager.js';
16
+
17
+ /**
18
+ * Get checkpoints directory for a feature
19
+ */
20
+ function getCheckpointsDir(featureName) {
21
+ return path.join(process.cwd(), '.morph/checkpoints', featureName);
22
+ }
23
+
24
+ /**
25
+ * Save checkpoint command
26
+ */
27
+ export async function checkpointSaveCommand(featureName, options = {}) {
28
+ if (!featureName) {
29
+ console.error(chalk.red('Error: Feature name required'));
30
+ process.exit(1);
31
+ }
32
+
33
+ const feature = getFeature(featureName);
34
+ if (!feature) {
35
+ console.error(chalk.red(`Error: Feature '${featureName}' not found`));
36
+ process.exit(1);
37
+ }
38
+
39
+ const outputDir = path.join(process.cwd(), '.morph/features', featureName);
40
+ if (!fs.existsSync(outputDir)) {
41
+ console.error(chalk.red(`Error: No outputs found for '${featureName}'`));
42
+ process.exit(1);
43
+ }
44
+
45
+ // Create checkpoint directory with timestamp
46
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
47
+ const label = options.label || feature.phase;
48
+ const checkpointName = `${label}_${timestamp}`;
49
+ const checkpointDir = path.join(getCheckpointsDir(featureName), checkpointName);
50
+ fs.mkdirSync(checkpointDir, { recursive: true });
51
+
52
+ // Copy all output files
53
+ const files = fs.readdirSync(outputDir);
54
+ let fileCount = 0;
55
+ for (const file of files) {
56
+ const src = path.join(outputDir, file);
57
+ const stat = fs.statSync(src);
58
+ if (stat.isFile()) {
59
+ fs.copyFileSync(src, path.join(checkpointDir, file));
60
+ fileCount++;
61
+ }
62
+ }
63
+
64
+ // Save state snapshot
65
+ const stateSnapshot = {
66
+ feature: { ...feature },
67
+ timestamp: new Date().toISOString(),
68
+ phase: feature.phase,
69
+ label,
70
+ files: files.filter(f => fs.statSync(path.join(outputDir, f)).isFile())
71
+ };
72
+ fs.writeFileSync(
73
+ path.join(checkpointDir, '_checkpoint.json'),
74
+ JSON.stringify(stateSnapshot, null, 2)
75
+ );
76
+
77
+ console.log(chalk.green(`\n✓ Checkpoint saved: ${checkpointName}`));
78
+ console.log(chalk.gray(` Phase: ${feature.phase}`));
79
+ console.log(chalk.gray(` Files: ${fileCount}`));
80
+ console.log(chalk.gray(` Path: ${checkpointDir}\n`));
81
+ }
82
+
83
+ /**
84
+ * Restore checkpoint command
85
+ */
86
+ export async function checkpointRestoreCommand(featureName, checkpointName, options = {}) {
87
+ if (!featureName) {
88
+ console.error(chalk.red('Error: Feature name required'));
89
+ process.exit(1);
90
+ }
91
+
92
+ const checkpointsDir = getCheckpointsDir(featureName);
93
+ if (!fs.existsSync(checkpointsDir)) {
94
+ console.error(chalk.red(`Error: No checkpoints found for '${featureName}'`));
95
+ process.exit(1);
96
+ }
97
+
98
+ // Find checkpoint to restore
99
+ let targetDir;
100
+ if (checkpointName) {
101
+ targetDir = path.join(checkpointsDir, checkpointName);
102
+ if (!fs.existsSync(targetDir)) {
103
+ console.error(chalk.red(`Error: Checkpoint '${checkpointName}' not found`));
104
+ process.exit(1);
105
+ }
106
+ } else {
107
+ // Restore latest
108
+ const checkpoints = fs.readdirSync(checkpointsDir)
109
+ .filter(d => fs.statSync(path.join(checkpointsDir, d)).isDirectory())
110
+ .sort()
111
+ .reverse();
112
+
113
+ if (checkpoints.length === 0) {
114
+ console.error(chalk.red(`Error: No checkpoints found for '${featureName}'`));
115
+ process.exit(1);
116
+ }
117
+
118
+ targetDir = path.join(checkpointsDir, checkpoints[0]);
119
+ checkpointName = checkpoints[0];
120
+ }
121
+
122
+ // Read checkpoint metadata
123
+ const metaPath = path.join(targetDir, '_checkpoint.json');
124
+ if (!fs.existsSync(metaPath)) {
125
+ console.error(chalk.red('Error: Checkpoint metadata not found'));
126
+ process.exit(1);
127
+ }
128
+
129
+ const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
130
+
131
+ // Restore output files
132
+ const outputDir = path.join(process.cwd(), '.morph/features', featureName);
133
+ fs.mkdirSync(outputDir, { recursive: true });
134
+
135
+ const files = fs.readdirSync(targetDir).filter(f => f !== '_checkpoint.json');
136
+ for (const file of files) {
137
+ fs.copyFileSync(path.join(targetDir, file), path.join(outputDir, file));
138
+ }
139
+
140
+ // Restore state
141
+ const state = loadState();
142
+ if (state.features[featureName] && meta.feature) {
143
+ // Preserve some current fields, restore phase and outputs
144
+ state.features[featureName].phase = meta.feature.phase;
145
+ state.features[featureName].outputs = meta.feature.outputs;
146
+ state.features[featureName].tasks = meta.feature.tasks;
147
+ state.features[featureName].approvalGates = meta.feature.approvalGates;
148
+ state.features[featureName].updatedAt = new Date().toISOString();
149
+ saveState(state);
150
+ }
151
+
152
+ console.log(chalk.green(`\n✓ Checkpoint restored: ${checkpointName}`));
153
+ console.log(chalk.gray(` Phase: ${meta.phase}`));
154
+ console.log(chalk.gray(` Files restored: ${files.length}`));
155
+ console.log(chalk.gray(` Original date: ${meta.timestamp}\n`));
156
+ }
157
+
158
+ /**
159
+ * List checkpoints command
160
+ */
161
+ export async function checkpointListCommand(featureName, options = {}) {
162
+ if (!featureName) {
163
+ console.error(chalk.red('Error: Feature name required'));
164
+ process.exit(1);
165
+ }
166
+
167
+ const checkpointsDir = getCheckpointsDir(featureName);
168
+ if (!fs.existsSync(checkpointsDir)) {
169
+ console.log(chalk.gray(`No checkpoints found for '${featureName}'.\n`));
170
+ return;
171
+ }
172
+
173
+ const checkpoints = fs.readdirSync(checkpointsDir)
174
+ .filter(d => fs.statSync(path.join(checkpointsDir, d)).isDirectory())
175
+ .sort()
176
+ .reverse();
177
+
178
+ if (checkpoints.length === 0) {
179
+ console.log(chalk.gray(`No checkpoints found for '${featureName}'.\n`));
180
+ return;
181
+ }
182
+
183
+ // JSON output
184
+ if (options.json) {
185
+ const list = checkpoints.map(cp => {
186
+ const metaPath = path.join(checkpointsDir, cp, '_checkpoint.json');
187
+ const meta = fs.existsSync(metaPath) ? JSON.parse(fs.readFileSync(metaPath, 'utf8')) : {};
188
+ return { name: cp, phase: meta.phase, timestamp: meta.timestamp, files: meta.files?.length || 0 };
189
+ });
190
+ console.log(JSON.stringify({ feature: featureName, checkpoints: list }, null, 2));
191
+ return;
192
+ }
193
+
194
+ // Visual output
195
+ console.log(chalk.cyan(`\nCheckpoints for '${featureName}':\n`));
196
+
197
+ for (const cp of checkpoints) {
198
+ const metaPath = path.join(checkpointsDir, cp, '_checkpoint.json');
199
+ let meta = {};
200
+ if (fs.existsSync(metaPath)) {
201
+ meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
202
+ }
203
+
204
+ const date = meta.timestamp ? new Date(meta.timestamp).toLocaleString() : 'unknown';
205
+ console.log(chalk.white(` ${cp}`));
206
+ console.log(chalk.gray(` Phase: ${meta.phase || 'unknown'} | Files: ${meta.files?.length || '?'} | ${date}`));
207
+ }
208
+ console.log('');
209
+ }
@@ -9,6 +9,7 @@ import { pathExists, readJson } from '../../utils/file-copier.js';
9
9
  import { checkClaudeMdImport } from '../../utils/claude-md-injector.js';
10
10
  import { buildPrIndex } from '../../lib/git/pr-index.js';
11
11
  import { pendingPrFeatures, MORPH_BRANCH_RE } from '../../lib/git/pending-prs.js';
12
+ import { prCarriesFeature } from '../../lib/git/pr-files.js';
12
13
  import {
13
14
  checkCLIOutdated,
14
15
  checkProjectOutdated,
@@ -19,6 +20,7 @@ import { checkMissingAgents, checkAgentsDrift } from '../../utils/agents-install
19
20
  import { getProjectTags } from '../../lib/stack-filter.js';
20
21
  import { auditGraph } from '../../lib/graph/graph-health.js';
21
22
  import { auditFeatureLifecycle } from '../../core/state/lifecycle-audit.js';
23
+ import { validateTestNodes, morphConfigPath } from '../../lib/tasks/test-nodes.js';
22
24
 
23
25
  const isWindows = platform() === 'win32';
24
26
  /**
@@ -367,6 +369,43 @@ async function doctorFullCommand(frameworkRoot) {
367
369
  check('state.json', false, true, 'not found (run: morph-spec init)');
368
370
  }
369
371
 
372
+ // ── 8. project.tests[] — o contrato do config, camada 3 ─────────────────
373
+ //
374
+ // As outras duas camadas são o `config.schema.json` (declarativa) e
375
+ // `validateTestNodes()` chamada pelo `verify` (fail-closed, em uso). Esta
376
+ // aqui existe para o defeito chegar ANTES do gate: um `id` com typo em
377
+ // `tests[]` só se manifesta quando alguém roda `morph-spec verify` e lê um
378
+ // nó vermelho — o doctor o mostra na checagem de saúde, que é onde se olha
379
+ // primeiro. Reusa `validateTestNodes` (a MESMA autoridade que o verify
380
+ // consulta): uma segunda leitura do mesmo campo aqui seria uma segunda
381
+ // opinião sobre o que é um nó válido, e as duas divergiriam.
382
+ //
383
+ // PULA de verdade — sem check e sem cabeçalho — quando não há `config.json`
384
+ // ou quando `project.tests` não foi declarado. Um projeto sem declaração usa
385
+ // o fallback de quatro passos e não tem contrato a violar; este repositório
386
+ // é exatamente esse caso, e um check que não pode passar aqui é ruído, não
387
+ // achado (`test/commands/doctor.test.js`: `doctor --full` sem nenhum `✗`).
388
+ // WARN e nunca ERROR: a barreira fail-closed é o `verify`; o doctor informa.
389
+ const testsConfigPath = morphConfigPath(frameworkRoot);
390
+ if (await pathExists(testsConfigPath)) {
391
+ let testsConfig = null;
392
+ try {
393
+ testsConfig = JSON.parse((await fs.readFile(testsConfigPath, 'utf8')).replace(/^\uFEFF/, ''));
394
+ } catch {
395
+ testsConfig = null; // config ilegível já é reportado pelo doctor base
396
+ }
397
+ if (testsConfig?.project?.tests !== undefined) {
398
+ console.log(chalk.cyan('\n .morph/config/config.json — project.tests[]'));
399
+ const declared = validateTestNodes(testsConfig, frameworkRoot);
400
+ const broken = declared.filter((n) => n.status === 'fail');
401
+ check(`config tests[] (${declared.length - broken.length}/${declared.length})`,
402
+ broken.length === 0, true,
403
+ broken.length > 0
404
+ ? broken.map((n) => n.reason ?? `invalid-test-node: ${n.id}`).join('; ')
405
+ : '');
406
+ }
407
+ }
408
+
370
409
  // ── Display ──────────────────────────────────────────────────────────────
371
410
  console.log('\n' + '─'.repeat(60));
372
411
  console.log(chalk.bold('\nResults:\n'));
@@ -587,11 +626,12 @@ export async function doctorWorktreesCommand(targetPath, options = {}) {
587
626
 
588
627
  const issues = auditWorktrees(targetPath);
589
628
  // `severity: 'info'` entries (a worktree carrying unintegrated work) and
590
- // `severity: 'warn'` entries (a worktree created outside morph's container)
591
- // are REPORTED but are not failures — a `doctor --worktrees || ...` in CI must
592
- // not go red because somebody has an open PR or a hand-made tree. The human
593
- // path below splits the same way; both surfaces must agree on what counts as
594
- // broken.
629
+ // `severity: 'warn'` entries (a worktree created outside morph's container,
630
+ // and the docker findings from `docker-audit.js`: an orphan stack, a port
631
+ // held by another compose project) are REPORTED but are not failures — a
632
+ // `doctor --worktrees || ...` in CI must not go red because somebody has an
633
+ // open PR, a hand-made tree, or a container still up. The human path below
634
+ // splits the same way; both surfaces must agree on what counts as broken.
595
635
  const isProblem = (i) => !['info', 'warn'].includes(i.severity);
596
636
 
597
637
  if (options.json) {
@@ -649,7 +689,10 @@ const PR_STALE_DAYS = 7;
649
689
  * revisão que nenhuma superfície local reivindica.
650
690
  * pr-merged-not-landed — o PR mergeou mas `archive/{f}` não está aqui. O
651
691
  * archive viaja no diff do PR, então ou falta um
652
- * `git pull`, ou ele não estava no diff.
692
+ * `git pull`, ou ele não estava no diff. Branch
693
+ * `morph/*` cujo diff nunca tocou
694
+ * `.morph/{features,archive}/{f}/` não conta: o nome
695
+ * não prova feature (ver pr-files.js).
653
696
  *
654
697
  * Offline-safe: índice `null` devolve `null`, NUNCA lista vazia — lista vazia
655
698
  * afirmaria "perguntei e não achei nada", que é diferente de "não pude
@@ -679,6 +722,9 @@ function auditPullRequests(root) {
679
722
  if (!m || pr.state !== 'MERGED') continue;
680
723
  const feature = m[1];
681
724
  if (existsSync(join(root, '.morph', 'archive', feature))) continue;
725
+ // Só `false` (diff lido, sem a feature) cala o aviso; `null` — não pude
726
+ // ler o diff — o mantém, ou um `git pull` realmente faltando ficaria mudo.
727
+ if (prCarriesFeature(root, pr.number, feature) === false) continue;
682
728
  issues.push({
683
729
  feature,
684
730
  type: 'pr-merged-not-landed',
@@ -689,6 +735,86 @@ function auditPullRequests(root) {
689
735
  return issues;
690
736
  }
691
737
 
738
+ /**
739
+ * `morph-spec doctor --ai` — as cinco checagens de projeto .NET com agentes.
740
+ *
741
+ * **Superfície própria, no molde do `--mcp`, e é decisão, não acidente.** O
742
+ * `--ai` NÃO entra no `--full` nem no `doctor` default:
743
+ *
744
+ * - o renderer do `--full` é `ok → ✓`, `warn → ⚠`, **`else → ✗`** — não tem o
745
+ * ramo `skip → ○`. Uma checagem de IA lá seria VERMELHA neste repositório
746
+ * mesmo dizendo "não verificado", e `test/commands/doctor.test.js` exige
747
+ * `doesNotMatch(out, /✗/)` para `doctor --full` aqui, com a justificativa
748
+ * escrita no próprio teste: *"every check must be satisfiable here — a
749
+ * permanently red one teaches nothing"*;
750
+ * - o `doctor` default é o comando mais rodado do dia, e cinco linhas `○`
751
+ * permanentes em todo projeto sem .NET é ruído com custo e sem informação.
752
+ *
753
+ * Exit code: 1 se houver ao menos um `error`; 0 caso contrário. `warn` e `skip`
754
+ * nunca mudam o código de saída.
755
+ *
756
+ * @param {string} targetPath
757
+ * @param {object} [options] - repassado a `collectAiChecks` (`config`, `deps`)
758
+ * @returns {Promise<Array>} as checagens, para o canário assere-las sem parsear texto
759
+ */
760
+ export async function doctorAiCommand(targetPath, options = {}) {
761
+ const { collectAiChecks, aiExitCode, allSkipped } = await import('../../lib/ai/ai-checks.js');
762
+ const { readMorphConfig } = await import('../../lib/tasks/test-runner.js');
763
+
764
+ logger.header('AI Health Check (.NET + agentes)');
765
+ logger.blank();
766
+
767
+ const config = options.config !== undefined ? options.config : readMorphConfig(targetPath);
768
+ const checks = collectAiChecks(targetPath, { config, deps: options.deps ?? {} });
769
+
770
+ if (allSkipped(checks)) {
771
+ // Condição 4 do silêncio: nenhuma checagem teve insumo. Uma linha, não
772
+ // cinco `○` vazios — listar cinco vezes "não verificado" é o ruído que
773
+ // esta superfície existe para não produzir.
774
+ const reasons = [...new Set(checks.map((c) => c.reason))];
775
+ for (const reason of reasons) {
776
+ console.log(chalk.gray(` ○ Nenhuma das ${checks.length} checagens teve insumo — ${reason}.`));
777
+ }
778
+ logger.blank();
779
+ process.exitCode = 0;
780
+ return checks;
781
+ }
782
+
783
+ for (const c of checks) {
784
+ if (c.status === 'ok') {
785
+ console.log(chalk.green(` ✓ ${c.name}`));
786
+ } else if (c.status === 'skip') {
787
+ console.log(chalk.gray(` ○ ${c.name}`) + chalk.gray(` — não verificado: ${c.reason}`));
788
+ } else if (c.status === 'warn') {
789
+ console.log(chalk.yellow(` ⚠ ${c.name}`));
790
+ } else {
791
+ console.log(chalk.red(` ✗ ${c.name}`));
792
+ }
793
+ for (const f of c.findings) {
794
+ const mark = f.severity === 'error' ? chalk.red(' ✗') : chalk.yellow(' →');
795
+ console.log(`${mark} ${chalk.gray(f.message)}`);
796
+ }
797
+ for (const file of c.unverified ?? []) {
798
+ console.log(chalk.gray(` ○ não verificado: ${relative(targetPath, file) || file} (ilegível)`));
799
+ }
800
+ }
801
+
802
+ logger.blank();
803
+ const errors = checks.filter((c) => c.status === 'error').length;
804
+ const warns = checks.filter((c) => c.status === 'warn').length;
805
+ const skips = checks.filter((c) => c.status === 'skip').length;
806
+ const oks = checks.filter((c) => c.status === 'ok').length;
807
+
808
+ const summary = `${oks} ok · ${warns} aviso(s) · ${errors} erro(s) · ${skips} não verificada(s)`;
809
+ if (errors > 0) console.log(chalk.red(` ❌ ${summary}`));
810
+ else if (warns > 0) console.log(chalk.yellow(` ⚠️ ${summary}`));
811
+ else console.log(chalk.green(` ✅ ${summary}`));
812
+ logger.blank();
813
+
814
+ process.exitCode = aiExitCode(checks);
815
+ return checks;
816
+ }
817
+
692
818
  export async function doctorCommand(options = {}) {
693
819
  // `--json` and `--prune` only mean something under `--worktrees`. Falling
694
820
  // through to the human health check would hand a script a banner and a table
@@ -703,6 +829,22 @@ export async function doctorCommand(options = {}) {
703
829
  return false;
704
830
  }
705
831
 
832
+ // `--ai` é superfície própria (D1). Combiná-la com outra superfície pediria
833
+ // dois relatórios com dois códigos de saída no mesmo processo — o segundo
834
+ // apagaria o primeiro em silêncio. Mesmo molde da guarda acima: recusa em
835
+ // vez de fingir.
836
+ if (options.ai) {
837
+ const conflicts = ['full', 'mcp', 'worktrees'].filter((f) => options[f]);
838
+ if (conflicts.length) {
839
+ const flags = conflicts.map((f) => `--${f}`).join(' e ');
840
+ logger.error(`--ai não se combina com ${flags} — cada superfície tem o seu relatório e o seu código de saída.`);
841
+ logger.dim(' Rode: morph-spec doctor --ai');
842
+ process.exitCode = 1;
843
+ return false;
844
+ }
845
+ return doctorAiCommand(process.cwd(), options);
846
+ }
847
+
706
848
  // Reset mode: remove morph-managed settings entries
707
849
  if (options.reset) {
708
850
  const targetPath = process.cwd();