@polymorphism-tech/morph-spec 8.33.0 → 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 (253) hide show
  1. package/CLAUDE.md +22 -2
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +43 -5
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +5 -4
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +2 -2
  8. package/framework/MORPH.md +20 -22
  9. package/framework/agents.json +198 -44
  10. package/framework/ai-pin.json +22 -0
  11. package/framework/commands/morph-media.md +45 -0
  12. package/framework/docs/CLI.md +238 -10
  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/README.md +11 -1
  25. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +5 -8
  26. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +79 -0
  27. package/framework/hooks/claude-code/core/chain.js +27 -7
  28. package/framework/hooks/claude-code/core/protect-spec-files.js +41 -4
  29. package/framework/hooks/claude-code/core/worktree-context.js +83 -14
  30. package/framework/hooks/claude-code/core/worktree-create.js +206 -0
  31. package/framework/hooks/claude-code/core/worktree-env-inject.js +87 -0
  32. package/framework/hooks/claude-code/core/worktree-remove.js +81 -0
  33. package/framework/hooks/shared/command-tokens.js +23 -0
  34. package/framework/hooks/shared/e2e-check.js +0 -0
  35. package/framework/hooks/shared/git-paths.js +25 -0
  36. package/framework/hooks/shared/hook-response.js +17 -0
  37. package/framework/hooks/shared/morph-cli.js +134 -0
  38. package/framework/rules/ai-agents-standards.md +14 -2
  39. package/framework/rules/infrastructure-standards.md +3 -1
  40. package/framework/schemas/ai-pin.schema.json +97 -0
  41. package/framework/schemas/config.schema.json +107 -0
  42. package/framework/schemas/feature.schema.json +23 -4
  43. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  44. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  45. package/framework/skills/core/morph-apply/SKILL.md +28 -7
  46. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  47. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  48. package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
  49. package/framework/skills/core/morph-implement/SKILL.md +20 -2
  50. package/framework/skills/core/morph-media/SKILL.md +215 -0
  51. package/framework/skills/core/morph-plan/SKILL.md +38 -4
  52. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  53. package/framework/skills/core/morph-review/SKILL.md +166 -9
  54. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  55. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  56. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  57. package/framework/standards/PROVENANCE.json +81 -3
  58. package/framework/standards/STANDARDS.json +1504 -830
  59. package/framework/standards/ai-agents/agent-archetypes.md +160 -0
  60. package/framework/standards/ai-agents/agent-session.md +256 -160
  61. package/framework/standards/ai-agents/agent-spec.md +196 -0
  62. package/framework/standards/ai-agents/batching.md +217 -193
  63. package/framework/standards/ai-agents/context-providers.md +261 -188
  64. package/framework/standards/ai-agents/conversational-agent-with-phases.md +239 -0
  65. package/framework/standards/ai-agents/cost-and-budget.md +256 -0
  66. package/framework/standards/ai-agents/durable-workflows-hitl.md +98 -5
  67. package/framework/standards/ai-agents/evals-with-cache.md +289 -0
  68. package/framework/standards/ai-agents/guardrails.md +222 -0
  69. package/framework/standards/ai-agents/llm-runtime-defaults.md +226 -0
  70. package/framework/standards/ai-agents/mcp-server.md +351 -250
  71. package/framework/standards/ai-agents/mcp-tools.md +250 -197
  72. package/framework/standards/ai-agents/media-pipeline.md +352 -0
  73. package/framework/standards/ai-agents/media-video.md +267 -0
  74. package/framework/standards/ai-agents/middleware-patterns.md +241 -207
  75. package/framework/standards/ai-agents/modalities-image-gen.md +422 -173
  76. package/framework/standards/ai-agents/modalities-speech-to-text.md +177 -171
  77. package/framework/standards/ai-agents/modalities-text-to-speech.md +189 -181
  78. package/framework/standards/ai-agents/modalities-vision.md +205 -201
  79. package/framework/standards/ai-agents/multi-agent-patterns.md +211 -155
  80. package/framework/standards/ai-agents/observability-patterns.md +227 -17
  81. package/framework/standards/ai-agents/production.md +260 -231
  82. package/framework/standards/ai-agents/prompt-sources.md +389 -0
  83. package/framework/standards/ai-agents/providers/model-registry.md +293 -158
  84. package/framework/standards/ai-agents/rag-custom-pgvector.md +69 -4
  85. package/framework/standards/ai-agents/service-tier-flex.md +143 -135
  86. package/framework/standards/ai-agents/setup.md +166 -50
  87. package/framework/standards/ai-agents/structured-output.md +280 -249
  88. package/framework/standards/ai-agents/sweet-spot.md +129 -109
  89. package/framework/standards/ai-agents/testing-ai.md +236 -0
  90. package/framework/standards/ai-agents/vector-data-extensions.md +50 -20
  91. package/framework/standards/ai-agents/workflows.md +27 -7
  92. package/framework/standards/frontend/design-system/ai-image-generation.md +12 -0
  93. package/framework/standards/frontend/design-system/ai-video-generation.md +13 -0
  94. package/framework/standards/infrastructure/docker/local-compose-isolation.md +90 -0
  95. package/framework/templates/REGISTRY.json +14 -1
  96. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
  97. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
  98. package/framework/templates/dotnet/ai-kit/Directory.Build.props +25 -0
  99. package/framework/templates/dotnet/ai-kit/Directory.Packages.props +26 -0
  100. package/framework/templates/dotnet/ai-kit/Morph.AiKit.sln +56 -0
  101. package/framework/templates/dotnet/ai-kit/NuGet.config +12 -0
  102. package/framework/templates/dotnet/ai-kit/README.md +128 -0
  103. package/framework/templates/dotnet/ai-kit/global.json +9 -0
  104. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +65 -0
  105. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +289 -0
  106. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +91 -0
  107. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +91 -0
  108. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +181 -0
  109. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +14 -0
  110. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +174 -0
  111. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +54 -0
  112. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +75 -0
  113. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +71 -0
  114. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +246 -0
  115. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +57 -0
  116. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +78 -0
  117. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +225 -0
  118. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +463 -0
  119. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +32 -0
  120. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +49 -0
  121. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +214 -0
  122. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +168 -0
  123. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +142 -0
  124. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +349 -0
  125. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +42 -0
  126. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +77 -0
  127. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +28 -0
  128. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +107 -0
  129. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +253 -0
  130. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +103 -0
  131. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +259 -0
  132. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +21 -0
  133. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +47 -0
  134. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +102 -0
  135. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +44 -0
  136. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +192 -0
  137. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +94 -0
  138. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +99 -0
  139. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +97 -0
  140. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +133 -0
  141. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +93 -0
  142. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +112 -0
  143. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +288 -0
  144. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +158 -0
  145. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +29 -0
  146. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +208 -0
  147. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +95 -0
  148. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Morph.AiKit.csproj +38 -0
  149. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +119 -0
  150. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +82 -0
  151. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +33 -0
  152. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +267 -0
  153. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +474 -0
  154. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/model-registry.v2.json +87 -0
  155. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +169 -0
  156. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +49 -0
  157. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +495 -0
  158. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +260 -0
  159. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +405 -0
  160. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +379 -0
  161. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +197 -0
  162. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +139 -0
  163. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +157 -0
  164. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +178 -0
  165. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +153 -0
  166. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +194 -0
  167. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +86 -0
  168. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +213 -0
  169. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +95 -0
  170. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +225 -0
  171. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +215 -0
  172. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +33 -0
  173. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +89 -0
  174. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +55 -0
  175. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +60 -0
  176. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +131 -0
  177. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +133 -0
  178. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +127 -0
  179. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +216 -0
  180. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +264 -0
  181. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +148 -0
  182. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +184 -0
  183. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +89 -0
  184. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +98 -0
  185. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +451 -0
  186. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +76 -0
  187. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +33 -0
  188. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +261 -0
  189. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +374 -0
  190. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/.morph-eval-cache.json +12 -0
  191. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/contents.data +13 -0
  192. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/entry.json +7 -0
  193. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/contents.data +13 -0
  194. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/entry.json +7 -0
  195. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/contents.data +13 -0
  196. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/entry.json +7 -0
  197. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/contents.data +13 -0
  198. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/entry.json +7 -0
  199. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/orcamento/acme/a-resposta-estruturada.json +24 -0
  200. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/a-saudacao.json +23 -0
  201. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/b-escalonamento.json +24 -0
  202. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/globex/a-saudacao.json +23 -0
  203. package/framework/templates/infrastructure/docker/docker-compose.template.yml +24 -14
  204. package/package.json +3 -3
  205. package/src/commands/e2e/e2e.js +91 -40
  206. package/src/commands/project/doctor.js +143 -6
  207. package/src/commands/project/env.js +95 -0
  208. package/src/commands/project/update.js +469 -153
  209. package/src/commands/project/worktree.js +274 -53
  210. package/src/commands/state/advance.js +24 -2
  211. package/src/commands/state/approve.js +33 -1
  212. package/src/commands/state/create.js +72 -11
  213. package/src/commands/state/delete.js +8 -0
  214. package/src/commands/state/finish.js +360 -53
  215. package/src/commands/state/gate-check.js +190 -6
  216. package/src/commands/state/score.js +36 -1
  217. package/src/commands/validation/verify.js +134 -25
  218. package/src/core/state/state-manager.js +9 -1
  219. package/src/dashboard/app/pages/HookInspector.jsx +430 -418
  220. package/src/dashboard/scanner/hooks-scanner.js +5 -1
  221. package/src/lib/ai/ai-checks.js +182 -0
  222. package/src/lib/ai/ai-project-scan.js +347 -0
  223. package/src/lib/ai/code-audit.js +181 -0
  224. package/src/lib/ai/comment-stripper.js +184 -0
  225. package/src/lib/ai/pin-drift.js +138 -0
  226. package/src/lib/ai/registry-audit.js +285 -0
  227. package/src/lib/e2e/compose-env.js +55 -14
  228. package/src/lib/e2e/stack-teardown.js +115 -0
  229. package/src/lib/evaluator-report.js +269 -15
  230. package/src/lib/generators/architecture-scanner.js +2 -1
  231. package/src/lib/git/default-branch.js +92 -4
  232. package/src/lib/graph/graphifyignore.js +2 -1
  233. package/src/lib/installers/effects.js +193 -0
  234. package/src/lib/installers/templates-reconcile.js +3 -3
  235. package/src/lib/risk-detector.js +284 -0
  236. package/src/lib/standards/verified-against.js +276 -0
  237. package/src/lib/tasks/eval-node.js +485 -0
  238. package/src/lib/tasks/linux-runner.js +297 -0
  239. package/src/lib/tasks/test-nodes.js +486 -0
  240. package/src/lib/tasks/test-runner.js +431 -37
  241. package/src/lib/validators/verify-runner.js +590 -28
  242. package/src/lib/worktree/dirs.js +32 -0
  243. package/src/lib/worktree/docker-audit.js +287 -0
  244. package/src/lib/worktree/env-allocator.js +69 -11
  245. package/src/lib/worktree/worktree-audit.js +70 -3
  246. package/src/scripts/setup-infra.js +24 -11
  247. package/src/utils/agents-installer.js +18 -8
  248. package/src/utils/claude-md-injector.js +10 -5
  249. package/src/utils/file-copier.js +28 -16
  250. package/src/utils/hooks-installer.js +75 -29
  251. package/src/utils/skills-installer.js +194 -187
  252. package/src/utils/version-checker.js +68 -3
  253. package/framework/standards/ai-agents/prompts-in-db.md +0 -214
