@polymorphism-tech/morph-spec 8.34.2 → 8.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/CLAUDE.md +20 -3
  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 +234 -9
  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/statusline.sh +11 -11
  26. package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
  27. package/framework/hooks/git/pre-commit/agents.sh +25 -25
  28. package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
  29. package/framework/hooks/git/pre-commit/specs.sh +50 -50
  30. package/framework/hooks/git/pre-push/run-tests.sh +44 -44
  31. package/framework/hooks/shared/e2e-check.js +135 -22
  32. package/framework/hooks/shared/stdin-reader.js +26 -26
  33. package/framework/rules/ai-agents-standards.md +14 -2
  34. package/framework/schemas/ai-pin.schema.json +97 -0
  35. package/framework/schemas/config.schema.json +107 -0
  36. package/framework/schemas/feature.schema.json +23 -4
  37. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  38. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  39. package/framework/skills/core/morph-apply/SKILL.md +26 -5
  40. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  41. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  42. package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
  43. package/framework/skills/core/morph-implement/SKILL.md +18 -2
  44. package/framework/skills/core/morph-media/SKILL.md +215 -0
  45. package/framework/skills/core/morph-plan/SKILL.md +33 -2
  46. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  47. package/framework/skills/core/morph-review/SKILL.md +166 -9
  48. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  49. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  50. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  51. package/framework/standards/PROVENANCE.json +81 -3
  52. package/framework/standards/STANDARDS.json +1483 -839
  53. package/framework/standards/ai-agents/agent-archetypes.md +160 -0
  54. package/framework/standards/ai-agents/agent-session.md +164 -68
  55. package/framework/standards/ai-agents/agent-spec.md +196 -0
  56. package/framework/standards/ai-agents/batching.md +61 -37
  57. package/framework/standards/ai-agents/context-providers.md +97 -24
  58. package/framework/standards/ai-agents/conversational-agent-with-phases.md +239 -0
  59. package/framework/standards/ai-agents/cost-and-budget.md +256 -0
  60. package/framework/standards/ai-agents/durable-workflows-hitl.md +98 -5
  61. package/framework/standards/ai-agents/evals-with-cache.md +289 -0
  62. package/framework/standards/ai-agents/guardrails.md +222 -0
  63. package/framework/standards/ai-agents/llm-runtime-defaults.md +226 -0
  64. package/framework/standards/ai-agents/mcp-server.md +119 -18
  65. package/framework/standards/ai-agents/mcp-tools.md +85 -32
  66. package/framework/standards/ai-agents/media-pipeline.md +352 -0
  67. package/framework/standards/ai-agents/media-video.md +267 -0
  68. package/framework/standards/ai-agents/middleware-patterns.md +39 -5
  69. package/framework/standards/ai-agents/modalities-image-gen.md +354 -105
  70. package/framework/standards/ai-agents/modalities-speech-to-text.md +8 -2
  71. package/framework/standards/ai-agents/modalities-text-to-speech.md +10 -2
  72. package/framework/standards/ai-agents/modalities-vision.md +6 -2
  73. package/framework/standards/ai-agents/multi-agent-patterns.md +61 -5
  74. package/framework/standards/ai-agents/observability-patterns.md +227 -17
  75. package/framework/standards/ai-agents/production.md +38 -9
  76. package/framework/standards/ai-agents/prompt-sources.md +389 -0
  77. package/framework/standards/ai-agents/providers/model-registry.md +174 -39
  78. package/framework/standards/ai-agents/rag-custom-pgvector.md +69 -4
  79. package/framework/standards/ai-agents/service-tier-flex.md +27 -19
  80. package/framework/standards/ai-agents/setup.md +166 -50
  81. package/framework/standards/ai-agents/structured-output.md +34 -3
  82. package/framework/standards/ai-agents/sweet-spot.md +23 -3
  83. package/framework/standards/ai-agents/testing-ai.md +236 -0
  84. package/framework/standards/ai-agents/vector-data-extensions.md +50 -20
  85. package/framework/standards/ai-agents/workflows.md +27 -7
  86. package/framework/standards/frontend/design-system/ai-image-generation.md +12 -0
  87. package/framework/standards/frontend/design-system/ai-video-generation.md +13 -0
  88. package/framework/standards/infrastructure/docker/local-compose-isolation.md +3 -1
  89. package/framework/templates/.idea/morph-templates.xml +92 -0
  90. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  91. package/framework/templates/IDE-SNIPPETS.md +262 -262
  92. package/framework/templates/REGISTRY.json +13 -0
  93. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  94. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
  95. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
  96. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
  97. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +1 -1
  98. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
  99. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
  100. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
  101. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
  102. package/framework/templates/code/dotnet/database/migration.cs +83 -83
  103. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  104. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  105. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  106. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
  107. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +1 -1
  108. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  109. package/framework/templates/code/typescript/contracts.ts +167 -167
  110. package/framework/templates/docs/clarifications.md +253 -253
  111. package/framework/templates/docs/schema-analysis.md +119 -119
  112. package/framework/templates/docs/ui-components.md +52 -52
  113. package/framework/templates/docs/ui-design-system.md +76 -76
  114. package/framework/templates/docs/ui-flows.md +167 -167
  115. package/framework/templates/docs/ui-mockups.md +98 -98
  116. package/framework/templates/dotnet/ai-kit/Directory.Build.props +25 -0
  117. package/framework/templates/dotnet/ai-kit/Directory.Packages.props +26 -0
  118. package/framework/templates/dotnet/ai-kit/Morph.AiKit.sln +56 -0
  119. package/framework/templates/dotnet/ai-kit/NuGet.config +12 -0
  120. package/framework/templates/dotnet/ai-kit/README.md +128 -0
  121. package/framework/templates/dotnet/ai-kit/global.json +9 -0
  122. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +65 -0
  123. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +289 -0
  124. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +91 -0
  125. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +91 -0
  126. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +181 -0
  127. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +14 -0
  128. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +174 -0
  129. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +54 -0
  130. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +75 -0
  131. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +71 -0
  132. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +246 -0
  133. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +57 -0
  134. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +78 -0
  135. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +225 -0
  136. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +463 -0
  137. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +32 -0
  138. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +49 -0
  139. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +214 -0
  140. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +168 -0
  141. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +142 -0
  142. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +349 -0
  143. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +42 -0
  144. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +77 -0
  145. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +28 -0
  146. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +107 -0
  147. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +253 -0
  148. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +103 -0
  149. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +259 -0
  150. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +21 -0
  151. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +47 -0
  152. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +102 -0
  153. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +44 -0
  154. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +192 -0
  155. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +94 -0
  156. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +99 -0
  157. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +97 -0
  158. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +133 -0
  159. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +93 -0
  160. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +112 -0
  161. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +288 -0
  162. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +158 -0
  163. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +29 -0
  164. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +208 -0
  165. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +95 -0
  166. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Morph.AiKit.csproj +38 -0
  167. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +119 -0
  168. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +82 -0
  169. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +33 -0
  170. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +267 -0
  171. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +474 -0
  172. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/model-registry.v2.json +87 -0
  173. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +169 -0
  174. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +49 -0
  175. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +495 -0
  176. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +260 -0
  177. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +405 -0
  178. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +379 -0
  179. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +197 -0
  180. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +139 -0
  181. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +157 -0
  182. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +178 -0
  183. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +153 -0
  184. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +194 -0
  185. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +86 -0
  186. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +213 -0
  187. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +95 -0
  188. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +225 -0
  189. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +215 -0
  190. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +33 -0
  191. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +89 -0
  192. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +55 -0
  193. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +60 -0
  194. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +131 -0
  195. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +133 -0
  196. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +127 -0
  197. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +216 -0
  198. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +264 -0
  199. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +148 -0
  200. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +184 -0
  201. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +89 -0
  202. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +98 -0
  203. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +451 -0
  204. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +76 -0
  205. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +33 -0
  206. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +261 -0
  207. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +374 -0
  208. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/.morph-eval-cache.json +12 -0
  209. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/contents.data +13 -0
  210. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/entry.json +7 -0
  211. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/contents.data +13 -0
  212. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/entry.json +7 -0
  213. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/contents.data +13 -0
  214. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/entry.json +7 -0
  215. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/contents.data +13 -0
  216. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/entry.json +7 -0
  217. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/orcamento/acme/a-resposta-estruturada.json +24 -0
  218. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/a-saudacao.json +23 -0
  219. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/b-escalonamento.json +24 -0
  220. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/globex/a-saudacao.json +23 -0
  221. package/framework/templates/feature/decisions.md +122 -122
  222. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
  223. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  224. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
  225. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  226. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
  227. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  228. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
  229. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  230. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  231. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  232. package/framework/templates/infrastructure/github/README.md +56 -56
  233. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  234. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  235. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  236. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  237. package/framework/templates/integrations/asaas-client.cs +387 -387
  238. package/framework/templates/integrations/asaas-webhook.cs +351 -351
  239. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  240. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  241. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  242. package/framework/templates/project/validate.js +120 -120
  243. package/framework/templates/state.template.json +17 -17
  244. package/framework/templates/ui/design-system.css +226 -226
  245. package/package.json +3 -3
  246. package/src/commands/dashboard/dashboard.js +53 -53
  247. package/src/commands/e2e/e2e.js +15 -2
  248. package/src/commands/index.js +13 -13
  249. package/src/commands/project/checkpoint.js +209 -209
  250. package/src/commands/project/doctor.js +140 -5
  251. package/src/commands/project/update.js +469 -153
  252. package/src/commands/project/worktree.js +8 -1
  253. package/src/commands/state/advance.js +24 -2
  254. package/src/commands/state/approve.js +33 -1
  255. package/src/commands/state/create.js +72 -11
  256. package/src/commands/state/finish.js +333 -21
  257. package/src/commands/state/gate-check.js +190 -6
  258. package/src/commands/state/score.js +36 -1
  259. package/src/commands/validation/verify.js +134 -25
  260. package/src/core/state/phase-state-machine.js +197 -197
  261. package/src/core/state/state-manager.js +9 -1
  262. package/src/core/templates/template-renderer.js +477 -477
  263. package/src/dashboard/app/components/DetailPanel.jsx +11 -11
  264. package/src/dashboard/app/components/FlowGraph.jsx +17 -17
  265. package/src/dashboard/app/components/SearchFilter.jsx +22 -22
  266. package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
  267. package/src/dashboard/app/main.jsx +13 -13
  268. package/src/dashboard/app/pages/AgentMap.jsx +370 -370
  269. package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
  270. package/src/dashboard/app/pages/RulesMap.jsx +273 -273
  271. package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
  272. package/src/dashboard/app/pages/Standards.jsx +332 -332
  273. package/src/dashboard/app/styles/index.css +47 -47
  274. package/src/dashboard/index.html +12 -12
  275. package/src/dashboard/package.json +24 -24
  276. package/src/dashboard/scanner/phases-scanner.js +15 -15
  277. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  278. package/src/dashboard/scanner/rules-scanner.js +37 -37
  279. package/src/dashboard/scanner/skills-scanner.js +43 -43
  280. package/src/dashboard/scanner/squads-scanner.js +13 -13
  281. package/src/dashboard/vite-plugin-morph-data.js +17 -17
  282. package/src/dashboard/vite.config.js +24 -24
  283. package/src/lib/ai/ai-checks.js +182 -0
  284. package/src/lib/ai/ai-project-scan.js +347 -0
  285. package/src/lib/ai/code-audit.js +181 -0
  286. package/src/lib/ai/comment-stripper.js +184 -0
  287. package/src/lib/ai/pin-drift.js +138 -0
  288. package/src/lib/ai/registry-audit.js +285 -0
  289. package/src/lib/detectors/design-system-detector.js +188 -188
  290. package/src/lib/detectors/index.js +217 -217
  291. package/src/lib/e2e/compose-env.js +50 -4
  292. package/src/lib/evaluator-report.js +269 -15
  293. package/src/lib/generators/design-system-generator.js +508 -508
  294. package/src/lib/generators/settings-generator.js +209 -209
  295. package/src/lib/git/default-branch.js +92 -4
  296. package/src/lib/hooks/hook-executor.js +167 -167
  297. package/src/lib/installers/effects.js +193 -0
  298. package/src/lib/installers/mcp-installer.js +719 -719
  299. package/src/lib/installers/templates-reconcile.js +3 -3
  300. package/src/lib/risk-detector.js +284 -0
  301. package/src/lib/standards/verified-against.js +276 -0
  302. package/src/lib/tasks/eval-node.js +485 -0
  303. package/src/lib/tasks/linux-runner.js +297 -0
  304. package/src/lib/tasks/task-classifier.js +30 -30
  305. package/src/lib/tasks/task-parser.js +209 -209
  306. package/src/lib/tasks/test-nodes.js +486 -0
  307. package/src/lib/tasks/test-runner.js +410 -33
  308. package/src/lib/validators/checkpoint-persistence.js +39 -39
  309. package/src/lib/validators/css/css-validator.js +340 -340
  310. package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
  311. package/src/lib/validators/shared/issue-counter.js +14 -14
  312. package/src/lib/validators/verify-runner.js +583 -28
  313. package/src/lib/worktree/docker-audit.js +287 -0
  314. package/src/lib/worktree/env-allocator.js +24 -0
  315. package/src/lib/worktree/worktree-audit.js +30 -1
  316. package/src/scripts/setup-infra.js +24 -11
  317. package/src/utils/agent-id-mapper.js +11 -11
  318. package/src/utils/agents-installer.js +18 -8
  319. package/src/utils/claude-md-injector.js +10 -5
  320. package/src/utils/claude-settings-manager.js +300 -300
  321. package/src/utils/file-copier.js +11 -4
  322. package/src/utils/hooks-installer.js +29 -26
  323. package/src/utils/skills-installer.js +36 -29
  324. package/src/utils/version-checker.js +68 -3
  325. package/framework/standards/ai-agents/prompts-in-db.md +0 -214
