@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
@@ -1006,8 +1006,15 @@ function isLiveSiblingWorktreeRegistration(relPath, root, feature) {
1006
1006
  * @param {string} rootPath - the primary root, for the replica comparison
1007
1007
  * @param {{ discardIgnored?: boolean }} options
1008
1008
  * @returns {{ shieldable: string[], blocking: string[], discarded: string[], replicable: string[] }}
1009
+ *
1010
+ * EXPORTADA para que o `finish --dry-run` (pré-voo) responda EXATAMENTE a
1011
+ * mesma pergunta que o teardown real vai responder. Uma segunda enumeração de
1012
+ * `git status --ignored` no finish.js — com a sua própria cópia de
1013
+ * IGNORED_SHIELDABLE_PREFIXES — faria o pré-voo prometer uma coisa e o
1014
+ * comando fazer outra, que é a única maneira de um dry-run ser pior que
1015
+ * nenhum. Consumida read-only: o pré-voo não copia, não apaga, não decide.
1009
1016
  */
1010
- function classifyIgnoredLeftovers(worktreePath, rootPath, options = {}) {
1017
+ export function classifyIgnoredLeftovers(worktreePath, rootPath, options = {}) {
1011
1018
  let out;
1012
1019
  try {
1013
1020
  // `-z` + execFile: NUL-delimited, no shell, no C-style quoting to undo —
@@ -1296,19 +1303,46 @@ function classifyRemovalError(stderr) {
1296
1303
  * @returns {Promise<{ removed: boolean, failure: {kind: string, detail: string}|null }>}
1297
1304
  */
1298
1305
  async function attemptWorktreeRemoval(worktreePath, root) {
1299
- let lastStderr = '';
1306
+ const stderrs = [];
1300
1307
  for (let round = 0; round < 2; round++) {
1301
1308
  for (const args of [['worktree', 'remove', worktreePath], ['worktree', 'remove', '--force', worktreePath]]) {
1302
1309
  try {
1303
1310
  execFileSync('git', args, { cwd: root, stdio: 'pipe' });
1304
1311
  return { removed: true, failure: null };
1305
1312
  } catch (err) {
1306
- lastStderr = err?.stderr ? String(err.stderr) : (err?.message || '');
1313
+ stderrs.push(err?.stderr ? String(err.stderr) : (err?.message || ''));
1307
1314
  }
1308
1315
  }
1309
1316
  if (round === 0) await new Promise((r) => setTimeout(r, 200));
1310
1317
  }
1311
- return { removed: false, failure: classifyRemovalError(lastStderr) };
1318
+ return { removed: false, failure: pickRemovalFailure(stderrs) };
1319
+ }
1320
+
1321
+ /**
1322
+ * Which of the attempts' errors the user should read.
1323
+ *
1324
+ * The LAST one used to win, and it was the worst choice: when the first
1325
+ * `git worktree remove` deletes the content and de-registers the tree but the
1326
+ * directory itself is held (a session with its cwd there), every later attempt
1327
+ * answers only `'…' is not a working tree` — an echo that hid the real cause
1328
+ * (the lock) and made the hint send the user to a command that can no longer
1329
+ * reach the tree. A lock anywhere wins; otherwise the first REAL error wins.
1330
+ *
1331
+ * @param {string[]} stderrs - one entry per failed attempt, in order
1332
+ * @returns {{ kind: string, detail: string, alreadyDeregistered: boolean }}
1333
+ */
1334
+ export function pickRemovalFailure(stderrs) {
1335
+ const list = (stderrs || []).map((s) => String(s || '')).filter((s) => s.trim());
1336
+ const notAWorktree = (s) => /is not a working tree/i.test(s);
1337
+ const classified = list.map(classifyRemovalError);
1338
+ const firstReal = list.findIndex((s) => !notAWorktree(s));
1339
+ const chosen = classified.find((c) => c.kind === 'in-use')
1340
+ || (firstReal >= 0 ? classified[firstReal] : classified[classified.length - 1])
1341
+ || { kind: 'unknown', detail: '' };
1342
+ // Git dropped the registration BETWEEN attempts: a failure for another reason
1343
+ // followed by "is not a working tree". Only the directory is left.
1344
+ const alreadyDeregistered = firstReal >= 0 && list.slice(firstReal + 1).some(notAWorktree);
1345
+ return { ...chosen, alreadyDeregistered };
1312
1346
  }
1313
1347
 
1314
1348
  /**
@@ -1444,6 +1478,10 @@ export async function teardownWorktree(worktreePath, { root, feature, branch, op
1444
1478
  ? `worktree NÃO removido: o backup falhou. Estes arquivos existem só neste worktree e a remoção os apagaria: ${sample(ignored.shieldable)}. `
1445
1479
  + (ignored.replicable.length ? `(${sample(ignored.replicable)} são idênticos aos da raiz — esses não são o problema.) ` : '')
1446
1480
  + `O backup iria para \`${backupDir}\`; libere espaço/permissão ali e rode \`${removeCmd}\`, ou copie os arquivos você mesmo e rode \`${removeCmd} --discard-ignored\` para pular o backup`
1481
+ : removalFailure?.alreadyDeregistered
1482
+ ? `worktree NÃO removido por inteiro: a 1ª tentativa do git já apagou o conteúdo e desregistrou o worktree, mas o diretório "${worktreePath}" ficou`
1483
+ + (removalFailure.kind === 'in-use' ? ' — EM USO por outro processo (instância do Claude Code, VS Code, dev server ou terminal com cwd nele)' : '')
1484
+ + `${removalFailure.detail ? ` (${removalFailure.detail})` : ''}. \`${removeCmd}\` não o alcança mais — o git não o conhece. Feche o que o usa, confira que está vazio e sem junction, e apague só o diretório (sem recursão)`
1447
1485
  : removalFailure?.kind === 'in-use'
1448
1486
  ? `worktree NÃO removido: o diretório está EM USO por outro processo (instância do Claude Code, VS Code, dev server ou terminal com cwd em "${worktreePath}"). A infra compartilhada ficou DESMONTADA de propósito — feche o que está usando o diretório e rode \`${removeCmd}\`. Se preferir continuar trabalhando nele, restaure a infra com \`morph-spec worktree link --all\``
1449
1487
  : `worktree NÃO removido: a remoção falhou${removalFailure?.detail ? ` (${removalFailure.detail})` : ''}. A infra compartilhada ficou DESMONTADA de propósito — rode \`${removeCmd}\` para tentar de novo, ou \`morph-spec worktree link --all\` para voltar a trabalhar nele`;
@@ -1490,6 +1528,30 @@ export function cwdIsInsideWorktree(cwd, worktreePath) {
1490
1528
  return cwdNorm === wtNorm || cwdNorm.startsWith(`${wtNorm}/`);
1491
1529
  }
1492
1530
 
1531
+ /**
1532
+ * Is the Claude Code SESSION that ran this command anchored in the worktree?
1533
+ *
1534
+ * Not the same question as `cwdIsInsideWorktree`. That one sees the PROCESS
1535
+ * cwd, and `cd <root> && morph-spec finish` answers it "no" — while the session
1536
+ * stays anchored in the worktree: its CLAUDE_PROJECT_DIR is the worktree, and
1537
+ * every project hook runs `node "$CLAUDE_PROJECT_DIR/.morph/framework/hooks/…"`.
1538
+ * Tearing that worktree down unlinks the junction those hooks resolve through,
1539
+ * and the directory stays held by the session — so the removal fails half-way
1540
+ * and every hook of every later tool call dies with MODULE_NOT_FOUND.
1541
+ *
1542
+ * The anchor arrives as MORPH_SESSION_PROJECT_DIR, exported by the
1543
+ * `worktree-env-inject` PreToolUse hook (the Bash tool does not export
1544
+ * CLAUDE_PROJECT_DIR itself); CLAUDE_PROJECT_DIR is honoured when present.
1545
+ *
1546
+ * @param {string} worktreePath
1547
+ * @param {NodeJS.ProcessEnv} [env]
1548
+ * @returns {boolean}
1549
+ */
1550
+ export function sessionAnchoredInWorktree(worktreePath, env = process.env) {
1551
+ const anchor = env.MORPH_SESSION_PROJECT_DIR || env.CLAUDE_PROJECT_DIR || '';
1552
+ return anchor ? cwdIsInsideWorktree(anchor, worktreePath) : false;
1553
+ }
1554
+
1493
1555
  // ── remove ───────────────────────────────────────────────────────────────
1494
1556
 
1495
1557
  /**
@@ -1573,6 +1635,16 @@ export async function worktreeRemoveCommand(featureArg, options = {}) {
1573
1635
  hint: `saia do worktree antes de removê-lo — o processo trava o próprio diretório de trabalho. Rode de novo a partir de "${root}"` }, 1);
1574
1636
  }
1575
1637
 
1638
+ // The SESSION's anchor, not the process cwd (see sessionAnchoredInWorktree):
1639
+ // refusing here, before the junctions are touched, leaves the session's hooks
1640
+ // intact. Unlike finish there is nothing else to do in this command, so the
1641
+ // refusal IS the deferral.
1642
+ if (sessionAnchoredInWorktree(worktreePath)) {
1643
+ const again = featureAddressable && feature ? `morph-spec worktree remove ${feature}` : `morph-spec worktree remove --target "${worktreePath}"`;
1644
+ emit({ removed: false, error: 'session-anchored-in-worktree', path: worktreePath, root,
1645
+ hint: `a sessão do Claude Code que rodou este comando está ancorada neste worktree — os hooks dela resolvem \`.morph/framework\` por ele, e desmontá-lo quebraria todo tool call seguinte (e o diretório seguiria travado pela sessão). Feche esta sessão e rode \`${again}\` de uma sessão aberta em "${root}"` }, 1);
1646
+ }
1647
+
1576
1648
  // `--if-clean` — the WorktreeRemove hook's contract: Claude Code fires it
1577
1649
  // when it believes the tree is disposable, but "disposable" is the user's
1578
1650
  // call, not the harness's. Any uncommitted or untracked file refuses the
@@ -198,7 +198,23 @@ export async function advanceCommand(featureName, options = {}) {
198
198
  process.exit(1);
199
199
  }
200
200
 
201
- if (gate) {
201
+ // Gate idempotency (F15): `setApprovalGate` REPLACES the gate object whole
202
+ // (state-manager.js), so re-writing an already-approved gate does not
203
+ // refresh it — it destroys `approvedBy`, `timestamp`, `notes` and
204
+ // `selfAssessed` in one assignment. And `resolveApprovedBy` above answers
205
+ // "auto" in every headless context, so `approve f proposal --approver
206
+ // lucas` followed by `advance f` used to rewrite a human's signature as
207
+ // "auto" — the one stamp the charter forbids on a human-approved gate.
208
+ // The state-sync hook already carries exactly this guard; `advance`, the
209
+ // other gate write-path, simply never got it.
210
+ //
211
+ // It skips ONLY the write. The scaffold and the handoff below still run,
212
+ // so re-running `advance` after a failed scaffold keeps working — the
213
+ // reason this is a guard and not an abort.
214
+ const priorGate = gate ? feature.approvalGates?.[gate] : null;
215
+ const alreadyApproved = priorGate?.approved === true;
216
+
217
+ if (gate && !alreadyApproved) {
202
218
  await setApprovalGate(featureName, gate, true, {
203
219
  approvedBy,
204
220
  approvedAt: new Date().toISOString(),
@@ -226,7 +242,13 @@ export async function advanceCommand(featureName, options = {}) {
226
242
  if (missingArtifacts.length) {
227
243
  console.log(chalk.yellow(` ⚠️ --force: avançou SEM ${missingArtifacts.join(', ')}.`));
228
244
  }
229
- if (gate) console.log(chalk.gray(` Gate "${gate}" approved (${approvedBy}).`));
245
+ // Report what actually happened: claiming "approved (auto)" when nothing
246
+ // was written is how the destroyed signature stayed invisible.
247
+ if (gate && alreadyApproved) {
248
+ console.log(chalk.gray(` Gate "${gate}" was already approved (${priorGate.approvedBy || 'unknown'}) — approval preserved, nothing re-written.`));
249
+ } else if (gate) {
250
+ console.log(chalk.gray(` Gate "${gate}" approved (${approvedBy}).`));
251
+ }
230
252
  if (nextDir) console.log(chalk.gray(` Scaffolded ${nextDir}/ — ready for the next phase's first artifact.\n`));
231
253
  else console.log('');
232
254
  } catch (error) {
@@ -180,7 +180,13 @@ export async function approveCommand(featureName, gateName, options = {}) {
180
180
  if (currentStatus?.approved) {
181
181
  console.log(chalk.yellow(`\n⚠️ Gate "${gateName}" is already approved`));
182
182
  console.log(chalk.gray(`Approved at: ${currentStatus.timestamp}`));
183
- console.log(chalk.gray(`Approved by: ${currentStatus.approvedBy || 'Unknown'}\n`));
183
+ console.log(chalk.gray(`Approved by: ${currentStatus.approvedBy || 'Unknown'}`));
184
+ // This early-out is a no-op that still exits 0 — deliberate, and
185
+ // `--force` does NOT cover it (that flag only covers gate ORDER). A
186
+ // caller who meant to re-approve therefore reads success and is left
187
+ // with nowhere to go. The way out already exists as `unapprove`; the
188
+ // message just never named it. Naming it is the whole fix (no new flag).
189
+ console.log(chalk.gray(`To approve it again, revoke it first: morph-spec unapprove ${featureName} ${gateName}\n`));
184
190
  return;
185
191
  }
186
192
 
@@ -349,6 +355,32 @@ export async function approveCommand(featureName, gateName, options = {}) {
349
355
  }
350
356
  selfAssessed = true;
351
357
  }
358
+
359
+ // REAVALIAÇÃO VISÍVEL. O `evaluator-report.md` é append-only, então uma
360
+ // segunda rodada acrescenta um `**Composite-Score:**` ao fim e o arquivo
361
+ // passa a carregar mais de um veredito. O gate decide pelo ÚLTIMO (o
362
+ // vigente) — mas aprovar em silêncio um relatório com notas superadas
363
+ // esconde do humano exatamente o fato que ele precisa ver: a nota MUDOU,
364
+ // e numa das rodadas medidas em campo ela CAIU (8.9 → 8.3, quando uma
365
+ // correção abriu um bloqueante).
366
+ //
367
+ // Aqui é o lugar: `approve` é a precondição humana do Gate 3 e a única
368
+ // superfície onde uma pessoa lê o veredito antes de carimbá-lo. Sem este
369
+ // bloco, `scoreTokens`/`superseded` seriam mitigação inerte — campo que
370
+ // ninguém lê não mitiga nada.
371
+ if (report.status === 'ok' && report.scoreTokens > 1) {
372
+ console.log(chalk.yellow(
373
+ `
374
+ ⚠️ O relatório do avaliador tem ${report.scoreTokens} vereditos (o arquivo é append-only).`
375
+ ));
376
+ console.log(chalk.gray(` Vigente (o que o gate usa): ${report.score}`));
377
+ console.log(chalk.gray(` Superado(s), na ordem do arquivo: ${report.superseded.join(', ')}`));
378
+ if (report.superseded.some((n) => n > report.score)) {
379
+ console.log(chalk.yellow(' A nota CAIU em relação a uma rodada anterior — confira o que a correção'));
380
+ console.log(chalk.yellow(' abriu antes de aprovar; o relatório mantém a análise das duas rodadas.'));
381
+ }
382
+ console.log('');
383
+ }
352
384
  }
353
385
  }
354
386
 
@@ -15,32 +15,61 @@ import { detectDefaultBranch } from '../../lib/git/default-branch.js';
15
15
  * `morph-spec finish` can later merge/PR it back. Symmetric with the worktree
16
16
  * flow, which gets its branch from `runWorktreeSetup`.
17
17
  *
18
+ * That checkout moves the dev's PRIMARY working tree, and `stdio: 'pipe'`
19
+ * swallows even git's own message — so the move is reported by the caller
20
+ * (`branchMoveWarning`), never silent. `checkout: false` (`--no-checkout`)
21
+ * still CREATES the branch — `finish --pr|--merge` depends on it existing in
22
+ * both flows — and only skips moving the tree.
23
+ *
18
24
  * Fail-open: outside a git repo, with no resolvable default, or on any git
19
25
  * error, the feature is simply left on the current branch — never a failed birth.
20
26
  *
21
27
  * @param {string} featureName
22
- * @returns {{ branched: boolean, branch?: string, from?: string, reason?: string }}
28
+ * @param {{ checkout?: boolean }} [opts] - `checkout: false` keeps the tree put
29
+ * @returns {{ branched: boolean, checkedOut: boolean, branch?: string, from?: string,
30
+ * movedFrom?: string, stayedOn?: string, reason?: string }}
31
+ * `movedFrom` is present ONLY when the working tree actually moved (it names
32
+ * the branch left behind); `stayedOn` only when the move was declined.
23
33
  */
24
- function branchCommonFlow(featureName) {
34
+ function branchCommonFlow(featureName, { checkout = true } = {}) {
25
35
  const cwd = process.cwd();
26
36
  try {
27
- if (!isGitRepo(cwd)) return { branched: false, reason: 'not-a-git-repo' };
37
+ if (!isGitRepo(cwd)) return { branched: false, checkedOut: false, reason: 'not-a-git-repo' };
28
38
  const branch = buildBranchName(featureName);
29
39
  const defaultBranch = detectDefaultBranch(cwd);
30
- if (!defaultBranch) return { branched: false, reason: 'no-default-branch' };
40
+ if (!defaultBranch) return { branched: false, checkedOut: false, reason: 'no-default-branch' };
31
41
  if (!branchExists(branch, cwd)) {
32
42
  execFileSync('git', ['branch', branch, defaultBranch], { cwd, stdio: 'pipe' });
33
43
  }
34
44
  const current = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd, stdio: 'pipe' }).toString().trim();
35
- if (current !== branch) {
36
- execFileSync('git', ['checkout', branch], { cwd, stdio: 'pipe' });
37
- }
38
- return { branched: true, branch, from: defaultBranch };
45
+ // Already there: nothing moved, so nothing to warn about.
46
+ if (current === branch) return { branched: true, checkedOut: true, branch, from: defaultBranch };
47
+ if (!checkout) return { branched: true, checkedOut: false, branch, from: defaultBranch, stayedOn: current };
48
+ execFileSync('git', ['checkout', branch], { cwd, stdio: 'pipe' });
49
+ return { branched: true, checkedOut: true, branch, from: defaultBranch, movedFrom: current };
39
50
  } catch {
40
- return { branched: false, reason: 'git-error' };
51
+ return { branched: false, checkedOut: false, reason: 'git-error' };
41
52
  }
42
53
  }
43
54
 
55
+ /**
56
+ * The alert for a checkout that MOVED. Built once and rendered on both
57
+ * surfaces — stdout (human) and stderr (under `--json`, so it survives a
58
+ * stdout redirect) — so the two can never drift apart.
59
+ *
60
+ * @param {{ branch: string, movedFrom: string }} branchResult
61
+ * @returns {string[]} lines, unstyled and unindented
62
+ */
63
+ function branchMoveWarning({ branch, movedFrom }) {
64
+ return [
65
+ `⚠️ Working tree MOVED: ${movedFrom} → ${branch}`,
66
+ ` This checkout is now ON the feature branch — uncommitted work came along,`,
67
+ ` and anything committed here from now on lands on ${branch}, not ${movedFrom}.`,
68
+ ` Use --no-checkout to create the branch without moving the tree, or --worktree`,
69
+ ` (the recommended route for parallel work) to isolate the feature in its own tree.`,
70
+ ];
71
+ }
72
+
44
73
  /** Feature names are kebab-case: lowercase alphanumerics and hyphens. */
45
74
  const FEATURE_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
46
75
 
@@ -130,6 +159,24 @@ export async function createCommand(featureName, options = {}) {
130
159
  process.exit(1);
131
160
  }
132
161
 
162
+ // `--no-checkout` pede que o checkout PRINCIPAL não se mova; `--worktree`
163
+ // nunca o move — a feature nasce numa árvore separada, e o caminho de
164
+ // worktree sequer chama `branchCommonFlow`, que é o único leitor do flag.
165
+ // Aceitar a combinação em silêncio é dizer que o CLI honrou um pedido que
166
+ // ele ignorou — o mesmo anti-padrão que esta série de fixes tirou do
167
+ // `finish --pr --push`. Recusa ALTA, e AQUI: junto das outras validações
168
+ // de forma, ANTES de `createFeature` — a primeira mutação do `create`.
169
+ // Uma recusa depois dela deixaria a feature nascida por um comando que
170
+ // falhou, pior que o no-op que ela substitui.
171
+ if (options.worktree && options.checkout === false) {
172
+ console.error(chalk.red(`\n❌ no-checkout-with-worktree: --no-checkout não se combina com --worktree`));
173
+ console.error(chalk.gray(` O --worktree já não move o checkout desta árvore: a feature nasce em worktrees/${featureName},`));
174
+ console.error(chalk.gray(` na branch morph/${featureName}, e o caminho de worktree nem chega a ler o --no-checkout.`));
175
+ console.error(chalk.gray(` Para isolar a feature na própria árvore: morph-spec create ${featureName} --worktree`));
176
+ console.error(chalk.gray(` Para criar só a branch morph/${featureName} sem mover esta árvore: morph-spec create ${featureName} --no-checkout\n`));
177
+ process.exit(1);
178
+ }
179
+
133
180
  const now = new Date().toISOString();
134
181
  const { workType, workTypeDetection } = resolveWorkType(options, now);
135
182
 
@@ -225,7 +272,8 @@ export async function createCommand(featureName, options = {}) {
225
272
  } else {
226
273
  // Common flow: still isolate the feature on its own branch so `finish`
227
274
  // can merge/PR it back. Fail-open (see branchCommonFlow).
228
- branchResult = branchCommonFlow(featureName);
275
+ // Commander's `--no-checkout` sets options.checkout === false.
276
+ branchResult = branchCommonFlow(featureName, { checkout: options.checkout !== false });
229
277
  }
230
278
  }
231
279
 
@@ -238,6 +286,11 @@ export async function createCommand(featureName, options = {}) {
238
286
  const out = { feature: featureName, created, ...finalFeature };
239
287
  if (worktreeResult) out.worktree = worktreeResult;
240
288
  if (branchResult) out.branch = branchResult;
289
+ // The move of the PRIMARY checkout is never silent — under --json it goes
290
+ // to stderr so it survives `... --json > out.json` instead of vanishing.
291
+ if (branchResult?.movedFrom) {
292
+ console.error(chalk.yellow(branchMoveWarning(branchResult).join('\n')));
293
+ }
241
294
  console.log(JSON.stringify(out, null, 2));
242
295
  return;
243
296
  }
@@ -262,7 +315,15 @@ export async function createCommand(featureName, options = {}) {
262
315
  }
263
316
  } else if (branchResult?.branched) {
264
317
  console.log(chalk.green(` branch: ${branchResult.branch} (from ${branchResult.from})`));
265
- console.log(chalk.gray(` close it with: morph-spec finish ${featureName} --pr | --merge\n`));
318
+ console.log(chalk.gray(` close it with: morph-spec finish ${featureName} --pr | --merge`));
319
+ if (branchResult.movedFrom) {
320
+ // Alert, not one more success line: the tree the dev is standing in moved.
321
+ console.log('');
322
+ for (const line of branchMoveWarning(branchResult)) console.log(chalk.yellow(` ${line}`));
323
+ } else if (branchResult.checkedOut === false) {
324
+ console.log(chalk.gray(` working tree left on ${branchResult.stayedOn} (--no-checkout)`));
325
+ }
326
+ console.log('');
266
327
  }
267
328
  } else {
268
329
  console.log(chalk.yellow(`\n⚠️ Feature "${featureName}" already exists — left untouched`));