@@ -21,9 +21,10 @@
21
21
  * Evaluator) are assumed by Claude itself per MORPH.md, not as separate agents.
22
22
  */
23
23
 
24
- import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, unlinkSync } from 'node:fs';
24
+ import { readFileSync, existsSync, readdirSync } from 'node:fs';
25
25
  import { join } from 'path';
26
26
  import { shouldInstall } from '../lib/stack-filter.js';
27
+ import { realEffects } from '../lib/installers/effects.js';
27
28
 
28
29
  /** Default tool scope for domain personas (override per agent via agents.json `tools`). */
29
30
  export const DEFAULT_AGENT_TOOLS = 'Read, Write, Edit, Bash, Glob, Grep';
@@ -79,10 +80,12 @@ const MANAGED_HEADER =
79
80
  * @param {string} frameworkDir - Path to morph-spec framework/ directory
80
81
  * @param {object} options
81
82
  * @param {string[]} [options.projectTags=[]] - Stack tags from config (e.g. ['dotnet', 'nextjs'])
83
+ * @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
84
+ * Coletor de efeitos; o default executa e registra numa lista descartada.
82
85
  * @returns {Promise<{ installed: number }>}
83
86
  */
84
87
  export async function installAgents(projectDir, frameworkDir = 'framework', options = {}) {
85
- const { projectTags = [] } = options;
88
+ const { projectTags = [], fx = realEffects() } = options;
86
89
 
87
90
  const agentsJsonPath = join(frameworkDir, 'agents.json');
88
91
  const agentsJson = JSON.parse(readFileSync(agentsJsonPath, 'utf-8'));
@@ -93,7 +96,7 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
93
96
  .map(([id, data]) => ({ id, ...data }));
94
97
 
95
98
  const targetDir = join(projectDir, '.claude', 'agents');
96
- mkdirSync(targetDir, { recursive: true });
99
+ fx.ensureDir(targetDir);
97
100
 
98
101
  const knownMcpIds = loadKnownMcpIds(frameworkDir);
99
102
 
@@ -111,7 +114,7 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
111
114
  const filename = `morph-${agent.id}.md`;
112
115
  const targetPath = join(targetDir, filename);
113
116
 
114
- writeFileSync(targetPath, renderAgentMd(agent, knownMcpIds), 'utf-8');
117
+ fx.writeFile(targetPath, renderAgentMd(agent, knownMcpIds));
115
118
  installedFilenames.add(filename);
116
119
  installedAgents.push({
117
120
  id: agent.id,
@@ -122,10 +125,17 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
122
125
  installed++;
123
126
  }
124
127
 
125
- // Cleanup orphan morph-* files filtered out by stack
126
- for (const file of readdirSync(targetDir)) {
127
- if (allPossible.has(file) && !installedFilenames.has(file)) {
128
- unlinkSync(join(targetDir, file));
128
+ // Cleanup orphan morph-* files filtered out by stack.
129
+ //
130
+ // Calado em `dryRun` pelo mesmo motivo do laço gêmeo em skills-installer.js
131
+ // (D6): numa execução real do `update` este diretório acabou de ser apagado
132
+ // inteiro por `cleanFrameworkDirs()`, então o laço nunca remove nada. Num
133
+ // dry ingênuo ele veria o diretório antigo e preveria remoções inexistentes.
134
+ if (!fx.dryRun) {
135
+ for (const file of readdirSync(targetDir)) {
136
+ if (allPossible.has(file) && !installedFilenames.has(file)) {
137
+ fx.remove(join(targetDir, file));
138
+ }
129
139
  }
130
140
  }
131
141
 
@@ -8,7 +8,8 @@
8
8
  * The root CLAUDE.md belongs to the USER — morph-spec only adds its import.
9
9
  */
10
10
 
11
- import { pathExists, readFile, writeFile } from './file-copier.js';
11
+ import { pathExists, readFile } from './file-copier.js';
12
+ import { realEffects } from '../lib/installers/effects.js';
12
13
 
13
14
  const MORPH_IMPORT = '@.claude/CLAUDE.md';
14
15
  const MORPH_MARKER = 'MORPH-SPEC';
@@ -24,11 +25,15 @@ const MORPH_MARKER = 'MORPH-SPEC';
24
25
  * Idempotent — does nothing if the import already exists.
25
26
  *
26
27
  * @param {string} claudeMdPath - Absolute path to root CLAUDE.md
28
+ * @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [fx]
29
+ * Coletor de efeitos; o default executa e registra numa lista descartada.
30
+ * A DECISÃO (criar / migrar / anexar / nada) sai de uma LEITURA, que
31
+ * acontece igual nos dois modos — por isso previsão e execução concordam.
27
32
  * @returns {Promise<'created'|'migrated'|'injected'|'already-present'>}
28
33
  */
29
- export async function injectMorphImport(claudeMdPath) {
34
+ export async function injectMorphImport(claudeMdPath, fx = realEffects()) {
30
35
  if (!await pathExists(claudeMdPath)) {
31
- await writeFile(claudeMdPath, `# Project\n\n${MORPH_IMPORT}\n`);
36
+ fx.writeFile(claudeMdPath, `# Project\n\n${MORPH_IMPORT}\n`);
32
37
  return 'created';
33
38
  }
34
39
 
@@ -39,13 +44,13 @@ export async function injectMorphImport(claudeMdPath) {
39
44
  }
40
45
 
41
46
  if (isMorphOnly(content)) {
42
- await writeFile(claudeMdPath, `# Project\n\n${MORPH_IMPORT}\n`);
47
+ fx.writeFile(claudeMdPath, `# Project\n\n${MORPH_IMPORT}\n`);
43
48
  return 'migrated';
44
49
  }
45
50
 
46
51
  // User has their own content — append the import section
47
52
  const separator = content.endsWith('\n') ? '\n' : '\n\n';
48
- await writeFile(claudeMdPath, `${content}${separator}## MORPH-SPEC\n\n${MORPH_IMPORT}\n`);
53
+ fx.writeFile(claudeMdPath, `${content}${separator}## MORPH-SPEC\n\n${MORPH_IMPORT}\n`);
49
54
  return 'injected';
50
55
  }
51
56
 
@@ -1,6 +1,8 @@
1
1
  import fs from 'fs-extra';
2
2
  import { join, dirname, resolve } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
+ import { WORKTREE_DIR_NAMES } from '../lib/worktree/dirs.js';
5
+ import { realEffects } from '../lib/installers/effects.js';
4
6
 
5
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
6
8
  const frameworkRoot = join(__dirname, '..', '..');
@@ -137,12 +139,12 @@ export async function createDirectoryLink(target, link) {
137
139
  }
138
140
  }
139
141
 
140
- export async function updateGitignore(projectPath) {
141
- const gitignorePath = join(projectPath, '.gitignore');
142
-
143
- const morphRules = [
144
- '',
145
- '# MORPH-SPEC',
142
+ /**
143
+ * The morph-owned block of the installed .gitignore. Exported so the worktree
144
+ * container canary (test/lib/worktree/dirs.test.js) can prove both names are
145
+ * covered without rendering a file.
146
+ */
147
+ export const MORPH_GITIGNORE_ENTRIES = [
146
148
  // Derivable, per-machine state index — rebuilt from the committed
147
149
  // .morph/features/{feature}/feature.json files. Never commit it: doing so
148
150
  // sweeps every feature's accumulated diffs into one commit. The feature
@@ -165,19 +167,29 @@ export async function updateGitignore(projectPath) {
165
167
  '.claude/agents/',
166
168
  '.claude/CLAUDE.md',
167
169
  '.claude/settings.local.json',
168
- '/worktrees/',
169
- // Legacy name (pre-C1). Kept so a project that already has `.worktrees/`
170
- // on disk does not suddenly see it as untracked dirt — which would also
171
- // make `finish`'s clean-root check refuse.
172
- '/.worktrees/',
170
+ // Both container names, anchored at the root. The legacy name (pre-C1) is
171
+ // kept so a project that already has `.worktrees/` on disk does not
172
+ // suddenly see it as untracked dirt — which would also make `finish`'s
173
+ // clean-root check refuse.
174
+ ...WORKTREE_DIR_NAMES.map((d) => `/${d}/`),
173
175
  '.playwright-mcp/',
174
176
  'docs/superpowers/',
175
177
  // Grafo de codebase — artefato derivado, reconstruído pelo hook do
176
178
  // graphify a cada commit. Committá-lo faria de todo commit um diff de
177
179
  // megabytes de JSON gerado.
178
180
  'graphify-out/',
179
- ''
180
- ];
181
+ ];
182
+
183
+ /**
184
+ * @param {string} projectPath
185
+ * @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [fx]
186
+ * Coletor de efeitos. O default executa e registra numa lista descartada, de
187
+ * modo que todo chamador antigo (`setup-infra`, `init`) siga idêntico.
188
+ */
189
+ export async function updateGitignore(projectPath, fx = realEffects()) {
190
+ const gitignorePath = join(projectPath, '.gitignore');
191
+
192
+ const morphRules = ['', '# MORPH-SPEC', ...MORPH_GITIGNORE_ENTRIES, ''];
181
193
 
182
194
  let content = '';
183
195
  let hasMorphSection = false;
@@ -223,7 +235,7 @@ export async function updateGitignore(projectPath) {
223
235
  content += morphRules.join('\n');
224
236
  }
225
237
 
226
- await writeFile(gitignorePath, content);
238
+ fx.writeFile(gitignorePath, content);
227
239
  }
228
240
 
229
241
  /**
@@ -243,7 +255,7 @@ export async function updateGitignore(projectPath) {
243
255
  *
244
256
  * @param {string} projectPath
245
257
  */
246
- export async function ensureGitattributes(projectPath) {
258
+ export async function ensureGitattributes(projectPath, fx = realEffects()) {
247
259
  const gitattributesPath = join(projectPath, '.gitattributes');
248
260
  const MARKER = '# MORPH-SPEC';
249
261
  const morphBlock = [
@@ -262,7 +274,7 @@ export async function ensureGitattributes(projectPath) {
262
274
  content += '\n';
263
275
  }
264
276
  content += `${morphBlock.join('\n')}\n`;
265
- await writeFile(gitattributesPath, content);
277
+ fx.writeFile(gitattributesPath, content);
266
278
  }
267
279
 
268
280
  /**
@@ -9,10 +9,11 @@
9
9
  */
10
10
 
11
11
  import { join, dirname } from 'path';
12
- import { readFile, writeFile, mkdir, copyFile } from 'fs/promises';
12
+ import { readFile } from 'fs/promises';
13
13
  import { existsSync, chmodSync, readdirSync } from 'fs';
14
14
  import { homedir } from 'os';
15
15
  import { execSync } from 'child_process';
16
+ import { realEffects } from '../lib/installers/effects.js';
16
17
 
17
18
  /**
18
19
  * Current hooks schema version — bump when hook definitions change.
@@ -27,8 +28,15 @@ import { execSync } from 'child_process';
27
28
  * (`copyDirectory` defaults to `overwrite: true`), so the bump does not gate
28
29
  * delivery — it makes `morph-spec update` report the change honestly instead
29
30
  * of "already current".
31
+ *
32
+ * 3.8.0 (worktree governance) registers Claude Code's WorktreeCreate /
33
+ * WorktreeRemove events — the native `claude -w` / EnterWorktree / Agent
34
+ * isolation worktree is replaced by `morph-spec worktree setup|provision`
35
+ * (junctions, port block, primary-root landing) and its removal routed
36
+ * through the safe `worktree remove --if-clean` — and appends
37
+ * block-raw-worktree-add to the PreToolUse:Bash chain.
30
38
  */
31
- const HOOKS_VERSION = '3.7.0';
39
+ const HOOKS_VERSION = '3.8.0';
32
40
 
33
41
  /** Marker for old dispatch.js (v1) */
34
42
  const OLD_DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
@@ -137,14 +145,50 @@ const MORPH_HOOKS = [
137
145
  // block-destructive-worktree-removal (dynamic: scans the target for reparse
138
146
  // points), so the cheap check short-circuits the filesystem walk. The second
139
147
  // one is the only defense against the raw-removal vector that does not depend
140
- // on someone having read MORPH.md. ===
148
+ // on someone having read MORPH.md. block-raw-worktree-add is its way-IN
149
+ // counterpart (a raw `git worktree add` builds a tree with no junctions, no
150
+ // ports, no identity) — static, so it costs nothing after the first two.
151
+ // worktree-env-inject is LAST: it rewrites launcher commands (updatedInput)
152
+ // with the worktree's port block, and a command a previous link blocked must
153
+ // never be rewritten. Block-first-wins guarantees it never sees one.
141
154
  {
142
155
  event: 'PreToolUse',
143
156
  matcher: 'Bash',
144
157
  hooks: [{
145
158
  type: 'command',
146
159
  command: 'node framework/hooks/claude-code/core/chain.js',
147
- args: ['pre-bash-denylist', 'block-destructive-worktree-removal']
160
+ args: ['pre-bash-denylist', 'block-destructive-worktree-removal', 'block-raw-worktree-add', 'worktree-env-inject']
161
+ }]
162
+ },
163
+
164
+ // === WorktreeCreate — REPLACES Claude Code's native `git worktree add` for
165
+ // `claude -w`, the EnterWorktree tool and `Agent isolation:"worktree"`
166
+ // (measured: all three fire it). The hook prints the created path as its
167
+ // last stdout line; a non-zero exit fails the creation. No matcher exists
168
+ // for this event. 120s: it spawns the morph-spec CLI (setup or provision),
169
+ // which runs git, links infra, allocates ports and materializes state. ===
170
+ {
171
+ event: 'WorktreeCreate',
172
+ matcher: null,
173
+ hooks: [{
174
+ type: 'command',
175
+ command: 'node framework/hooks/claude-code/core/worktree-create.js',
176
+ timeout: 120000
177
+ }]
178
+ },
179
+
180
+ // === WorktreeRemove — routes Claude Code's own cleanup through
181
+ // `morph-spec worktree remove --target --if-clean` (junctions dropped BEFORE
182
+ // git touches the tree; a dirty tree is left alone). Output ignored, always
183
+ // exit 0. Secondary net: in headless runs the event was observed not to
184
+ // fire — block-destructive-worktree-removal remains the primary defense. ===
185
+ {
186
+ event: 'WorktreeRemove',
187
+ matcher: null,
188
+ hooks: [{
189
+ type: 'command',
190
+ command: 'node framework/hooks/claude-code/core/worktree-remove.js',
191
+ timeout: 60000
148
192
  }]
149
193
  },
150
194
 
@@ -237,9 +281,14 @@ const MORPH_HOOKS = [
237
281
  * - Tracks installed version via _morph_managed.hooks_version
238
282
  *
239
283
  * @param {string} targetPath - Project root directory
284
+ * @param {object} [options]
285
+ * @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
286
+ * Coletor de efeitos. Esta função é o seam mais barato do caminho de update:
287
+ * o objeto de settings INTEIRO já é montado em memória e há UMA escrita, no
288
+ * fim. Previsão e execução percorrem a mesma montagem.
240
289
  * @returns {Promise<{ installed: number, updated: boolean }>}
241
290
  */
242
- export async function installClaudeHooks(targetPath) {
291
+ export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}) {
243
292
  const claudeDir = join(targetPath, '.claude');
244
293
  const settingsPath = join(claudeDir, 'settings.local.json');
245
294
 
@@ -366,11 +415,9 @@ export async function installClaudeHooks(targetPath) {
366
415
  settings._morph_managed.generated_at = new Date().toISOString();
367
416
 
368
417
  // Ensure .claude directory exists
369
- if (!existsSync(claudeDir)) {
370
- await mkdir(claudeDir, { recursive: true });
371
- }
418
+ fx.ensureDir(claudeDir);
372
419
 
373
- await writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
420
+ fx.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n');
374
421
  return { installed, updated: !alreadyCurrent };
375
422
  }
376
423
 
@@ -487,19 +534,24 @@ function resolvePythonForStatusline() {
487
534
  *
488
535
  * @param {string} hooksSourceDir - Path to framework/hooks/claude-code/
489
536
  * @param {string} [globalClaudeDirOverride] - Override ~/.claude/ (for testing)
537
+ * @param {object} [options]
538
+ * @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
539
+ * Coletor de efeitos. Estes são os efeitos FORA do projeto — o relatório do
540
+ * `--dry-run` é obrigado a nomeá-los separadamente, porque ninguém os
541
+ * adivinha da palavra "update".
490
542
  * @returns {Promise<{ installed: boolean, globalDir: string }>}
491
543
  */
492
- export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOverride = null) {
544
+ export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOverride = null, { fx = realEffects() } = {}) {
493
545
  const globalDir = globalClaudeDirOverride || join(homedir(), '.claude');
494
- await mkdir(globalDir, { recursive: true });
546
+ fx.ensureDir(globalDir);
495
547
 
496
548
  // Copy statusline scripts to ~/.claude/
497
549
  const scripts = ['statusline.sh', 'statusline.py'];
498
550
  for (const fname of scripts) {
499
551
  const src = join(hooksSourceDir, fname);
500
552
  if (existsSync(src)) {
501
- await copyFile(src, join(globalDir, fname));
502
- if (process.platform !== 'win32') {
553
+ fx.copyFile(src, join(globalDir, fname));
554
+ if (process.platform !== 'win32' && !fx.dryRun) {
503
555
  try { chmodSync(join(globalDir, fname), 0o755); } catch { /* non-critical */ }
504
556
  }
505
557
  }
@@ -507,14 +559,6 @@ export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOve
507
559
 
508
560
  // Register statusLine in ~/.claude/settings.json (global user settings)
509
561
  const settingsPath = join(globalDir, 'settings.json');
510
- let settings = {};
511
- if (existsSync(settingsPath)) {
512
- try {
513
- settings = JSON.parse(await readFile(settingsPath, 'utf-8'));
514
- } catch {
515
- settings = {};
516
- }
517
- }
518
562
 
519
563
  // On Windows, Claude Code cannot execute .sh files directly — call Python instead.
520
564
  const statuslineCommand = process.platform === 'win32'
@@ -526,14 +570,16 @@ export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOve
526
570
  // `↻` countdown to the 5h rate-limit reset — the only widget that moves on
527
571
  // its own — sits frozen through a long turn. Everything else is event-driven
528
572
  // and would be fine without this.
529
- settings.statusLine = {
530
- type: 'command',
531
- command: statuslineCommand,
532
- padding: 2,
533
- refreshInterval: 10
534
- };
535
-
536
- await writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
573
+ // Fusão rasa: só `statusLine` é do morph-spec; tudo o mais no
574
+ // `~/.claude/settings.json` é do usuário e sobrevive intocado.
575
+ fx.mergeJson(settingsPath, {
576
+ statusLine: {
577
+ type: 'command',
578
+ command: statuslineCommand,
579
+ padding: 2,
580
+ refreshInterval: 10
581
+ }
582
+ });
537
583
  return { installed: true, globalDir };
538
584
  }
539
585