@@ -11,11 +11,14 @@ import {
11
11
  buildBranchName,
12
12
  branchExists,
13
13
  cwdIsInsideWorktree,
14
+ classifyIgnoredLeftovers,
14
15
  } from '../project/worktree.js';
15
16
  import { detectDefaultBranch, hasOriginRemote } from '../../lib/git/default-branch.js';
17
+ import { commitsAhead, resolveBaseRef } from '../../lib/git/branch-classifier.js';
16
18
  import { resolveGh } from '../../lib/git/gh.js';
17
19
  import { releaseClaim } from '../../../framework/hooks/shared/claim-registry.js';
18
20
  import { tearDownFeatureStack } from '../../lib/e2e/stack-teardown.js';
21
+ import { findReparsePoints } from '../../../framework/hooks/shared/reparse-scan.js';
19
22
  import { runArchive } from './archive.js';
20
23
 
21
24
  /** workType → conventional-commit type. `hotfix` IS a fix; anything else is a chore. */
@@ -84,8 +87,14 @@ export function buildPrTitle(feature, fj) {
84
87
  * `resumed: true` in its output — see Validation 0c.
85
88
  *
86
89
  * @param {string} feature
87
- * @param {{ pr?: boolean, merge?: boolean, keep?: boolean, force?: boolean,
88
- * discardIgnored?: boolean, cwd?: string }} [options]
90
+ * `--dry-run` desvia para {@link finishPreflight} ANTES de qualquer validação
91
+ * que saia com exit 1 e ANTES de qualquer mutação: o pré-voo existe para
92
+ * EXPLICAR, e um pré-voo que recusa pelos mesmos motivos do comando real não
93
+ * explica nada.
94
+ *
95
+ * @param {{ pr?: boolean, merge?: boolean, keep?: boolean, keepVolumes?: boolean,
96
+ * push?: boolean, force?: boolean, discardIgnored?: boolean, dryRun?: boolean,
97
+ * cwd?: string }} [options]
89
98
  */
90
99
  export async function finishCommand(feature, options = {}) {
91
100
  const cwd = options.cwd || process.cwd();
@@ -136,6 +145,26 @@ export async function finishCommand(feature, options = {}) {
136
145
  process.exit(1);
137
146
  }
138
147
  const mode = wantPr ? 'pr' : 'merge';
148
+
149
+ // `--push` publica a DEFAULT depois de um merge local; o fluxo `--pr` não tem
150
+ // merge local nenhum (a default só recebe a feature quando o PR mergear), e
151
+ // já publica `morph/{f}` por conta própria. A combinação é um pedido que o
152
+ // comando não consegue honrar — e aceitar em silêncio o que não se pode
153
+ // honrar é exatamente o defeito que esta série de fixes existe para tirar do
154
+ // CLI (o `score` que gravava um zero fabricado, o `approve` que saía 0 sem
155
+ // aprovar nada). Recusa ALTA, e AQUI: junto do gate de modo, antes do
156
+ // archive — que é a primeira mutação do finish. Uma recusa depois dele
157
+ // deixaria a feature meio fechada, pior que o no-op que ela substitui.
158
+ if (wantPr && options.push) {
159
+ emit({
160
+ finished: false,
161
+ error: 'push-with-pr',
162
+ hint: `--push não se combina com --pr: o --pr já publica ${buildBranchName(feature)} e abre o PR, enquanto o --push publica a branch default depois de um merge LOCAL — e no fluxo --pr não há merge local. Para publicar a branch e abrir o PR: \`morph-spec finish ${feature} --pr\`. Para integrar na default localmente e publicá-la: \`morph-spec finish ${feature} --merge --push\`.`,
163
+ feature,
164
+ });
165
+ process.exit(1);
166
+ }
167
+
139
168
  const expectedBranch = buildBranchName(feature);
140
169
 
141
170
  // ── Resolve the flow ──────────────────────────────────────────────────────
@@ -149,6 +178,29 @@ export async function finishCommand(feature, options = {}) {
149
178
  let worktreePath = null;
150
179
  let featureCwd = root; // where the feature's own tree (and Gate 3) lives
151
180
 
181
+ // ── PRÉ-VOO ───────────────────────────────────────────────────────────────
182
+ // AQUI, e não mais abaixo: as validações seguintes saem com exit 1 (branch
183
+ // divergente, cwd dentro do worktree, feature ausente), e o pré-voo existe
184
+ // justamente para ser rodado ANTES de o estado estar bom. Ele reporta o que
185
+ // encontrar — inclusive o cwd dentro do worktree, como AVISO — e sai 0 sem
186
+ // ter tocado em nada.
187
+ if (options.dryRun) {
188
+ emit(finishPreflight({
189
+ feature,
190
+ root,
191
+ worktreePath: isWorktreeFlow ? found.path : null,
192
+ cwd,
193
+ mode,
194
+ branch: expectedBranch,
195
+ // `options` INTEIRO, e não um subconjunto escolhido a dedo: `--discard-ignored`
196
+ // INVERTE a classificação (o exclusivo deixa de ser copiado e passa a ser
197
+ // destruído), então um pré-voo que não recebe as flags do comando prevê
198
+ // outro comando. Ver B1 na errata E4.
199
+ options,
200
+ }));
201
+ return;
202
+ }
203
+
152
204
  if (isWorktreeFlow) {
153
205
  // Validation 0: the worktree must actually be ON the branch we will merge.
154
206
  if (found.branch !== found.expectedBranch) {
@@ -441,6 +493,166 @@ export async function finishCommand(feature, options = {}) {
441
493
  return integratePr({ emit, root, feature, expectedBranch, defaultBranch, isWorktreeFlow, worktreePath, options, prTitle, prBody, arch });
442
494
  }
443
495
 
496
+ /**
497
+ * O aviso que acompanha CADA reparse point listado pelo pré-voo.
498
+ *
499
+ * Exportado porque é a frase que carrega o fato: uma junction/symlink dentro do
500
+ * worktree aponta para conteúdo que vive na RAIZ, e `rm -rf` / `git worktree
501
+ * remove --force` atravessam o link. Já aconteceu: um `worktree remove --force`
502
+ * atravessou a junction e apagou o `node_modules` da raiz.
503
+ */
504
+ export const REPARSE_WARNING =
505
+ 'estes são junctions/symlinks: apagá-los fora do morph-spec apaga o conteúdo REAL na raiz';
506
+
507
+ /**
508
+ * `morph-spec finish <feature> --pr|--merge --dry-run` — o PRÉ-VOO.
509
+ *
510
+ * NÃO MUTA NADA. Nenhum commit, nenhum archive, nenhum worktree removido,
511
+ * nenhum `gh` que crie coisa alguma, nenhum `git fetch`. Ele responde, ANTES,
512
+ * as três perguntas que hoje só se respondem depois do estrago:
513
+ *
514
+ * 1. O que este `finish` vai COPIAR para o backup e o que vai DESCARTAR?
515
+ * (`classifyIgnoredLeftovers` — a MESMA função que o teardown consulta;
516
+ * um segundo enumerador aqui faria o pré-voo prometer o que o comando não
517
+ * cumpre, e um dry-run que mente é pior que nenhum.)
518
+ * 2. Que reparse points existem, e o que significa apagá-los por fora?
519
+ * (`findReparsePoints`, exaustivo e fail-closed.)
520
+ * 3. A base andou? — a pergunta INVERSA da de #89. Lá se mede "quantos
521
+ * commits EU tenho que o upstream não tem"; aqui, "quantos commits
522
+ * `origin/{default}` tem que a MINHA base não tem", que é o que decide se
523
+ * este merge vai ser trivial ou uma surpresa. Mesma função
524
+ * (`commitsAhead`), argumentos invertidos.
525
+ *
526
+ * `cwd` dentro do worktree é AVISO, não recusa: o caminho real continua
527
+ * recusando antes da primeira mutação (Validation 0b), e recusar aqui negaria
528
+ * ao usuário justamente a explicação que ele veio buscar — do lugar mais
529
+ * provável de estar quando a pede.
530
+ *
531
+ * SEM `git fetch` implícito: um fetch dentro de um comando que promete não
532
+ * mexer em nada é efeito colateral de rede. O pré-voo AVISA que o dado pode
533
+ * estar velho e sugere o fetch.
534
+ *
535
+ * @param {{ feature: string, root: string, worktreePath: string|null,
536
+ * cwd: string, mode: 'pr'|'merge', branch: string,
537
+ * options?: { discardIgnored?: boolean, keep?: boolean } }} args
538
+ * @returns {object} o payload emitido — nunca lança
539
+ */
540
+ export function finishPreflight({ feature, root, worktreePath, cwd, mode, branch, options = {} }) {
541
+ const warnings = [];
542
+
543
+ // ── A base andou? (vale COM e SEM worktree — UC-16 A1) ────────────────────
544
+ const defaultBranch = detectDefaultBranch(root);
545
+ const baseRef = defaultBranch ? resolveBaseRef(defaultBranch, root) : null;
546
+ // `commitsAhead(base, branch)` conta `base..branch`. Passando a branch da
547
+ // feature como BASE e o baseRef como BRANCH, contamos o que a default tem e a
548
+ // feature não — exatamente a pergunta inversa.
549
+ const commits = baseRef && branchExists(branch, root)
550
+ ? commitsAhead(branch, baseRef, root)
551
+ : null;
552
+
553
+ const out = {
554
+ preflight: true,
555
+ finished: false,
556
+ feature,
557
+ mode,
558
+ branch,
559
+ baseAdvanced: { ref: baseRef, commits },
560
+ // `null` é DESCONHECIDO e nunca é renderizado como 0 (UC-16 P6): "não deu
561
+ // para perguntar" e "a default não andou" são fatos opostos, e confundi-los
562
+ // é como um `--dry-run` passa confiança que não tem.
563
+ baseAdvancedNote: commits === null
564
+ ? (baseRef === null
565
+ ? 'sem contraparte remota nem branch default resolvível — a contagem é DESCONHECIDA, não zero'
566
+ : 'não foi possível comparar as refs — a contagem é DESCONHECIDA, não zero')
567
+ : `o dado é do último \`git fetch\`; rode \`git fetch ${PUSH_REMOTE}\` para atualizá-lo (o pré-voo não faz fetch: ele promete não mexer em nada)`,
568
+ warnings,
569
+ wouldDo: mode === 'merge'
570
+ ? [
571
+ `arquivar .morph/features/${feature} em .morph/archive/${feature} (commit na branch ${branch})`,
572
+ `fazer merge --no-ff de ${branch} em ${defaultBranch || '<default não resolvida>'} LOCALMENTE (sem push)`,
573
+ ...(worktreePath ? [`derrubar o stack e remover o worktree ${worktreePath.replace(/\\/g, '/')}${options.discardIgnored === true ? ' — SEM copiar os gitignored exclusivos para o backup (--discard-ignored)' : ''}`] : []),
574
+ ]
575
+ : [
576
+ `arquivar .morph/features/${feature} em .morph/archive/${feature} (commit na branch ${branch})`,
577
+ `publicar ${branch} em ${PUSH_REMOTE} e abrir um PR contra ${defaultBranch || '<default não resolvida>'} (NÃO integra: o merge do PR é que encerra)`,
578
+ ...(worktreePath ? [`derrubar o stack e remover o worktree ${worktreePath.replace(/\\/g, '/')}${options.discardIgnored === true ? ' — SEM copiar os gitignored exclusivos para o backup (--discard-ignored)' : ''}`] : []),
579
+ ],
580
+ };
581
+
582
+ if (defaultBranch) out.defaultBranch = defaultBranch;
583
+
584
+ // ── Seções de worktree — omitidas quando não há worktree (UC-16 A1) ───────
585
+ if (worktreePath) {
586
+ out.worktreePath = worktreePath.replace(/\\/g, '/');
587
+
588
+ const inside = cwdIsInsideWorktree(cwd, worktreePath);
589
+ out.cwdInsideWorktree = inside;
590
+ if (inside) {
591
+ warnings.push(
592
+ `você está DENTRO de ${out.worktreePath}: o finish de verdade recusa aqui, antes de qualquer mutação `
593
+ + `(o Windows tranca o diretório de trabalho do processo e a remoção falharia no meio). `
594
+ + `Rode o finish a partir de "${root.replace(/\\/g, '/')}"`,
595
+ );
596
+ }
597
+
598
+ // AS MESMAS FLAGS do comando real. `--discard-ignored` INVERTE a
599
+ // classificação — o exclusivo deixa de ser copiado e entra no lote
600
+ // destruído sem backup — então prever com `options` vazio é prever OUTRO
601
+ // comando. Medido antes do conserto: o pré-voo prometia salvar caminhos que
602
+ // o `--discard-ignored` destruía. Normalizado com `=== true` para que
603
+ // `undefined` e `false` sejam a mesma coisa, como no `worktree remove`.
604
+ const discardIgnored = options.discardIgnored === true;
605
+ const ignored = classifyIgnoredLeftovers(worktreePath, root, { discardIgnored });
606
+ out.discardIgnored = discardIgnored;
607
+ out.ignored = {
608
+ // COPIADO para .git/morph-spec/finish-backup antes da remoção: existe só
609
+ // aqui, ou difere do que está na raiz. Com `--discard-ignored` esta lista
610
+ // sai VAZIA por construção — a flag significa "pule o backup".
611
+ shieldable: ignored.shieldable,
612
+ // Idêntico byte a byte ao da raiz (SHA-256): apagar não perde nada.
613
+ replicable: ignored.replicable,
614
+ // Artefato regenerável + os replicáveis (+ o exclusivo, sob a flag):
615
+ // descartado sem backup.
616
+ discardable: ignored.discarded,
617
+ };
618
+ if (ignored.blocking.length) {
619
+ out.ignored.blocking = ignored.blocking;
620
+ warnings.push('não foi possível enumerar os gitignored do worktree — o finish real vai recusar a remoção');
621
+ }
622
+
623
+ if (discardIgnored) {
624
+ // SEGUNDA leitura, deliberada e só aqui: sob a flag, `shieldable` sai
625
+ // vazia e a informação mais valiosa do pré-voo — QUAIS destes existem só
626
+ // aqui — desapareceria justamente no modo destrutivo. Perguntar à MESMA
627
+ // função, sem a flag, é a única forma de nomeá-los sem recriar a regra
628
+ // (uma segunda cópia da triagem é o defeito que o export de
629
+ // `classifyIgnoredLeftovers` existe para evitar).
630
+ const semFlag = classifyIgnoredLeftovers(worktreePath, root);
631
+ out.ignored.discardableExclusive = semFlag.shieldable;
632
+ if (semFlag.shieldable.length) {
633
+ warnings.push(
634
+ `--discard-ignored: ${semFlag.shieldable.length} caminho(s) gitignored existem SÓ neste worktree e serão `
635
+ + `DESTRUÍDOS sem cópia (${semFlag.shieldable.slice(0, 5).join(', ')}). Sem a flag eles iriam para `
636
+ + '.git/morph-spec/finish-backup e a remoção aconteceria do mesmo jeito.',
637
+ );
638
+ }
639
+ }
640
+
641
+ const scan = findReparsePoints(worktreePath);
642
+ out.reparsePoints = scan.points;
643
+ if (scan.unprovable.length) out.reparseUnprovable = scan.unprovable;
644
+ out.reparseWarning = REPARSE_WARNING;
645
+ if (scan.points.length) {
646
+ warnings.push(
647
+ `${scan.points.length} reparse point(s) no worktree — ${REPARSE_WARNING}. `
648
+ + 'O `morph-spec worktree remove` derruba os links ANTES de apagar; um `git worktree remove --force` cru, não.',
649
+ );
650
+ }
651
+ }
652
+
653
+ return out;
654
+ }
655
+
444
656
  // ── merge integration (local, no push) ───────────────────────────────────────
445
657
 
446
658
  async function integrateMerge({ emit, root, feature, expectedBranch, defaultBranch, isWorktreeFlow, worktreePath, options, arch }) {
@@ -476,16 +688,15 @@ async function integrateMerge({ emit, root, feature, expectedBranch, defaultBran
476
688
  process.exit(1);
477
689
  }
478
690
 
691
+ // Uma medição só para as DUAS saídas deste merge (--keep e normal): é o
692
+ // mesmo merge, então a contagem e a publicação têm de ser as mesmas.
693
+ const publish = publicationState({ root, defaultBranch, push: options.push });
694
+
479
695
  if (options.keep) {
480
696
  dropFromRootIndex(root, feature);
481
- // Worktree preserved on purpose: the root's `worktree` registration stays
482
- // VALID (it still names a live directory), so nothing to clean up here —
483
- // only report where it lives and how to close it later.
484
697
  emit({
485
- finished: true, integrated: true, mode: 'merge', feature, merged: expectedBranch, kept: true, worktreeRemoved: false,
486
- worktree: { path: worktreePath, branch: expectedBranch },
487
- hint: `worktree preservado em ${worktreePath} — feche depois com \`morph-spec worktree remove ${feature}\`.`,
488
- ...backupNote, ...ucNote,
698
+ ...mergeOutput({ feature, branch: expectedBranch, defaultBranch, publish, worktreePath, shedBackup, kept: true }),
699
+ ...ucNote,
489
700
  });
490
701
  process.exit(0);
491
702
  }
@@ -496,7 +707,7 @@ async function integrateMerge({ emit, root, feature, expectedBranch, defaultBran
496
707
  const e2e = reclaimStack(feature, { root, worktreePath, options });
497
708
  const teardown = await teardownWorktree(worktreePath, { root, feature, branch: expectedBranch, options, deleteBranch: true });
498
709
  dropFromRootIndex(root, feature);
499
- emit({ ...mergeOutput({ feature, branch: expectedBranch, worktreePath, teardown, shedBackup }), ...ucNote, e2e });
710
+ emit({ ...mergeOutput({ feature, branch: expectedBranch, defaultBranch, publish, worktreePath, teardown, shedBackup }), ...ucNote, e2e });
500
711
  process.exit(0);
501
712
  }
502
713
 
@@ -524,9 +735,10 @@ async function integrateMerge({ emit, root, feature, expectedBranch, defaultBran
524
735
  process.exit(1);
525
736
  }
526
737
  tryGit(['branch', '-d', expectedBranch], root);
738
+ const publish = publicationState({ root, defaultBranch, push: options.push });
527
739
  const e2e = reclaimStack(feature, { root, worktreePath: null, options });
528
740
  // The root index (gitignored) was already updated by the archive (same cwd).
529
- emit({ finished: true, integrated: true, mode: 'merge', feature, merged: expectedBranch, into: defaultBranch, worktreeRemoved: false, nextStep: MERGE_NEXT_STEP, ...ucNote, e2e });
741
+ emit({ ...mergeOutput({ feature, branch: expectedBranch, defaultBranch, publish }), ...ucNote, e2e });
530
742
  process.exit(0);
531
743
  }
532
744
 
@@ -647,25 +859,125 @@ function promotedNote(arch) {
647
859
  }
648
860
 
649
861
  /**
650
- * O comando se chama `finish`, e no modo `--merge` ele NÃO publica nada: o
651
- * merge é local, o remoto continua sem a feature. Quem lê "finished: true"
652
- * conclui o contrário — foi pegadinha repetida em sessões diferentes. O modo
653
- * `--pr` empurra e abre o PR; o `--merge` deixa o push para o humano, e é isso
654
- * que esta linha diz em voz alta.
862
+ * O único remoto com que o `finish` conversa — o mesmo que `hasOriginRemote` e
863
+ * o `resolveBaseRef` do branch-classifier assumem. Um nome só, aqui, para que o
864
+ * comando que a saída IMPRIME e o push que o `--push` EXECUTA jamais possam
865
+ * nomear remotos diferentes.
866
+ */
867
+ const PUSH_REMOTE = 'origin';
868
+
869
+ /**
870
+ * O que sobrou por publicar depois que o merge local aterrissou — e, com
871
+ * `--push`, o resultado da publicação.
872
+ *
873
+ * A contagem REUSA `resolveBaseRef` + `commitsAhead` (branch-classifier), em vez
874
+ * de uma terceira cópia de `rev-list --count`. Detalhe que decide o P4:
875
+ * `resolveBaseRef` degrada para a branch LOCAL quando não existe ref de
876
+ * rastreamento remoto — comparar a branch consigo mesma devolveria um `0` de
877
+ * aparência honesta para um repo que nunca deu push. Por isso a comparação só
878
+ * vale quando o ref resolvido é diferente do nome da branch; caso contrário
879
+ * `ahead` fica `null` ("incomparável"), nunca zero.
880
+ *
881
+ * Fail-open por desenho: o merge JÁ aconteceu quando esta função roda, então
882
+ * nenhum resultado daqui — nem um push que falhou — pode transformar um sucesso
883
+ * em exit 1. A falha vira `pushError` no payload.
884
+ *
885
+ * @param {{ root: string, defaultBranch: string, push?: boolean }} ctx
886
+ * @returns {{ remote: string, branch: string, ahead: number|null, pushed: boolean,
887
+ * published?: number|null, pushError?: string }}
655
888
  */
656
- const MERGE_NEXT_STEP = 'integração LOCAL — nada foi publicado. Rode `git push` para levar ao remoto.';
889
+ function publicationState({ root, defaultBranch, push }) {
890
+ const baseRef = resolveBaseRef(defaultBranch, root);
891
+ const tracked = !!baseRef && baseRef !== defaultBranch;
892
+ const ahead = tracked ? commitsAhead(baseRef, defaultBranch, root) : null;
893
+ const state = { remote: PUSH_REMOTE, branch: defaultBranch, ahead, pushed: false };
894
+
895
+ if (!push) return state;
657
896
 
658
- function mergeOutput({ feature, branch, worktreePath, teardown, shedBackup }) {
897
+ if (!hasOriginRemote(root)) {
898
+ return { ...state, pushError: 'no-origin-remote' };
899
+ }
900
+ try {
901
+ execFileSync('git', ['push', PUSH_REMOTE, defaultBranch], { cwd: root, stdio: 'pipe' });
902
+ } catch (err) {
903
+ return { ...state, pushError: gitErr(err) };
904
+ }
905
+ // Re-medir DEPOIS do push: o que ficou para trás é a única prova de que a
906
+ // publicação foi completa, e um `ahead` congelado do antes seria mentira.
907
+ const after = resolveBaseRef(defaultBranch, root);
908
+ const remaining = after && after !== defaultBranch ? commitsAhead(after, defaultBranch, root) : null;
909
+ return { ...state, published: ahead, ahead: remaining, pushed: true };
910
+ }
911
+
912
+ /**
913
+ * A frase do próximo passo — PRODUTOR ÚNICO.
914
+ *
915
+ * O comando se chama `finish`, e no modo `--merge` ele NÃO publica nada por
916
+ * conta própria: o merge é local, o remoto continua sem a feature. Quem lê
917
+ * "finished: true" conclui o contrário — foi pegadinha repetida em sessões
918
+ * diferentes. Três saídas de `--merge` montavam esse aviso por conta própria, e
919
+ * uma delas (o `--keep`) simplesmente esquecia dele; um produtor único é o que
920
+ * impede a divergência de voltar.
921
+ *
922
+ * @param {ReturnType<typeof publicationState>} publish
923
+ * @returns {string}
924
+ */
925
+ function mergeNextStep(publish) {
926
+ const { remote, branch, ahead, pushed, published, pushError } = publish;
927
+ const pushCmd = `\`git push ${remote} ${branch}\``;
928
+
929
+ if (pushed) {
930
+ const n = typeof published === 'number' ? `${published} commit(s)` : 'a integração';
931
+ return `PUBLICADO — ${n} de ${branch} enviado(s) para ${remote}/${branch} pelo --push.`;
932
+ }
933
+ if (pushError === 'no-origin-remote') {
934
+ return `integração LOCAL — nada foi publicado: --push foi pedido mas não há remoto \`${remote}\` configurado.`;
935
+ }
936
+ if (pushError) {
937
+ return `integração LOCAL — o push pedido por --push FALHOU e nada foi publicado. Rode ${pushCmd} manualmente.`;
938
+ }
939
+ // Sem contraparte remota não se inventa contagem (P4): fica a frase estática.
940
+ if (ahead === null) {
941
+ return 'integração LOCAL — nada foi publicado. Rode `git push` para levar ao remoto.';
942
+ }
943
+ return `integração LOCAL — nada foi publicado: ${branch} está ${ahead} commit(s) à frente de ${remote}/${branch}. Rode ${pushCmd} para levar ao remoto (ou re-execute o finish com --push).`;
944
+ }
945
+
946
+ /**
947
+ * CONSTRUTOR ÚNICO da saída de sucesso do `--merge`. As três emissões (worktree
948
+ * normal, worktree `--keep` e fluxo comum) passam por aqui — era a terceira
949
+ * cópia, montada à mão no `--keep`, que saía sem `nextStep` nenhum.
950
+ *
951
+ * `teardown === null` cobre os dois casos sem desmonte: o `--keep` (worktree
952
+ * vivo de propósito) e o fluxo comum (não há worktree algum).
953
+ *
954
+ * @param {{ feature: string, branch: string, defaultBranch: string,
955
+ * publish: ReturnType<typeof publicationState>, worktreePath?: string|null,
956
+ * teardown?: object|null, shedBackup?: string|null, kept?: boolean }} args
957
+ */
958
+ function mergeOutput({ feature, branch, defaultBranch, publish, worktreePath = null, teardown = null, shedBackup = null, kept = false }) {
659
959
  return {
660
960
  finished: true,
661
961
  integrated: true,
662
962
  mode: 'merge',
663
963
  feature,
664
964
  merged: branch,
665
- worktreeRemoved: teardown.worktreeRemoved,
666
- nextStep: MERGE_NEXT_STEP,
965
+ into: defaultBranch,
966
+ ...(kept ? { kept: true } : {}),
967
+ worktreeRemoved: teardown ? teardown.worktreeRemoved : false,
968
+ nextStep: mergeNextStep(publish),
969
+ publish,
667
970
  ...(shedBackup ? { shedBackup } : {}),
668
- ...teardownExtras(teardown, worktreePath, branch, true),
971
+ ...(kept
972
+ ? {
973
+ // Worktree preserved on purpose: the root's `worktree` registration stays
974
+ // VALID (it still names a live directory), so nothing to clean up here —
975
+ // only report where it lives and how to close it later.
976
+ worktree: { path: worktreePath, branch },
977
+ hint: `worktree preservado em ${worktreePath} — feche depois com \`morph-spec worktree remove ${feature}\`.`,
978
+ }
979
+ : {}),
980
+ ...(teardown ? teardownExtras(teardown, worktreePath, branch, true) : {}),
669
981
  };
670
982
  }
671
983