@polymorphism-tech/morph-spec 8.37.1 → 8.40.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 (317) hide show
  1. package/CLAUDE.md +37 -13
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +75 -49
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +6 -5
  8. package/framework/MORPH.md +25 -22
  9. package/framework/agents.json +96 -36
  10. package/framework/docs/CLI.md +54 -17
  11. package/framework/evals/rubrics/code-quality.md +5 -0
  12. package/framework/evals/rubrics/condensed.md +6 -2
  13. package/framework/evals/rubrics/contracts.md +20 -0
  14. package/framework/evals/rubrics/test-coverage.md +17 -6
  15. package/framework/hooks/README.md +9 -3
  16. package/framework/hooks/claude-code/core/graph-refresh-kick.js +52 -0
  17. package/framework/hooks/claude-code/core/telemetry-log.js +172 -87
  18. package/framework/hooks/claude-code/core/validate-completion.js +16 -90
  19. package/framework/hooks/claude-code/core/worktree-context.js +10 -0
  20. package/framework/hooks/shared/command-tokens.js +96 -0
  21. package/framework/hooks/shared/git-exclude.js +44 -0
  22. package/framework/hooks/shared/git-paths.js +52 -0
  23. package/framework/hooks/shared/graph-freshness.js +380 -0
  24. package/framework/hooks/shared/graph-refresh.js +416 -0
  25. package/framework/hooks/shared/morph-cli.js +17 -5
  26. package/framework/hooks/shared/redact-secrets.js +55 -0
  27. package/framework/hooks/shared/telemetry-logger.js +25 -2
  28. package/framework/hooks/shared/transcript-usage.js +80 -0
  29. package/framework/mcp/registry.json +33 -2
  30. package/framework/rubrics/backend.json +2 -2
  31. package/framework/rubrics/default.json +2 -2
  32. package/framework/rubrics/frontend.json +2 -2
  33. package/framework/rules/csharp-standards.md +1 -0
  34. package/framework/rules/infrastructure-standards.md +0 -1
  35. package/framework/rules/morph-workflow.md +10 -4
  36. package/framework/rules/nextjs-standards.md +1 -0
  37. package/framework/schemas/config.schema.json +41 -1
  38. package/framework/schemas/feature.schema.json +5 -1
  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 +40 -11
  42. package/framework/skills/core/morph-brainstorming/SKILL.md +16 -7
  43. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  44. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  45. package/framework/skills/core/morph-implement/SKILL.md +44 -10
  46. package/framework/skills/core/morph-media/SKILL.md +1 -1
  47. package/framework/skills/core/morph-plan/SKILL.md +73 -15
  48. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  49. package/framework/skills/core/morph-review/SKILL.md +51 -13
  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 +39 -13
  53. package/framework/standards/STANDARDS.json +189 -14
  54. package/framework/standards/ai-agents/context-providers.md +16 -26
  55. package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
  56. package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
  57. package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
  58. package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
  59. package/framework/standards/ai-agents/modalities-vision.md +1 -18
  60. package/framework/standards/ai-agents/prompt-sources.md +0 -4
  61. package/framework/standards/ai-agents/testing-ai.md +2 -1
  62. package/framework/standards/architecture/code-comments.md +100 -0
  63. package/framework/standards/backend/dotnet/testing.md +22 -0
  64. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
  65. package/framework/standards/frontend/admin-app/data-table.md +562 -0
  66. package/framework/standards/frontend/admin-app/feedback.md +236 -0
  67. package/framework/standards/frontend/admin-app/forms.md +232 -0
  68. package/framework/standards/frontend/admin-app/page-structure.md +203 -0
  69. package/framework/standards/frontend/design-system/ai-image-generation.md +5 -5
  70. package/framework/standards/frontend/nextjs/components.md +37 -58
  71. package/framework/standards/frontend/nextjs/data-fetching.md +1 -0
  72. package/framework/standards/frontend/nextjs/forms.md +31 -30
  73. package/framework/standards/frontend/nextjs/state-management.md +2 -1
  74. package/framework/standards/frontend/nextjs/testing.md +18 -0
  75. package/framework/standards/integration/mcp/mcp-tools.md +48 -117
  76. package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
  77. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
  78. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
  79. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
  80. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
  81. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
  82. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
  83. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
  84. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
  85. package/framework/templates/code/dotnet/database/migration.cs +49 -83
  86. package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
  87. package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
  88. package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
  89. package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
  90. package/framework/templates/code/dotnet/jobs/job.cs +2 -97
  91. package/framework/templates/code/dotnet/test.cs +2 -87
  92. package/framework/templates/code/typescript/contracts.ts +140 -167
  93. package/framework/templates/context/design-system.md +3 -3
  94. package/framework/templates/docs/proposal.md +11 -0
  95. package/framework/templates/docs/spec.md +15 -0
  96. package/framework/templates/docs/ui-mockups.md +115 -98
  97. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
  98. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
  99. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
  100. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
  101. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
  102. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
  103. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
  104. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
  105. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
  106. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
  107. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
  108. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
  109. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
  110. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
  111. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
  112. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
  113. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
  114. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
  115. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
  116. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
  117. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
  118. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
  119. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
  120. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
  121. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
  122. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
  123. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
  124. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
  125. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
  126. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
  127. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
  128. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
  129. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
  130. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
  131. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
  132. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
  133. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
  134. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
  135. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
  136. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
  137. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
  138. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
  139. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
  140. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
  141. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
  142. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
  143. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
  144. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
  145. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
  146. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
  147. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
  148. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
  149. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
  150. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
  151. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
  152. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
  153. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
  154. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
  155. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
  156. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
  157. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
  158. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
  159. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
  160. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
  161. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
  162. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
  163. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
  164. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
  165. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
  166. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
  167. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
  168. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
  169. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
  170. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
  171. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
  172. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
  173. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
  174. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
  175. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
  176. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
  177. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
  178. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
  179. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
  180. package/framework/templates/feature/decisions.md +124 -122
  181. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
  182. package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
  183. package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
  184. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
  185. package/framework/templates/integrations/asaas-client.cs +332 -387
  186. package/framework/templates/integrations/asaas-webhook.cs +290 -351
  187. package/framework/templates/integrations/neon-auth-config.cs +9 -105
  188. package/framework/templates/project/validate.js +97 -120
  189. package/package.json +2 -2
  190. package/src/commands/dashboard/dashboard.js +80 -53
  191. package/src/commands/graph/affected.js +4 -3
  192. package/src/commands/graph/build.js +109 -18
  193. package/src/commands/graph/explain.js +8 -5
  194. package/src/commands/graph/status.js +14 -8
  195. package/src/commands/mcp/mcp-setup.js +37 -9
  196. package/src/commands/observability/cost.js +148 -18
  197. package/src/commands/project/doctor.js +85 -15
  198. package/src/commands/project/init.js +6 -3
  199. package/src/commands/project/install-plugin-cmd.js +1 -1
  200. package/src/commands/project/update.js +30 -0
  201. package/src/commands/project/worktree.js +73 -90
  202. package/src/commands/state/advance.js +18 -0
  203. package/src/commands/state/approve.js +15 -0
  204. package/src/commands/state/archive.js +62 -9
  205. package/src/commands/state/finish.js +62 -14
  206. package/src/commands/state/gate-check.js +8 -1
  207. package/src/commands/state/gate-decision.js +8 -4
  208. package/src/commands/state/report-check.js +90 -9
  209. package/src/commands/state/usecase.js +66 -0
  210. package/src/commands/tasks/dag.js +17 -2
  211. package/src/commands/validation/validate-feature.js +6 -1
  212. package/src/commands/validation/verify.js +20 -1
  213. package/src/dashboard/app/App.jsx +80 -16
  214. package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
  215. package/src/dashboard/app/components/CommandPalette.jsx +155 -0
  216. package/src/dashboard/app/components/DetailPanel.jsx +47 -11
  217. package/src/dashboard/app/components/EntityLink.jsx +55 -0
  218. package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
  219. package/src/dashboard/app/components/FlowGraph.jsx +19 -17
  220. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
  221. package/src/dashboard/app/components/Markdown.jsx +47 -0
  222. package/src/dashboard/app/components/PhaseRail.jsx +111 -0
  223. package/src/dashboard/app/components/SearchFilter.jsx +40 -22
  224. package/src/dashboard/app/components/Sidebar.jsx +146 -37
  225. package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
  226. package/src/dashboard/app/hooks/usePref.js +38 -0
  227. package/src/dashboard/app/lib/format.js +69 -0
  228. package/src/dashboard/app/lib/phases.js +56 -0
  229. package/src/dashboard/app/lib/project.js +50 -0
  230. package/src/dashboard/app/pages/Activity.jsx +24 -0
  231. package/src/dashboard/app/pages/AgentMap.jsx +371 -370
  232. package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
  233. package/src/dashboard/app/pages/Features.jsx +400 -0
  234. package/src/dashboard/app/pages/Guide.jsx +146 -0
  235. package/src/dashboard/app/pages/HookInspector.jsx +357 -322
  236. package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
  237. package/src/dashboard/app/pages/Portfolio.jsx +156 -0
  238. package/src/dashboard/app/pages/Rubrics.jsx +101 -0
  239. package/src/dashboard/app/pages/RulesMap.jsx +300 -273
  240. package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
  241. package/src/dashboard/app/pages/Standards.jsx +407 -332
  242. package/src/dashboard/app/styles/index.css +187 -47
  243. package/src/dashboard/index.html +14 -12
  244. package/src/dashboard/package.json +32 -24
  245. package/src/dashboard/scanner/activity-scanner.js +351 -0
  246. package/src/dashboard/scanner/agents-scanner.js +63 -1
  247. package/src/dashboard/scanner/docs-scanner.js +59 -0
  248. package/src/dashboard/scanner/feature-status.js +10 -0
  249. package/src/dashboard/scanner/features-scanner.js +360 -0
  250. package/src/dashboard/scanner/hooks-scanner.js +8 -1
  251. package/src/dashboard/scanner/index.js +14 -7
  252. package/src/dashboard/scanner/phases-scanner.js +14 -15
  253. package/src/dashboard/scanner/portfolio.js +153 -0
  254. package/src/dashboard/scanner/project-api.js +195 -0
  255. package/src/dashboard/scanner/rules-scanner.js +42 -37
  256. package/src/dashboard/scanner/skills-scanner.js +105 -43
  257. package/src/dashboard/scanner/standards-scanner.js +4 -1
  258. package/src/dashboard/vite-plugin-morph-data.js +134 -17
  259. package/src/lib/ai/comment-stripper.js +101 -4
  260. package/src/lib/detectors/claude-config-detector.js +2 -7
  261. package/src/lib/detectors/index.js +5 -1
  262. package/src/lib/evaluator-report.js +47 -0
  263. package/src/lib/generators/handoff-generator.js +4 -18
  264. package/src/lib/generators/settings-generator.js +208 -209
  265. package/src/lib/git/default-branch.js +52 -0
  266. package/src/lib/git/tree-fingerprint.js +58 -0
  267. package/src/lib/git/tree-state.js +167 -0
  268. package/src/lib/graph/god-nodes.js +12 -3
  269. package/src/lib/graph/graph-health.js +48 -2
  270. package/src/lib/graph/graph-loader.js +99 -25
  271. package/src/lib/graph/graphifyignore.js +145 -28
  272. package/src/lib/graph/sanitize.js +117 -11
  273. package/src/lib/installers/mcp-installer.js +724 -719
  274. package/src/lib/installers/memory-installer.js +1 -2
  275. package/src/lib/projects/registry.js +154 -0
  276. package/src/lib/risk-detector.js +90 -0
  277. package/src/lib/specs/acervo-format.js +70 -0
  278. package/src/lib/specs/acervo-lifecycle.js +264 -0
  279. package/src/lib/specs/adr-index.js +169 -0
  280. package/src/lib/specs/revocations.js +156 -0
  281. package/src/lib/specs/screen-checklist.js +357 -0
  282. package/src/lib/specs/usecase-check.js +20 -16
  283. package/src/lib/specs/usecase-promoter.js +103 -38
  284. package/src/lib/specs/usecase-registry.js +7 -7
  285. package/src/lib/tasks/tasks-json-parser.js +2 -3
  286. package/src/lib/telemetry/metrics.js +386 -0
  287. package/src/lib/telemetry/telemetry-reader.js +161 -51
  288. package/src/lib/telemetry/verify-event.js +79 -0
  289. package/src/lib/validators/code-budget/code-budget-validator.js +200 -0
  290. package/src/lib/validators/code-budget/comment-scanner.js +75 -0
  291. package/src/lib/validators/code-budget/index.js +9 -0
  292. package/src/lib/validators/reuse/reuse-validator.js +4 -2
  293. package/src/lib/validators/reuse/symbol-scanner.js +27 -22
  294. package/src/lib/validators/validation-runner.js +21 -5
  295. package/src/lib/validators/verify-runner.js +11 -0
  296. package/src/lib/worktree/finish-backup.js +739 -0
  297. package/src/lib/worktree/ignored-triage.js +15 -8
  298. package/src/lib/worktree/worktree-audit.js +5 -0
  299. package/src/scripts/install-plugin.js +1 -1
  300. package/src/scripts/setup-infra.js +31 -22
  301. package/src/utils/agents-installer.js +338 -24
  302. package/src/utils/banner.js +1 -1
  303. package/src/utils/hooks-installer.js +75 -15
  304. package/src/commands/index.js +0 -13
  305. package/src/commands/project/checkpoint.js +0 -209
  306. package/src/core/state/phase-state-machine.js +0 -197
  307. package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
  308. package/src/dashboard/app/pages/LiveState.jsx +0 -206
  309. package/src/dashboard/scanner/squads-scanner.js +0 -13
  310. package/src/dashboard/scanner/state-scanner.js +0 -85
  311. package/src/lib/generators/index.js +0 -10
  312. package/src/lib/hooks/hook-executor.js +0 -167
  313. package/src/lib/validators/checkpoint-persistence.js +0 -39
  314. package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
  315. package/src/lib/validators/shared/index.js +0 -12
  316. package/src/lib/validators/shared/issue-counter.js +0 -14
  317. package/src/lib/validators/shared/result-formatter.js +0 -124
package/CLAUDE.md CHANGED
@@ -11,11 +11,18 @@ This repo is the **framework source code** — not a project that uses morph-spe
11
11
  ## Build & Test Commands
12
12
 
13
13
  ```bash
14
- # Run all tests (sequential — required due to process.chdir state)
14
+ # Once per clone: the dashboard render smoke test (test/dashboard/pages-smoke.test.js)
15
+ # needs the dashboard's own deps and FAILS without them — CI installs them too
16
+ npm install --prefix src/dashboard
17
+
18
+ # Dev loop: only the test files the current change can reach (vs the merge-base with main)
19
+ npm run test:changed # add -- --list to see the selection and why
20
+
21
+ # Run all tests (files in parallel, ~2.5 min; the repo-mutating files run serially after)
15
22
  npm test
16
23
 
17
- # Run a single test file
18
- node --test test/lib/state-manager.test.js
24
+ # A subtree or file (still parallel); --test-concurrency=1 to debug serially
25
+ node scripts/run-tests.mjs test/lib/state-manager.test.js
19
26
 
20
27
  # Coverage
21
28
  npm run test:coverage
@@ -49,7 +56,7 @@ No build step — pure ESM JavaScript, runs directly via Node.js 18+.
49
56
  - **`framework/`** — Metadata installed into target projects (READ-ONLY at runtime):
50
57
  - `MORPH.md` — NLH charter (the single source of behavioral truth)
51
58
  - `CLAUDE.md` — Runtime instructions injected into target's `.claude/CLAUDE.md`
52
- - `agents.json` (v5.2.0) — 8 agents: 7 domain personas + 1 stage persona (`scout`, read-only), no tiers
59
+ - `agents.json` (v5.3.0) — 8 agents: 7 domain personas + 1 stage persona (`scout`, read-only), no tiers; `maxTurns` per persona (ADR-004)
53
60
  - `phases.json` — 5 phases (proposal, uiux, plan, implement, review)
54
61
  - `standards/STANDARDS.json` — index of available standards
55
62
  - `hooks/claude-code/core/` — 7 fail-open guardrail hooks
@@ -87,8 +94,10 @@ State lives in `.morph/state.json`. Phase derived from filesystem (not stored).
87
94
  ## Testing Conventions
88
95
 
89
96
  - **Framework:** Node.js native `node:test` — no Jest or Mocha
90
- - **Concurrency:** Always sequential (`--test-concurrency=1`) — tests use `process.chdir()`
97
+ - **Concurrency:** files run in parallel (`scripts/run-tests.mjs`) — `node --test` gives each file its own process, so `process.chdir()` stays inside the file. A test that WRITES a tracked file of this repo (and restores it) must be listed in `SERIAL` in `scripts/run-tests.mjs`; the runner fails if the parallel phase changes the tracked tree
98
+ - **Subprocesses:** drive the CLI through `test/helpers/cli.js` (`runCli`, no shell) and git through `test/helpers/git.js` (`git(cwd, ...args)`) — `execSync('git …')` pays a cmd.exe per call on Windows. Hooks that export `run(payload)` can be called in-process (see `test/hooks/loop-detect.test.js`); keep one spawn per hook for the standalone wiring
91
99
  - **Temp dirs:** `createTempDir()` / `cleanupTempDir()` from `test/helpers/test-utils.js`
100
+ - **Home:** a suite that runs `setupInfra`/`updateCommand`/`initCommand` calls `isolateHome()` (test-utils) at the top — the installers write `~/.claude/settings.json` through `os.homedir()`. Canary: `test/meta/home-isolation.test.js`
92
101
  - **Mock state:** `createMockState(overrides)` and `createStateFile(tempDir, state)` from test-utils
93
102
 
94
103
  ## Code Conventions
@@ -106,11 +115,15 @@ When modifying framework components, verify the full scope of related files.
106
115
 
107
116
  | When you modify... | Also verify... |
108
117
  |----------------------------------|-------------------------------------------------------------------------------------------|
109
- | `framework/agents.json` | agents-installer.js (test/utils/agents-installer.test.js), doctor.js (REQUIRED_AGENTS) |
118
+ | `framework/agents.json` | agents-installer.js (test/utils/agents-installer.test.js), doctor.js (REQUIRED_AGENTS). **`maxTurns` (ADR-004):** UMA régua de validade, `maxTurnsError` em `agents-installer.js`, com QUATRO leitores — `installAgents` (recusa antes de escrever), `checkAgentsDrift` (`invalid`), `validateAgentsJson` do `doctor` (error) e o Check 6 do `validate-framework.js` (`test/framework/validate-framework-agents.test.js`); uma segunda noção de "valor válido" faz o mesmo arquivo passar num e ser recusado no outro. Sem o campo, o frontmatter sai byte a byte igual ao de antes (canário COMPAT). O corpo gerado anuncia o teto ao sub-agent, e a cláusula 5 dos templates de dispatch depende disso. O drift lê o registry MCP do PACOTE (`mcpRegistryDir`): a cópia instalada não tem `mcp/`, e ler dela dava toda persona com `mcp` como stale numa instalação limpa. O marcador `PARCIAL` (1ª linha) que a cláusula 5 e o corpo da persona mandam gravar é lido por `report-check.js` (`PARTIAL_MARKER` → `report-incomplete`, ação `resume`): mudar a grafia num lado só devolve o parcial lido como `ok`. **`surfaces` (ADR-005):** mesma régua única em quatro leitores (`surfacesError`), e `personaStandardIds` é a leitura única de "todo standard da persona" para o Check 1, o drift e os scanners do dashboard (`agents-scanner.js`, `standards-scanner.js`) — iterar só `standards` deixa id de superfície sem validar e a aresta do grafo sumida. Sem o campo, o `.md` sai byte a byte o de antes: `test/fixtures/agents/ui-designer-5.3.0.{json,md}` é a persona do 5.3.0 e o render do installer de main, comparados com `strictEqual` — nunca regere a fixture para fazer o teste passar. A chave da superfície é a que o checklist de tela do `morph-uiux` escreve em `Superfície` e que o `morph-plan` §5a usa para somar a lista em `tasks[].standards` |
110
119
  | `framework/standards/**` | STANDARDS.json entry exists (o **digest** e o que `standards --find` devolve — corrigir a prosa e esquecer o digest e o caso mais comum), agents reference standard, .md file exists. **Bloco de codigo `csharp` num standard e afirmacao verificavel:** extraia com `node scripts/extract-doc-csharp.mjs <std.md> "## Secao" out.cs` e compile numa sonda com o pin + `TreatWarningsAsErrors` (sem ele `MEAI001` vira aviso e o build fica verde sobre codigo que o consumidor nao compila). Medido: um exemplo carimbado sem compilar tinha 3 erros independentes |
120
+ | `isImplementer` (Check 6 de `scripts/validate-framework.js`) | toda persona com Write/Edit no escopo efetivo de tools tem de alcançar, pelos `standards`, as âncoras `comment-policy` e `test-budget` do STANDARDS.json (`IMPLEMENTER_ANCHORS`). Renomear a chave da âncora no registry derruba o `validate`, e persona implementadora nova sem os dois standards também. Installer e `doctor` não cobram isso de propósito: um `agents.json` instalado antigo continua valendo |
111
121
  | `scripts/generate-standards-registry.js` | `framework/standards/STANDARDS.json` (REGENERE, nunca patch textual — tres features escrevem no mesmo arquivo), `src/lib/standards/verified-against.js` (`parseVerifiedAgainst`/`deriveProvedBy`: `verifiedAgainst` e `provedBy` sao DERIVADOS a cada run e NAO entram em `PRESERVE_FIELDS`), `test/framework/standards-registry.test.js`. **Renomear um standard e uma PARADA, nao um exit 0**: path sumido + path novo faz o gerador recusar nomeando os dois conjuntos e prescrevendo a edicao do campo `path` da entrada antiga — ele nao adivinha o par. `MORPH_STANDARDS_BASE` e o unico seam de teste do script e nada em producao o define |
112
- | `framework/hooks/claude-code/**` | hooks-installer.js MORPH_HOOKS array, hook follows fail-open pattern |
113
- | `framework/skills/core/**` | MORPH.md skills table, skills-installer.js, SKILL.md frontmatter |
122
+ | `framework/hooks/claude-code/**` | hooks-installer.js MORPH_HOOKS array, hook follows fail-open pattern. O `timeout` de cada entrada é em SEGUNDOS, a unidade que o Claude Code lê — em milissegundos ele passa do default de 600 e prende a sessão por horas (canário em `test/hooks/hooks-installer.test.js`); binding `async: true` não tem timeout aplicado |
123
+ | `framework/hooks/shared/telemetry-logger.js` (esquema v2 do `events.jsonl`) | os leitores do MESMO evento: `telemetry-reader.js` (`ingestedBytes`), `src/lib/telemetry/metrics.js` (`cost --metrics`), `verify-event.js`, `report-check.js`, os hooks que gravam e o `activity-scanner.js` do dashboard (lê por `loadTelemetryAllTrees` e soma só por `aggregateCost` — nunca uma segunda soma). `isFailedToolCall` é a régua única de "a chamada falhou: não leu nem mudou nada" — um segundo teste de `PostToolUseFailure` volta a contar os bytes de um Read que falhou e a edição que não entrou. Evento v1 (sem `schemaVersion`) nunca vira zero: cai em "sem agente", `unknown-*` ou "sem duração". O esquema mora em `framework/docs/CLI.md` (`cost --metrics`); campo novo aqui entra lá. Os bindings que só registram (`PostToolUseFailure`, `PostToolBatch`, `SubagentStop`) são `async: true` em `hooks-installer.js`; hook que decide nunca é async (canário em `test/hooks/hooks-installer.test.js`) |
124
+ | `framework/hooks/shared/command-tokens.js` (`producerOf`, `exitCodeMasked`) | `telemetry-log.js` (grava `exitMasked`), `metrics.js` (o rerun agrupa pelo comando produtor e manda o exit mascarado para `unknown-*`) e os guards de Bash que leem `tokenize`. A tabela de `test/hooks/command-tokens.test.js` é o contrato: cada forma que a cláusula 3 do dispatch manda evitar (`\| tail`, `; echo`, `\|\| true`, `&` de fundo) tem linha lá, ao lado de uma parecida que não mascara (`cd src; dotnet test`, `> log 2>&1`) |
125
+ | `framework/hooks/shared/redact-secrets.js` | `telemetry-logger.js` (redige os campos de texto livre, `FREE_TEXT_FIELDS`) e a lista de formas redigidas em `framework/docs/CLI.md`. Regra nova entra em `test/hooks/redact-secrets.test.js` com um caso parecido que NÃO pode ser redigido (`docker run -u 1000:1000`, "Bearer authentication", `git push origin HEAD:refs/heads/main`): regra frouxa apaga o comando que a métrica de rerun lê |
126
+ | `framework/skills/core/**` | MORPH.md skills table (canário `test/framework/morph-skills-table.test.js`: toda skill `morph-*` do disco na tabela do §7, e nenhuma fantasma), skills-installer.js, SKILL.md frontmatter |
114
127
  | `framework/phases.json` | output-schema.js (phase mappings), test/framework/phases.test.js |
115
128
  | `framework/templates/**` | REGISTRY.json (template registered), valid Handlebars helpers |
116
129
  | `framework/templates/REGISTRY.json` (o campo `kind`) | os QUATRO checks de `scripts/validate-framework.js` que leem `kind`/`path`: **4** (resolve arquivo × diretório), **9** (shape exigido por kind), **10** (só lê `path` que é ARQUIVO — `readFileSync` num diretório derruba o validador inteiro com EISDIR, não vira erro de validação) e **12** (cobertura por prefixo; projeto registrado sem arquivo rastreado é erro). `test/framework/validate-framework-registry.test.js` cobra os quatro |
@@ -119,23 +132,32 @@ When modifying framework components, verify the full scope of related files.
119
132
  | `.github/workflows/*.yml` (o job `ai-kit`) | os DOIS workflows carregam o MESMO job — `ci.yml` (toda PR) e `release.yml`, onde `publish` declara `needs: [test, ai-kit]` e continua **sem matrix** (matrix no job que publica roda `npm publish` 1x por OS). Tres detalhes sao load-bearing: `working-directory: framework/templates/dotnet/ai-kit` (o `dotnet test` resolve o `global.json` pelo CWD, nunca pelo `.sln` do argumento — da raiz ele FALHA, enquanto o `dotnet build` funciona, que e o que torna a armadilha facil), `global-json-file` em vez de `dotnet-version` (a banda do SDK vem do pin, nunca digitada no YAML) e o passo `generate-ai-pin-props.js --check` antes do restore (sem ele o job compila props dessincronizadas do pin e reporta verde). `test/framework/ai-kit-ci-packaging.test.js` cobra a forma do job E mede o tarball |
120
133
  | `src/lib/standards/verified-against.js` | os leitores do MESMO fato "quao velho e este standard": `scripts/check-freshness.js` (hard fail por `Last-verified`), `test/framework/standard-header.test.js` (campos obrigatorios no header) e `framework/ai-pin.json` (a data de referencia). Uma segunda copia do regex e como o contador de `// VERIFY:` ficou inerte: `/^\s*\/\/\s*VERIFY:/` exigia inicio de linha e dois-pontos e enxergava 0 de 10 marcadores reais. **`deriveProvedBy` mora aqui pela mesma razao**, e o quarto leitor e `scripts/generate-standards-registry.js`, que DERIVA `verifiedAgainst`/`provedBy` a cada run — **TRES** tipografias da clausula sao reais no acervo e a regua e a SOMA de paths (27 em 16 entradas), nunca a contagem de entradas: a crase abre DEPOIS de "provado por", ANTES dela, e a clausula tambem aparece em negrito no MEIO da frase (`**provado por**`). Ignorar a segunda perdia 4 das 16 provas; ignorar a terceira perdia 2 paths de `llm-runtime-defaults` **sem mover contador nenhum** — a entrada seguia entre as 16 e o exit era 0. Por isso `test/framework/standards-registry.test.js` pina os paths ESCRITOS A MAO, e nunca chama `deriveProvedBy` para gerar a expectativa do acervo real |
121
134
  | `src/core/paths/output-schema.js`| Run `npm run generate` to update phase-utils.js |
135
+ | `src/lib/specs/acervo-format.js` (o formato do acervo) | as formas que TODO leitor e escritor do acervo compartilham — id de UC, nome canônico `UC-{nn}-{slug}.md` (vem de `OUTPUT_SCHEMA.usecases.filenamePattern`), `superseded` e o corte de linha de tabela GFM — com seis leitores: `usecase-registry.js`, `usecase-promoter.js`, `usecase-check.js`, `acervo-lifecycle.js`, `revocations.js` e `tasks-json-parser.js`. Uma segunda cópia é como dois leitores começam a discordar do mesmo arquivo: um UC que um enxerga e o outro não. `supersessionProblems` (`acervo-lifecycle.js`) é a regra ÚNICA do frontmatter de revogação: o escritor do `finish` só produz o que ela aceita, e `dag`/`usecase check` a rodam enquanto o `spec.md` ainda pode ser corrigido (ele congela no Gate 2). O índice de ADRs (`adr-index.js`) é um arquivo por ADR, como o acervo, para que features paralelas não se toquem no merge. O exemplo da seção de revogação em `framework/templates/docs/spec.md` e nas skills é lido pelo código em `test/framework/acervo-lifecycle-docs.test.js` |
122
136
  | `framework/MORPH.md` | All core skills (cross-references), CLAUDE.md (slash command list), e o **teto de 38 400 chars** (`test/framework/resident-size.test.js`). MORPH.md vive colado no teto: linha nova ali só entra liberando pelo menos o mesmo número de caracteres **na mesma seção**, com o antes/depois medido — `MORPH_MD_LIMIT` não sobe |
123
137
  | a divisão loop × suíte (`verify {f} {task}` recorta sozinho; `--filter` força nós, `--full` desliga) | os QUATRO textos que a declaram e não podem discordar: `framework/docs/CLI.md` (seção "Suítes de teste" — a tabela canônica, a forma de `project.tests[]` e a subseção *O recorte por task*, com os limites), `morph-implement` §9, `morph-apply` §3a (o `verify` do loop e os `outputs` gravados ANTES dele — o recorte os lê) e `morph-review` (o Gate 3 roda **sem** filtro e `verify {feature}` nunca recorta). Uma superfície que ensine filtrar no Gate 3 devolve o F30 pela porta da frente; canário: `test/framework/verify-stack-docs-prose.test.js` |
124
- | `src/lib/worktree/ignored-triage.js` | `worktree.js` (`classifyIgnoredLeftovers`), `test/lib/worktree/ignored-triage.test.js`, e os canários de `test/commands/finish.test.js` que pinam "o exemplar único nunca morre" — mudar um bucket muda o que o `finish` apaga |
138
+ | `src/lib/worktree/ignored-triage.js` | `worktree.js` (`classifyIgnoredLeftovers`), `test/lib/worktree/ignored-triage.test.js`, e os canários de `test/commands/finish/merge-worktree.test.js` (e o pré-voo de `finish/dry-run.test.js`) que pinam "o exemplar único nunca morre" — mudar um bucket muda o que o `finish` apaga |
139
+ | `src/lib/worktree/finish-backup.js` (backups do finish e a retenção) | os QUATRO pontos que escrevem ou leem `.git/morph-spec/finish-backup/`: `finish.js` (o shed da raiz e a poda do fim, `enforceBackupRetention`), `worktree.js` (o backup do desmonte e a poda do fim do `worktree remove`), `worktree-audit.js` (`auditFinishBackups` → `doctor --worktrees`) e `doctor.js` (a linha `.git/morph-spec` do padrão, nunca `✗`, e o `--prune`, a ÚNICA poda que inclui legado). `resolveBackupRetention` é a autoridade do `finishBackup.retention` de `framework/schemas/config.schema.json`: política que não se lê PULA a poda, nunca cai no padrão, porque a operação apaga. Backup sem manifesto é legado, e a poda automática nunca o apaga nem o conta. Um finish grava as duas pastas com o MESMO carimbo, e o carimbo da execução vai em `protectStamps`. Só `task/` é namespace. Apagar é PROVE-or-DECLINE, como a triagem de `ignored-triage.js` (que decide o que entra no backup) |
125
140
  | `src/lib/git/pr-index.js` | os QUATRO leitores do mesmo fato "esta branch tem PR": `fleet-scanner.js` (coluna PR), `branch-classifier.js` (o que NÃO apagar), `worktree-audit.js` (o que NÃO sugerir remover) e `pending-prs.js` (a síntese "aguardando merge"). `null` é UNKNOWN, nunca "sem PR" |
126
141
  | `src/lib/git/branch-classifier.js` | `src/commands/project/branch.js`, `worktree-audit.js` (`unintegratedWork` reusa `classifyBranch` — o `doctor` e o `branch prune` não podem discordar sobre a mesma branch), `test/commands/branch-prune.test.js`, e `lifecycle-audit.js` (`abandoned-scaffold` conta commit próprio pelo mesmo `resolveBaseRef` + `commitsAhead`) |
127
142
  | `src/lib/git/pending-prs.js` | os TRÊS consumidores da mesma síntese "aguardando merge": `list.js` (linha sintetizada), `fleet-scanner.js` (entrada `pr-pending`) e `doctor.js` (`auditPullRequests`). Uma feature visível numa superfície e invisível na outra é o bug que este módulo existe para impedir |
128
143
  | `src/lib/git/gh.js` | `pr-index.js`, `pr-files.js` e `finish.js` — as DUAS formas de invocar o `gh` viraram uma; uma segunda cópia de `resolveGh` volta a tornar o caminho de sucesso do índice intestável |
129
- | `src/lib/worktree/feature-truth.js` | The four leaders reading the same "which tree owns this feature" predicate: `fleet-scanner.js`, `list.js`, `handoff.js`, and the mutator guard (`approve.js`/`advance.js`/`score.js`/`gate-decision.js`/`gate-check.js`) |
144
+ | `src/lib/git/tree-fingerprint.js` (a impressão da árvore) | `verify.js` (grava `fingerprint` no evento `Verify`; `findRepeatedVerify`, de `verify-event.js`, compara com o último verify de feature COMPLETO — rodada com `persisted: false` nunca é a prova que uma completa substitui) e `metrics.js` (`verify.unchangedRepeats`, pela mesma regra). `.morph/` fica fora, exceto `config/config.json`: o próprio verify escreve feature.json e logs a cada rodada, e contá-los faria duas rodadas sobre o mesmo código parecerem diferentes. `repeatOf` é informação e nenhum gate o lê: ligá-lo ao Gate 3 é aceitar a prova de uma rodada anterior no lugar da atual |
145
+ | `src/lib/git/tree-state.js` (o bloco `tree` do `report-check`) | `report-check.js` (toda saída, inclusive o batch `report-check <f> T1 T2`, e a foto `--before-eval` em `4-review/tree-before-eval.json`) e as skills que leem os campos: `morph-implement`/`morph-apply` (pós-dispatch e retomada do PARCIAL) e `morph-review` (higiene contra `tree.sinceBeforeEval`). Campo renomeado aqui sem as skills é instrução que lê `undefined`: `test/framework/report-check-wiring.test.js` confere que todo `tree.*` citado existe na saída real. A foto compara CONTEÚDO (blob por arquivo), não o `git status`: a mutação que o avaliador commitou também aparece. Fora de git o bloco é `null`, nunca árvore vazia. `MUTATION_MARKER` tem a mesma grafia que as skills mandam gravar |
146
+ | `src/lib/worktree/feature-truth.js` | The five leaders reading the same "which tree owns this feature" predicate: `fleet-scanner.js`, `list.js`, `handoff.js`, the mutator guard (`approve.js`/`advance.js`/`score.js`/`gate-decision.js`/`gate-check.js`) and the dashboard's `features-scanner.js` (only for ACTIVE features — it spawns git — and a worktree owner without the feature files is shown as `rootStale`, never silently replaced by the root skeleton) |
130
147
  | `src/lib/tasks/test-runner.js` | `verify-runner.js` (o nó `tests` repassa o `--timeout` e lê `timedOut`), `src/commands/project/worktree.js` (as constantes `WORKTREES_DIR`/`LEGACY_WORKTREES_DIR` que a descoberta ignora — duplicar as strings aqui é como o bug volta), `test/lib/test-runner.test.js` (o canário do ignore de worktrees e o do timeout escalado). Um worktree é o checkout de OUTRA feature: varrê-lo dobra o relógio e reporta sobre código fora da árvore avaliada. `stripAnsi` roda antes de TODO extrator de contagem (ponto único em `parseTestCount`) e das strings de `isZeroTestRun` — uma cor SGR no meio do sumário derrotava os regexes ancorados (#110). O extrator `dotnet` lê os DOIS sumários (#115): a linha VSTest e o bloco MTP (cabeçalho `Test run summary:`/`Resumo da execução de teste:` na coluna 0, `total:` indentado), em pt-BR e en-US — UM bloco por invocação de `dotnet test` (uma solução de dois projetos dá um bloco agregado, medido), então somar blocos é somar links `&&`; um terceiro locale é `null`. `mtpSummaryTotals` (um total por bloco) é o que `judgeNarrowedRun` lê para achar, numa cadeia MTP recortada, o link que rodou 0 (exit 8 ignorado) escondido pela soma. `ZERO_RUN_SIGNATURES` mistura o medido e o documentado — medidos (capturas em `test/fixtures/test-output/`): o exit 8 do MTP e a string pt-BR do VSTest; só documentados, sem captura: a string en-US do VSTest e as de vitest/jest. O exit 9 fica FORA de propósito: é piso de projeto, não zero testes. Leitor: `judgeNarrowedRun` (`verify-runner.js`). `extractCsprojTargets`, `isInside` e `canonicalPath` também são lidos por `test-scope.js` (`canonicalPath` dá a todo path de `outputs` a grafia do DISCO — no win32 um `Foo.test.ts` mal grafado passa no `isFile`, e sob `--linux`, case-sensitive, o runner não o acha enquanto um irmão mantém a contagem acima de zero); `dotnet-test-introspection.js` lê só `isInside`. Um segundo canonicalizador é como os dois lados passam a discordar sobre o mesmo path |
148
+ | `src/lib/specs/screen-checklist.js` (checklist de tela, #129) | o sinal mora aqui (`collectScreenChecklist`) e tem QUATRO leitores: `gate-check.js` na `uiux` (`problems` → pausa) e no `review` (`uncovered`: item decidido sem linha na seção `## Checklist de tela` que `evaluator-report.js` lê — última ocorrência; tela toda "não se aplica" não deve nada), e `approve.js`/`advance.js` com assinatura `auto` (`uiuxAutoApprovalBlock`). Política só em `risk-detector.js`: `uiuxChecklistPause` é o predicado ÚNICO do gate-check pausar e do auto recusar — uma segunda regra no advance fez approve e advance discordarem da mesma feature — e `gateDecisions.uiux.pause === false` destrava os dois só para o mockup que a decisão viu: `persistGateDecision` (`gate-decision.js`, o escritor único de `gateDecisions`) grava `mockupsHash` e `uiuxAutoApprovalBlock` o compara com o arquivo atual — os dois leem `mockupsHash` daqui, e um segundo cálculo do hash (outra normalização de BOM/CRLF) faz toda decisão parecer de outro mockup. `--force` não destrava em nenhum dos dois. Mockup legado (`## S\d+:` e nenhuma `## Tela:`, o template anterior) é isento nas duas fases com `exempt` = `LEGACY_MOCKUP_EXEMPTION` à vista no `getRiskSignals`; qualquer outro heading é "nenhuma tela declarada", senão trocar o heading pularia o checklist (fixture: `test/fixtures/mockups/legacy-ui-mockups.md`, o template de main). Os rótulos dos itens casam com DOIS textos que o parser não vê, os dois com canário em `test/lib/specs/screen-checklist.test.js`: o template do prompt da `morph-uiux` §3 e `framework/templates/docs/ui-mockups.md`; mais o formato de linha que o prompt do avaliador (`morph-review` §4) e a rubrica `contracts.md` ("Contrato de tela") prescrevem. A `Superfície` é validada contra as chaves de `surfaces` da `ui-designer` (`knownSurfaces`) — é a chave que o `morph-plan` §5a usa para somar standards |
131
149
  | `src/lib/tasks/test-scope.js` (o recorte por task, #87) | Este DECIDE, `verify-runner.js` executa: `applyTaskScope` roda UMA vez em `runVerify`, antes do build, e `preDecideRun` (pula `out-of-scope`) e `judgeRun` → `judgeNarrowedRun` (nunca `compareTestCount`; zero ou desconhecido é vermelho) são a cópia única para host e `--linux` — o container recebe o recorte pelo `node.command`. Importa `effectiveGroups` (`test-nodes.js`), `extractCsprojTargets`/`isInside`/`canonicalPath` (`test-runner.js`), `normalizePath` (`traceability-check.js` — um normalizador só para `outputs`), `samePathCase` (`docker-audit.js` — só a CAIXA vem do disco: um path que atravessa symlink/junction fica como escrito, senão o arquivo muda de nó) e a introspecção .NET abaixo. `runs[].selection` sai só no `--json`: `nodeStatuses()` não o persiste. Piso `--minimum-expected-tests` de projeto MTP (#114): quando `floorOverride` o lê de UM lugar, o link recortado ganha `"-p:TestingPlatformCommandLineArguments=<valor com o piso em 1>"` (o `-p:` troca o valor inteiro — medido, sem recompilar); qualquer outra forma roda inteiro, e também o comando que já declara a propriedade (o `-p:` do recorte vem por último e ganharia) e o link que roda mais de um projeto (o `-p:` é global à invocação e trocaria os argumentos do outro). Forma de comando nova se lê e se MEDE (`test/fixtures/test-output/README.md`) antes de estreitar; o resto roda INTEIRO com motivo. Canários: `test/lib/tasks/test-scope.test.js`, o bloco `runVerify — task-scope recorte` de `test/lib/verify-runner.test.js`, `test/commands/verify-filter.test.js` e a prosa em `test/framework/verify-stack-docs-prose.test.js` |
132
150
  | `src/lib/tasks/dotnet-test-introspection.js` | Só LÊ (dividido de `test-scope.js`): o varredor C# que apaga literais (`blankCSharpLiterals`), o tripwire contra o texto CRU + o balanço de chaves (`csharpClasses`), `classNamesOf`, `dotnetTestMode` (lê o `global.json` por `jsoncToJson` de `src/lib/ai/comment-stripper.js`), o piso `--minimum-expected-tests` / `<VSTestTestCaseFilter>` do projeto, `floorOverride` (#114: o valor do `-p:` que rebaixa o piso a 1 e repassa o resto — só com UM `<TestingPlatformCommandLineArguments>` literal no csproj, sem atributo, num `<PropertyGroup>` sem atributo direto sob `<Project>` (`enclosingElements` anda nas tags: `Condition` no GRUPO, `<Target>` e `<Choose>` injetavam um filtro que o MSBuild não usaria), sem `TreatAsLocalProperty`, sem `<Import>`, sem a propriedade em nenhum outro arquivo que liga o projeto, sem piso no testconfig e sem `` $ @ % ; , & " ' ` \ ! `` no valor; `null` roda inteiro; `bindingMsbuildFiles` é a lista única de arquivos MSBuild que `msbuildDeclares` também lê — o `.csproj.user` e `Directory.Build.props/.targets/.rsp` + `Directory.Packages.props` até a raiz do DISCO, porque o MSBuild não para no repositório), `ignoresExitCode` (o MESMO scan de csproj/Directory.Build.*/testconfig: `--ignore-exit-code` ou `TESTINGPLATFORM_EXITCODE_IGNORE` desarma a prova do recorte MTP que resta quando o sumário é ilegível, o exit code — desde a #115 a contagem do sumário, pt-BR/en-US, prova primeiro, e o scan fica porque decide ANTES da rodada), o projeto de teste dono de um `.cs` (`dotnetTestProjectOf` — só `.cs`/`*.csproj`, via `nearestCsprojOf`), `nearestProjectOf` + `isDotnetTestProject` (qualquer arquivo; cobrem `.csproj`/`.fsproj`/`.vbproj` — `test-scope.js` roda o nó INTEIRO por QUALQUER arquivo não-`.cs` que o nó pode rodar cujo projeto mais próximo é de teste: `.feature`, `.verified.txt`, `.razor`, dado de teste), `isNonCSharpSource`/`isDotnetSource`, `readsLikeTestSource` (o marcador FROUXO no texto cru atrás de `unrecognisedTestFile` e do segundo braço do F2 — falso positivo só custa relógio) e `CS_TEST_ATTRIBUTE` (nome inteiro numa seção de atributo que FECHA — `, TestContext` num construtor e uma coleção `[\n Env.Test,\n]` compravam um verde; o resíduo sondado mora no JSDoc de `csharpClasses`). **Falha para o lado SEGURO:** varredura desbalanceada, literal sem fim, buraco de interpolação cortado ou palavra-chave de tipo que não virou declaração → o nó roda INTEIRO; um erro que ESTREITA roda menos testes e sai verde. `test-scope.js` reexporta `dotnetTestMode`/`classNamesOf`, e `test/lib/tasks/dotnet-test-introspection.test.js` pina que são as MESMAS funções. A garantia e o que ela NÃO cobre moram no JSDoc de `csharpClasses`: mexeu no varredor, reescreva-o junto e prove por mutação que o tripwire ainda morde |
133
151
  | `src/lib/tasks/linux-runner.js` | `verify-runner.js` (`runNodesInLinux` faz as MESMAS pré-decisões do laço local e julga o resultado pelo MESMO `judgeRun` — uma segunda cópia dessa regra é como container e host passam a discordar da mesma suíte), `framework/hooks/shared/e2e-check.js` (`defaultExec`/`isDockerAvailable` são reusados, nunca recriados), `src/lib/worktree/dirs.js` (nomes do contêiner de worktree nas exclusões do `tar`), `framework/schemas/config.schema.json` (`linuxImage`) e `test/lib/tasks/linux-runner.test.js` (o canário do bind mount: montar a árvore em `/work` é v9fs case-insensitive e não reproduz F35) |
134
152
  | `--record-baseline` (`recordTestBaselines` em `src/lib/tasks/test-nodes.js`) | `verify-runner.js` (`baselineTargetProblem` recusa ANTES do build; `recordBaselineFrom` só grava com todos os `runs[]` verdes), `src/commands/validation/verify.js` (não persiste stamp: é manutenção de config, não prova de gate), `framework/schemas/config.schema.json` (`expectedCount`) e `atomicWriteJson` de `state-manager.js` — o número é MEDIDO por comando, nunca digitado (D8). Com `{task}` é erro de uso ANTES do build (`baselineTargetProblem`, #87), e `recordTestBaselines` recusa de novo qualquer medição com `selection` — a segunda barreira mora no gravador |
135
153
  | `src/lib/tasks/eval-node.js` | os leitores do MESMO nó `runner: "evals"`: `test-nodes.js` (o enum de `TEST_RUNNERS` e a validação fail-closed do bloco `evals`), `verify-runner.js` (`preDecideRun` faz o pré-voo — os três estados de cache reprovam ANTES de `dotnet test`; `judgeEvalRun` lê o sumário depois), `verify.js` (`evalsStatusOf` deriva `nodes.evals` do stamp) e `framework/schemas/config.schema.json`. O pin tem UM leitor (`readAiPin`), chamado com duas raízes (`framework/` e `.morph/framework/`) — um segundo parser de `ai-pin.json` é como as duas verdades começam. **`null` é DESCONHECIDO, nunca 0**: sumário ilegível não pode fabricar queda contra `expectedCount`, e miss de cache é `fail` nomeado, JAMAIS `skip`. A unidade do nó é o CENÁRIO: só `judgeEvalRun` compara com `expectedCount` — `judgeRun` passa baseline `null` para `runner: "evals"` (#110 parte 2: os `[Fact]` do extrator contra a baseline de cenários reprovavam um 24/24), e sem sumário legível a contagem é `null`, nunca a do extrator — nem compara nem vira baseline. O sumário (`morph-eval-{id}-{pid}.json` no temp) é APAGADO antes e depois da rodada: um arquivo herdado sob o mesmo pid era lido como desta. `--linux` com nó de eval é erro de uso: o canal `MORPH_EVAL_*` carrega caminhos absolutos do host e o container roda uma cópia em `/work` |
136
154
  | `nodeStatuses()` em `src/commands/validation/verify.js` | os TRÊS blocos `nodes` de `framework/schemas/feature.schema.json`, todos `additionalProperties: false` — `verification.nodes`, `verifications.feature.nodes` e `taskVerifications.*.nodes`. A função é escritora ÚNICA dos três: um campo novo aqui sem os três no schema faz o histórico violar o próprio contrato. Canário: "os DOIS escritores do mapa de nós concordam" em `test/commands/verify.test.js` |
137
- | `src/lib/graph/**` | `test/lib/graph/*.test.js`, the committed fixture `test/fixtures/graph/namespace-hierarchy.graph.json` (ADR-003 regression — never hand-edit it), `doctor.js` (`codebase graph` check), `architecture-scanner.js` (centrality section), `setup-infra.js` step 18b |
155
+ | `src/lib/graph/**` | `test/lib/graph/*.test.js`, the committed fixture `test/fixtures/graph/namespace-hierarchy.graph.json` (ADR-003 regression — never hand-edit it), `doctor.js` (`codebase graph` check), `architecture-scanner.js` (centrality section), `setup-infra.js` step 18b. `isTestPath` (`sanitize.js`) é o predicado ÚNICO de teste: `god-nodes.js`, `recoverDispatch`, `graph status\|explain` e `symbol-scanner.js` (`isTestFile`, que o `code-budget` também lê) o importam, e uma segunda régua faz o grafo e o validador discordarem sobre o mesmo arquivo. A fixture `test-doubles.graph.json` saiu do graphify real (passo a passo em `docs/specs/graphify-integration/fixture-test-doubles/`) — regerar, nunca editar à mão |
156
+ | `framework/hooks/shared/graph-freshness.js` (a regra única de "grafo em dia") | os três lados que perguntam: `graph-refresh.js` (decide se roda e se o disparo solta processo), `graph-loader.js` `getFreshness` (`graph status\|explain\|affected` e o `doctor`) e `graph build\|refresh` (gravam a rodada em `<git-common-dir>/morph-spec/graph-refresh-state.json`). O registro da rodada existe porque o `graphify update` 0.9.50 não recarimba `built_at_commit` quando a topologia não muda: sem ele, o grafo parecia velho para sempre e cada disparo rodava o graphify de novo (~80 s no MORPH_GHLBrain). As regras do `.graphifyignore` entram por CONTEÚDO (hash sem comentário, BOM e CRLF), nunca pelo relógio, e `ignoreTextOf` é a ÚNICA leitura de "as regras desta árvore": numa worktree sem arquivo próprio, as da raiz — as mesmas que o `graph refresh` põe ali durante a rodada (`prepareIgnore` em `build.js`). O arquivo temporário leva `TEMP_IGNORE_MARKER` na 1ª linha; nas duas triagens de sujeira (`finish` e `worktree remove`) `withoutLeftoverIgnore` o tira da conta ANTES das recusas e `dropLeftoverIgnore` o apaga só DEPOIS delas — apagar antes é mutação atrás de uma precondição. Mudar a grafia do marcador num lado só devolve o `worktree-dirty` de uma rodada morta no meio. Mora em `hooks/shared` porque hook não importa `src/` |
157
+ | `framework/hooks/shared/graph-refresh.js` (o refresh em background) | os dois disparos (`graph-refresh-kick`, depois de Bash/PowerShell com `git`/`gh`/`morph-spec`, e o SessionStart de `worktree-context.js`), `graph build\|refresh` (o MESMO lock por repositório) e `worktree.js` (`seedGraphFromRoot`: a worktree nasce com o grafo da raiz e o registro dela — em dia no HEAD só se a raiz estava). **Só a raiz**: o worker destacado nasce com `cwd` no git common dir e nunca recebe o caminho de uma worktree — no win32 um diretório em uso não se apaga, e o `finish` de uma worktree com refresh correndo falhava no meio. A worktree anda por `morph-spec graph refresh`, em primeiro plano. `runGraphify` roda o graphify com `cwd` no git common dir, nunca na árvore (ele lê o alvo, o ignore e o git pelo argumento — medido no 0.9.50). O disparo lê o HEAD pelo disco (`readPrimaryHead`, `git-paths.js`) e não solta processo quando o grafo não pede refresh: ele roda a cada comando git do agente. O step 18b de `setup-infra.js` não instala o hook git do graphify: ele não roda em worktree, não tem `post-merge` e carimba como em dia um grafo sem os arquivos do merge |
158
+ | `framework/hooks/shared/git-exclude.js` | ÚNICO escritor dos blocos do morph-spec no `info/exclude` do COMMON dir — o `.git/worktrees/<id>/info/exclude` o git não lê para worktree vinculada. Leitores que passam o seu marcador: `worktree.js`, `handoff-generator.js` e `graph-refresh.js`. Resolve o common dir pelo disco, sem subprocesso: um `git` gerado por um processo destacado abria janela de console no win32 |
138
159
  | `src/lib/validators/reuse/**` | `validation-runner.js` (case + push universal), `framework/rubrics/*.json` (o id `reuse` mapeado numa categoria, senão o achado não pesa no score), `test/fixtures/graph/prospectpro-reuse.graph.json` (recorte do grafo real — regerar com `build-reuse-fixture.mjs`, nunca editar à mão) |
160
+ | `src/lib/validators/code-budget/**` | `validation-runner.js` (case + push universal fora do `light`), `framework/rubrics/*.json` (o id `code-budget` em `code_quality` nas TRÊS, senão o aviso não pesa), `framework/docs/CLI.md` (o que conta como teste e os limites 1,5×/40 linhas) e `test/integration/code-budget-verify.test.js`. Lê `addedLinesAgainst` (`default-branch.js`), `isTestFile`/`isProjectSource` (`symbol-scanner.js`, a mesma régua de teste do `reuse`) e `stripComments` com `js` (`comment-stripper.js`). Sempre `warning`. Não conseguir medir é skip `unavailable` com razão, e o `validation-runner` não põe validador pulado em `results`: pulado não conta como verde |
139
161
  | o ciclo de reuso (`reuse-map.md`) | `output-schema.js` (+ `npm run generate`), `framework/schemas/tasks.schema.json` + `tasks-json-parser.js` (campo `reuse`), `morph-plan` §1c/§5b, template de dispatch de `morph-implement`/`morph-apply`, personas de `agents.json`, `evals/rubrics/code-quality.md` + `condensed.md`, `morph-review` (insumo do avaliador), `test/framework/graph-fallback.test.js` |
140
162
  | `framework/mcp/registry.json` | mcp-installer.js, claude-config-detector.js, agents-installer.js (personas' `mcp` field), `scripts/validate-framework.js` Check 8, `test/framework/mcp-registry.test.js` |
141
163
  | Any `framework/` file that calls `morph-spec graph` | The fallback to grep must be declared in the SAME file — `test/framework/graph-fallback.test.js` enforces it (ADR-002: a skill that requires the graph with no fallback is a bug) |
@@ -146,8 +168,10 @@ When modifying framework components, verify the full scope of related files.
146
168
  | `src/lib/risk-detector.js` | os DOIS lados da mesma decisão de gate: `gate-check.js` (`collectSignals` COLETA do disco; nenhum sinal que descreva um FATO pode entrar em `OVERRIDABLE_SIGNAL_KEYS` — `gate`/`workType`/`e2e*` são o precedente), `src/commands/validation/verify.js` (o stamp `verification` é a ÚNICA coisa que o gate lê: campo novo aqui só vira sinal se o stamp o gravar), `framework/schemas/feature.schema.json` (`verification` é `additionalProperties: false`), `framework/MORPH.md` §5 (tabela de sinais — ela se declara fonte única) e `framework/docs/CLI.md`. Política escrita no coletor cria duas fontes para o mesmo fato — foi assim que `gate-check` e `approve` divergiram uma vez sobre o score do Gate 3. **`verificationScope` é sinal, não auditoria**: a dispensa de e2e (`project`/`scope`) só conta vinda de um stamp de escopo `feature` — um `verify {f} {task}` de task `group: docs` carimba a razão de dispensa e compraria o Gate 3 da feature inteira. **E o escopo de task pausa o ship por si só** (#87): no `review`, `verificationScope` string ≠ `'feature'` pausa ANTES do bloco de e2e, mesmo com todos os nós verdes — o recorte automático faz TODO `verify {f} {task}` rodar menos que a suíte; `null` (stamp legado) não pausa por esta regra. O par mora em `verify.js`: `--filter` de escopo de feature não persiste o stamp (`reason: 'filtered'`), nem `--skip-tests`/`--skip-build` (`'skip-tests'`/`'skip-build'`, #113 — nó pulado não reprova o rollup e nenhuma regra do gate lê `tests`/`build` pulados; a lista é `PARTIAL_RUN_FLAGS`) |
147
169
  | `src/lib/installers/effects.js` | os NOVE pontos de emissão que atravessam o caminho de `update`: `update.js` (clean, payloads, spawn de `generate-refs.js`, `gitIndex`), `skills-installer.js`, `agents-installer.js`, `setup-infra.js` (`installRulesForStack`, `updateClaudeMdAgentsBlock`), `hooks-installer.js` (`installClaudeHooks`, `installGlobalStatusline`), `file-copier.js` (`updateGitignore`, `ensureGitattributes`), `claude-md-injector.js`, `version-checker.js` (`saveProjectMorphVersion`) e `templates-reconcile.js`. **O caminho real também emite** — o resumo de uma execução real sai de `list()`, e é por isso que um ponto de emissão esquecido some das DUAS saídas ao mesmo tempo. Canários: `test/lib/installers/effects.test.js` (igualdade dry × real do MESMO roteiro) e `test/commands/update-dry-run.test.js` — que tem DOIS canários e precisa dos dois: **igualdade** prova que os dois lados percorrem o mesmo caminho, **diferencial** (mudar `--templates` muda a previsão) prova que o caminho lê a flag. Só igualdade aprova uma previsão e uma execução que ignoram a MESMA flag — foi medido nesta feature, por mutação. Um laço de limpeza de órfão novo tem de nascer calado em `fx.dryRun` (D6): o clean o subsome. E a lista de `frameworkDirsToClean` é pinada **à mão** no teste (`DIRETORIOS_APAGADOS_POR_INTEIRO`, dez caminhos, `deepEqual` ordenado): derivar a expectativa chamando a própria função deixou a suíte inteira cega — medido, dava para remover 2 das 10 entradas sem matar nenhum dos 207 arquivos de teste |
148
170
  | `src/lib/ai/**` (`doctor --ai`) | `src/commands/project/doctor.js` (`doctorAiCommand` + a guarda de uso indevido), `bin/morph-spec.js` (`--ai`), `framework/docs/CLI.md`. **`--ai` NUNCA entra no `doctor --full` nem no default** — o renderer do `--full` manda tudo que não é `ok`/`warn` para `✗` e `test/commands/doctor.test.js` exige zero `✗` neste repositório. **Nada alcançável por `--ai` pode escrever**: `test/commands/doctor-ai.test.js` varre o grafo de imports E o corpo de `doctorAiCommand` (com os imports dinâmicos fixados nos dois permitidos), reprovando `writeFileSync`/`spawn`/`fs-extra` em CÓDIGO — comentários apagados antes, porque os módulos EXPLICAM por escrito que não fazem isso e medir a menção seria medir uma cópia do objeto. `null` é DESCONHECIDO e vira `skip` **com razão obrigatória**; `[]` é "perguntei e não achei" e vira `ok` |
149
- | `src/lib/ai/registry-audit.js` | o leitor de PRODUÇÃO do mesmo documento: `framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs` (`CommentHandling = Skip`, `AllowTrailingCommas`, `AllowedAliasProperties` com `pricing`/`pricingWaiver`) e o exemplar `model-registry.v2.json` que o framework manda COPIAR. A auditoria tem de enxergar a **mesma forma** que o leitor de produção enxerga: com `JSON.parse` estrito, duas das cinco checagens saíam `skip` sobre o exemplar canônico — e `skip` não muda exit code, então o silêncio era total. A tolerância a JSONC vive em `src/lib/ai/comment-stripper.js`, ÚNICA (o `code-audit.js` usa o mesmo varredor) — e a paridade é nos DOIS sentidos: `jsoncToJson` **lança** no comentário de bloco não fechado, porque o `JsonCommentHandling.Skip` do .NET lança, e ser mais frouxo que o kit é aprovar documento que a app recusa carregar. Ao mascarar strings, **as aspas delimitadoras ficam**: apagá-las junto faz um elemento-string virar espaço puro e o detector de vírgula sobrando comer um SEPARADOR real (`{"tags":["a","b"]}` saía inválido). Canário obrigatório: array terminado em **string**, com e sem vírgula sobrando — uma suíte só com arrays de número passa dos dois lados dessa inversão. A **REGRA DURA** do exemplar — *todo alias declara `pricing` OU `pricingWaiver`, nunca os dois, nunca nenhum* — é do alias, não de um mapa no topo: ler só o mapa dava 6 falsos positivos sobre um documento correto. O canário **copia o exemplar do disco**, nunca uma paráfrase dele |
150
- | `src/lib/worktree/dirs.js` | ÚNICA fonte do nome do container (`worktrees/`, sem ponto — Jest esconde diretório com ponto e reporta 0 testes sem erro). `worktree.js` re-exporta; `graphifyignore.js`, `file-copier.js`, `architecture-scanner.js`, `test-runner.js`, `worktree-audit.js`, `state-scanner.js`, `tasks/linux-runner.js` (exclusões do `tar` do `--linux`) importam. `test/lib/worktree/dirs.test.js` cobra cada leitor |
171
+ | `src/lib/ai/registry-audit.js` | o leitor de PRODUÇÃO do mesmo documento: `framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs` (`CommentHandling = Skip`, `AllowTrailingCommas`, `AllowedAliasProperties` com `pricing`/`pricingWaiver`) e o exemplar `model-registry.v2.json` que o framework manda COPIAR. A auditoria tem de enxergar a **mesma forma** que o leitor de produção enxerga: com `JSON.parse` estrito, duas das cinco checagens saíam `skip` sobre o exemplar canônico — e `skip` não muda exit code, então o silêncio era total. A tolerância a JSONC vive em `src/lib/ai/comment-stripper.js`, ÚNICA (o `code-audit.js` usa o mesmo varredor, e o `code-budget` também, o único com a opção `js` — template e regex literal de TS/JS; sem ela o varredor é o de antes) — e a paridade é nos DOIS sentidos: `jsoncToJson` **lança** no comentário de bloco não fechado, porque o `JsonCommentHandling.Skip` do .NET lança, e ser mais frouxo que o kit é aprovar documento que a app recusa carregar. Ao mascarar strings, **as aspas delimitadoras ficam**: apagá-las junto faz um elemento-string virar espaço puro e o detector de vírgula sobrando comer um SEPARADOR real (`{"tags":["a","b"]}` saía inválido). Canário obrigatório: array terminado em **string**, com e sem vírgula sobrando — uma suíte só com arrays de número passa dos dois lados dessa inversão. A **REGRA DURA** do exemplar — *todo alias declara `pricing` OU `pricingWaiver`, nunca os dois, nunca nenhum* — é do alias, não de um mapa no topo: ler só o mapa dava 6 falsos positivos sobre um documento correto. O canário **copia o exemplar do disco**, nunca uma paráfrase dele |
172
+ | `src/lib/worktree/dirs.js` | ÚNICA fonte do nome do container (`worktrees/`, sem ponto — Jest esconde diretório com ponto e reporta 0 testes sem erro). `worktree.js` re-exporta; `graphifyignore.js`, `file-copier.js`, `architecture-scanner.js`, `test-runner.js`, `worktree-audit.js`, os scanners do dashboard (`features-scanner.js`, `project-api.js`), `tasks/linux-runner.js` (exclusões do `tar` do `--linux`) importam. `test/lib/worktree/dirs.test.js` cobra cada leitor |
173
+ | `src/dashboard/**` (páginas e scanners) | `test/dashboard/pages-smoke.test.js` renderiza as 7 rotas e os painéis de detalhe pela config Vite do dashboard com os dados REAIS do scanner — build verde não prova nada (8.38.0 saiu com `ReferenceError` na rota inicial). Campo que a página lê tem de sair do scanner: `activePhases` é DERIVADO das linhas de dispatch das skills de fase (`personaPhases`, com `isImplementer`), nunca uma lista à mão. Edge do React Flow só existe no cliente (chave duplicada não aparece no SSR — o dedupe de `standardRefs` mora no scanner). `src/dashboard/.gitignore` é o que mantém `node_modules` fora do tarball (`test/dashboard/packaging.test.js`). `scanner/feature-status.js` (`isFinished`) é a regra ÚNICA de "feature encerrada" do servidor (portfólio, `awaiting`, `taskIndex`) e do cliente (lista e quadro) — arquivada conta como encerrada seja qual for o `status` legado. O plugin engole o `full-reload` do Vite para `.html` sob `.morph/` (`isMorphDataReload`): `handleHotUpdate` devolvendo `[]` NÃO segura `.html` no Vite 6, e um agente gravando um mockup recarregava o dashboard. `resolveFeatureTree` (git, ~0,3 s por chamada) é lembrado por feature no `project-api.js` e só é esquecido por evento estrutural (`add`/`unlink`/`addDir`/`unlinkDir`) ou TTL de 60 s — um scan síncrono trava o event loop do dev server inteiro |
174
+ | `src/lib/projects/registry.js` (projetos da máquina, `~/.morph-spec/projects.json`) | os TRÊS escritores automáticos — `init`, `update` (nunca sob `--dry-run`) e `dashboard` — mais o add/remove do portfólio (`src/dashboard/scanner/portfolio.js`). Registrar é fail-open: registro ilegível ou não gravável nunca derruba o comando. Nunca registra pasta sob o temp do sistema (as suítes rodam `init`/`update` ali às centenas, inclusive por CLI em spawn que herda o home real) nem checkout de worktree (`WORKTREE_DIR_NAMES`). Teste que registra usa `MORPH_PROJECTS_REGISTRY` apontando para um temp, ou o seam `tempRoot` — nenhuma suíte escreve o `~/.morph-spec` de quem roda. Leitura poda projeto cuja pasta sumiu; descoberta é UM nível abaixo das pastas-mãe e só sugere, nunca adiciona |
151
175
 
152
176
  ### Test Coverage
153
177
 
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  > Spec-driven development framework for multi-stack projects. Turns feature requests into implementation-ready code through structured, AI-orchestrated phases.
4
4
 
5
5
  **Package:** `@polymorphism-tech/morph-spec`
6
- **Version:** 8.37.1
6
+ **Version:** 8.40.0
7
7
  **Requires:** Node.js 22+, Claude Code
8
8
 
9
9
  ---
@@ -379,4 +379,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
379
379
 
380
380
  ---
381
381
 
382
- *morph-spec v8.37.1 by [Polymorphism Tech](https://polymorphism.tech)*
382
+ *morph-spec v8.40.0 by [Polymorphism Tech](https://polymorphism.tech)*
package/bin/morph-spec.js CHANGED
@@ -16,66 +16,83 @@ import { dirname, join } from 'path';
16
16
  import { readFileSync } from 'fs';
17
17
  import { renderLogo, maybePrintFirstRunBanner } from '../src/utils/banner.js';
18
18
 
19
+ // Command modules load on demand. Importing all ~45 of them eagerly cost
20
+ // ~700 ms on EVERY invocation (init.js alone pulls inquirer: ~550 ms), paid
21
+ // by each hook that shells out to the CLI and by each CLI spawn in the suite.
22
+ // test/bin/command-modules-import.test.js imports every path below, so a module
23
+ // that breaks at import time still fails the suite, not only its own command.
24
+ const lazy = (path, name) => async (...args) => (await import(path))[name](...args);
25
+
19
26
  // === Project / install ===
20
- import { initCommand } from '../src/commands/project/init.js';
21
- import { setupInfraCommand } from '../src/commands/project/setup-infra-cmd.js';
22
- import { installPluginCommand } from '../src/commands/project/install-plugin-cmd.js';
23
- import { updateCommand } from '../src/commands/project/update.js';
24
- import { doctorCommand } from '../src/commands/project/doctor.js';
25
- import { statusCommand } from '../src/commands/project/status.js';
26
- import { worktreeSetupCommand, worktreeProvisionCommand, worktreeLinkCommand, worktreeListCommand, worktreeRemoveCommand } from '../src/commands/project/worktree.js';
27
- import { branchPruneCommand } from '../src/commands/project/branch.js';
28
- import { fleetCommand } from '../src/commands/project/fleet.js';
29
- import { envCommand } from '../src/commands/project/env.js';
30
- import { claimCommand } from '../src/commands/project/claim.js';
31
- import { standardsCommand } from '../src/commands/project/standards.js';
32
- import { reportCheckCommand } from '../src/commands/state/report-check.js';
27
+ const initCommand = lazy('../src/commands/project/init.js', 'initCommand');
28
+ const setupInfraCommand = lazy('../src/commands/project/setup-infra-cmd.js', 'setupInfraCommand');
29
+ const installPluginCommand = lazy('../src/commands/project/install-plugin-cmd.js', 'installPluginCommand');
30
+ const updateCommand = lazy('../src/commands/project/update.js', 'updateCommand');
31
+ const doctorCommand = lazy('../src/commands/project/doctor.js', 'doctorCommand');
32
+ const statusCommand = lazy('../src/commands/project/status.js', 'statusCommand');
33
+ const worktreeSetupCommand = lazy('../src/commands/project/worktree.js', 'worktreeSetupCommand');
34
+ const worktreeProvisionCommand = lazy('../src/commands/project/worktree.js', 'worktreeProvisionCommand');
35
+ const worktreeLinkCommand = lazy('../src/commands/project/worktree.js', 'worktreeLinkCommand');
36
+ const worktreeListCommand = lazy('../src/commands/project/worktree.js', 'worktreeListCommand');
37
+ const worktreeRemoveCommand = lazy('../src/commands/project/worktree.js', 'worktreeRemoveCommand');
38
+ const branchPruneCommand = lazy('../src/commands/project/branch.js', 'branchPruneCommand');
39
+ const fleetCommand = lazy('../src/commands/project/fleet.js', 'fleetCommand');
40
+ const envCommand = lazy('../src/commands/project/env.js', 'envCommand');
41
+ const claimCommand = lazy('../src/commands/project/claim.js', 'claimCommand');
42
+ const standardsCommand = lazy('../src/commands/project/standards.js', 'standardsCommand');
43
+ const reportCheckCommand = lazy('../src/commands/state/report-check.js', 'reportCheckCommand');
33
44
 
34
45
  // === Approval gates (human-only) ===
35
- import { approveCommand, approvalStatusCommand, unapproveCommand } from '../src/commands/state/approve.js';
36
- import { advanceCommand } from '../src/commands/state/advance.js';
37
- import { archiveCommand } from '../src/commands/state/archive.js';
38
- import { finishCommand } from '../src/commands/state/finish.js';
39
- import { scoreCommand } from '../src/commands/state/score.js';
40
- import { gateDecisionCommand } from '../src/commands/state/gate-decision.js';
41
- import { gateCheckCommand } from '../src/commands/state/gate-check.js';
42
- import { createCommand } from '../src/commands/state/create.js';
43
- import { retypeCommand } from '../src/commands/state/retype.js';
44
- import { handoffCommand } from '../src/commands/state/handoff.js';
45
- import { listCommand } from '../src/commands/state/list.js';
46
- import { deleteCommand } from '../src/commands/state/delete.js';
47
- import { usecaseReserveCommand } from '../src/commands/state/usecase.js';
46
+ const approveCommand = lazy('../src/commands/state/approve.js', 'approveCommand');
47
+ const approvalStatusCommand = lazy('../src/commands/state/approve.js', 'approvalStatusCommand');
48
+ const unapproveCommand = lazy('../src/commands/state/approve.js', 'unapproveCommand');
49
+ const advanceCommand = lazy('../src/commands/state/advance.js', 'advanceCommand');
50
+ const archiveCommand = lazy('../src/commands/state/archive.js', 'archiveCommand');
51
+ const finishCommand = lazy('../src/commands/state/finish.js', 'finishCommand');
52
+ const scoreCommand = lazy('../src/commands/state/score.js', 'scoreCommand');
53
+ const gateDecisionCommand = lazy('../src/commands/state/gate-decision.js', 'gateDecisionCommand');
54
+ const gateCheckCommand = lazy('../src/commands/state/gate-check.js', 'gateCheckCommand');
55
+ const createCommand = lazy('../src/commands/state/create.js', 'createCommand');
56
+ const retypeCommand = lazy('../src/commands/state/retype.js', 'retypeCommand');
57
+ const handoffCommand = lazy('../src/commands/state/handoff.js', 'handoffCommand');
58
+ const listCommand = lazy('../src/commands/state/list.js', 'listCommand');
59
+ const deleteCommand = lazy('../src/commands/state/delete.js', 'deleteCommand');
60
+ const usecaseReserveCommand = lazy('../src/commands/state/usecase.js', 'usecaseReserveCommand');
61
+ const usecaseCheckCommand = lazy('../src/commands/state/usecase.js', 'usecaseCheckCommand');
48
62
 
49
63
  // === Tasks ===
50
- import { dagCommand } from '../src/commands/tasks/dag.js';
64
+ const dagCommand = lazy('../src/commands/tasks/dag.js', 'dagCommand');
51
65
 
52
66
  // === Codebase graph ===
53
- import { graphStatusCommand } from '../src/commands/graph/status.js';
54
- import { graphBuildCommand } from '../src/commands/graph/build.js';
55
- import { graphAffectedCommand } from '../src/commands/graph/affected.js';
56
- import { graphExplainCommand } from '../src/commands/graph/explain.js';
67
+ const graphStatusCommand = lazy('../src/commands/graph/status.js', 'graphStatusCommand');
68
+ const graphBuildCommand = lazy('../src/commands/graph/build.js', 'graphBuildCommand');
69
+ const graphAffectedCommand = lazy('../src/commands/graph/affected.js', 'graphAffectedCommand');
70
+ const graphExplainCommand = lazy('../src/commands/graph/explain.js', 'graphExplainCommand');
57
71
 
58
72
  // === Validation ===
59
- import { validateCommand } from './validate.js';
60
- import { validateFeatureCommand } from '../src/commands/validation/validate-feature.js';
61
- import { verifyCommand } from '../src/commands/validation/verify.js';
62
- import { e2eUpCommand, e2eStatusCommand, e2eDownCommand } from '../src/commands/e2e/e2e.js';
73
+ const validateCommand = lazy('./validate.js', 'validateCommand');
74
+ const validateFeatureCommand = lazy('../src/commands/validation/validate-feature.js', 'validateFeatureCommand');
75
+ const verifyCommand = lazy('../src/commands/validation/verify.js', 'verifyCommand');
76
+ const e2eUpCommand = lazy('../src/commands/e2e/e2e.js', 'e2eUpCommand');
77
+ const e2eStatusCommand = lazy('../src/commands/e2e/e2e.js', 'e2eStatusCommand');
78
+ const e2eDownCommand = lazy('../src/commands/e2e/e2e.js', 'e2eDownCommand');
63
79
 
64
80
  // === Scaffolding ===
65
- import { scaffoldDotnetCommand, scaffoldNextjsCommand } from '../src/commands/scaffold/scaffold.js';
81
+ const scaffoldDotnetCommand = lazy('../src/commands/scaffold/scaffold.js', 'scaffoldDotnetCommand');
82
+ const scaffoldNextjsCommand = lazy('../src/commands/scaffold/scaffold.js', 'scaffoldNextjsCommand');
66
83
 
67
84
  // === MCP setup ===
68
- import { mcpSetupCommand } from '../src/commands/mcp/mcp-setup.js';
85
+ const mcpSetupCommand = lazy('../src/commands/mcp/mcp-setup.js', 'mcpSetupCommand');
69
86
 
70
87
  // === Context refresh ===
71
- import { contextRefreshCommand } from '../src/commands/context/context-refresh.js';
88
+ const contextRefreshCommand = lazy('../src/commands/context/context-refresh.js', 'contextRefreshCommand');
72
89
 
73
90
  // === Dashboard ===
74
- import { dashboardCommand } from '../src/commands/dashboard/dashboard.js';
91
+ const dashboardCommand = lazy('../src/commands/dashboard/dashboard.js', 'dashboardCommand');
75
92
 
76
93
  // === Observability ===
77
- import { telemetryCommand } from '../src/commands/observability/telemetry.js';
78
- import { costCommand } from '../src/commands/observability/cost.js';
94
+ const telemetryCommand = lazy('../src/commands/observability/telemetry.js', 'telemetryCommand');
95
+ const costCommand = lazy('../src/commands/observability/cost.js', 'costCommand');
79
96
 
80
97
  const __dirname = dirname(fileURLToPath(import.meta.url));
81
98
  const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
@@ -118,7 +135,7 @@ program
118
135
 
119
136
  program
120
137
  .command('install-plugin <name>')
121
- .description('Install a Claude Code plugin (superpowers, context7)')
138
+ .description('Install a Claude Code plugin (context7)')
122
139
  .action(installPluginCommand);
123
140
 
124
141
  program
@@ -137,7 +154,7 @@ program
137
154
  .option('--mcp', 'Check MCP server configuration')
138
155
  .option('--ai', 'Auditar um projeto .NET com agentes: pin × projeto, API renomeada, tarifa e `api` por alias, prompt sem fonte')
139
156
  .option('--worktrees', "Reconcile git's worktree registry × the directories on disk × known features")
140
- .option('--prune', "With --worktrees: drop git records for worktree directories that are already gone, and clear any feature's root worktree registration pointing at one of them")
157
+ .option('--prune', "With --worktrees: drop git records for worktree directories that are already gone, clear any feature's root worktree registration pointing at one of them, and apply the finish-backup retention (.git/morph-spec/finish-backup) — the only path that also prunes backups written before it (no manifest)")
141
158
  .option('--json', 'With --worktrees: output as JSON')
142
159
  .option('--reset', 'Remove morph-managed entries from .claude/settings.local.json')
143
160
  .action(doctorCommand);
@@ -285,7 +302,7 @@ program
285
302
  // ─── Use cases ────────────────────────────────────────────────────────────
286
303
  const usecaseCommand = program
287
304
  .command('usecase')
288
- .description('Project-wide UC-{nn} numbering (acervo + every worktree of this repo)');
305
+ .description('Project-wide use cases: UC-{nn} numbering (acervo + every worktree of this repo) and the acervo lifecycle check');
289
306
 
290
307
  usecaseCommand
291
308
  .command('reserve')
@@ -295,6 +312,12 @@ usecaseCommand
295
312
  .option('--json', 'Output as JSON')
296
313
  .action((options) => usecaseReserveCommand(options));
297
314
 
315
+ usecaseCommand
316
+ .command('check <feature>')
317
+ .description('Check a feature against the acervo lifecycle: tasks citing a superseded UC, malformed supersession frontmatter, spec.md revocation rows the finish could not apply (plus the task/use-case plumbing dag checks, when tasks.json exists)')
318
+ .option('--json', 'Output as JSON')
319
+ .action((feature, options) => usecaseCheckCommand(feature, options));
320
+
298
321
  // ─── Tasks ────────────────────────────────────────────────────────────────
299
322
  program
300
323
  .command('dag <feature>')
@@ -568,10 +591,11 @@ program
568
591
 
569
592
  // ─── Sub-agent report protocol ────────────────────────────────────────────
570
593
  program
571
- .command('report-check <feature> [task]')
572
- .description('Audit sub-agent report files against the work that actually landed. Answers "did the WORK land even though the REPORT did not" — exits 0 only when everything is intact')
594
+ .command('report-check <feature> [tasks...]')
595
+ .description('Audit sub-agent report files against the work that actually landed. Answers "did the WORK land even though the REPORT did not" — exits 0 only when everything is intact. Several task ids audit a parallel batch in one call')
596
+ .option('--before-eval', 'Save a snapshot of the tree before dispatching the evaluator; later runs report what changed since')
573
597
  .option('--json', 'Output as JSON')
574
- .action((feature, task, options) => reportCheckCommand(feature, task, options));
598
+ .action((feature, tasks, options) => reportCheckCommand(feature, tasks.length > 1 ? tasks : tasks[0], options));
575
599
 
576
600
  // ─── Dashboard ────────────────────────────────────────────────────────────
577
601
  program
@@ -594,8 +618,10 @@ program
594
618
  .command('cost [feature]')
595
619
  .description('Cost-facing view over .morph/logs/events.jsonl: task duration, bytes re-ingested by kind (proxy, not tokens), dispatch counts')
596
620
  .option('--by <mode>', 'Group by: task|phase (default: feature-wide summary; with --all-trees, applies to the combined aggregate)')
597
- .option('--all-trees', 'Aggregate .morph/logs/events.jsonl across every git worktree that has one, not just the current tree — per-tree breakdown + combined total')
621
+ .option('--all-trees', 'Aggregate across every git worktree with a log plus the logs finish saved in .git/morph-spec/finish-backup — per-tree breakdown + combined total')
622
+ .option('--metrics', 'Loop measurements: turns, tool calls (orchestrator x sub-agent), time per command class, identical reruns, rereads, feature verify count, git per task, reuse validator per tree')
623
+ .option('--top <n>', 'With --metrics: how many reread files to list (default 10)', (v) => Number.parseInt(v, 10))
598
624
  .option('--json', 'Output the aggregate as JSON')
599
625
  .action((feature, options) => costCommand(feature, options));
600
626
 
601
- program.parse();
627
+ await program.parseAsync();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morph-spec",
3
- "version": "8.37.1",
3
+ "version": "8.40.0",
4
4
  "displayName": "MORPH-SPEC Framework",
5
5
  "description": "Spec-driven development with 38 agents and 8-phase workflow for .NET/Blazor/Next.js/Azure",
6
6
  "publisher": "polymorphism-tech",
@@ -187,4 +187,4 @@ Never edited directly — go through CLI commands or `morph-spec update` instead
187
187
 
188
188
  ---
189
189
 
190
- *morph-spec v8.37.1 by Polymorphism Tech*
190
+ *morph-spec v8.40.0 by Polymorphism Tech*
@@ -249,4 +249,4 @@ morph-spec doctor
249
249
 
250
250
  ---
251
251
 
252
- *morph-spec v8.37.1 by Polymorphism Tech*
252
+ *morph-spec v8.40.0 by Polymorphism Tech*
@@ -17,9 +17,10 @@ Se não houver feature ativa, pergunte ao usuário qual feature trabalhar.
17
17
  - Pular para código sem proposal aprovado (Gate 1) e plano aprovado (Gate 2).
18
18
  - Modificar o contrato do plano depois do Gate 2 — `spec.md`, `mandate.md`, e os campos `title`/`description`/`dependencies`/`effort`/`doneCriteria`/`group`/`agent`/`standards` de `tasks.json` ficam congelados (`protect-spec-files` bloqueia `Write`/`Edit`); mutáveis: `status`/`outputs`/`notes` (ver SEMPRE). **Fato errado sobre o código não exige revogar gate**: bloco `## Errata` append-only no fim, corpo intacto (MORPH.md §3). **Use cases (`2-plan/usecases/UC-*.md`) NÃO congelam** — editáveis por design.
19
19
  - Escrever critérios de aceite no `proposal.md` — pertencem aos use cases (pós-condição **observável em teste**, chega **literal** ao `doneCriteria`).
20
- - Editar `.morph/specs/usecases/` à mão — só o `finish` escreve lá (lê de `2-plan/usecases/`, carimba `status: implemented`, sobrescreve por ID). Mudar um UC existente (`bug`): copie para `2-plan/usecases/` da feature e corrija lá.
20
+ - Editar `.morph/specs/usecases/` à mão — só o `finish` escreve lá. Mudar um UC existente (`bug`): copie para `2-plan/usecases/` da feature e corrija lá.
21
21
  - Listar perguntas em texto simples — sempre `AskUserQuestion` (1-4 perguntas por chamada).
22
22
  - Usar `Guid.NewGuid()`, classes não-sealed sem justificativa, exceptions como fluxo de controle, camada de Service/Manager entre handler e dados, repository genérico (`IRepository<T>`/`IUnitOfWork`), ou SQL cru sem justificativa.
23
+ - Comentar o que nome, tipo ou teste já dizem: o padrão é nenhum comentário (`architecture-code-comments`).
23
24
  - Editar `taskScores`, `gateDecisions` ou gates de `feature.json` à mão — sempre via `morph-spec score`/`gate-check`/`approve` (timestamps server-side; Edit manual colide com o hook `state-sync`).
24
25
 
25
26
  **SEMPRE:**
@@ -66,14 +67,14 @@ Se não houver feature ativa, pergunte ao usuário qual feature trabalhar.
66
67
  | `fleet` | Visão agregada de toda feature ativa (raiz + worktrees) |
67
68
  | `claim [f] [--release] [--list]` | Posse advisory — confirme antes de `finish`/`archive`/`delete` se outra sessão tiver claim vivo |
68
69
  | `dag <f>` | DAG de `tasks.json`: sequencial/paralelo, recomenda isolamento |
69
- | `graph status\|build\|refresh` | Grafo de codebase. `status` reporta frescor: o rebuild do hook é assíncrono |
70
+ | `graph status\|build\|refresh` | Grafo de codebase. `status` reporta frescor e se há refresh em background |
70
71
  | `graph affected\|explain <sym>` | Blast radius e vizinhança saneados. Sem grafo, caia para grep — nunca é erro |
71
72
  | `verify <f> [task] [--json]` | Build+testes+validadores+e2e, antes do juiz LLM |
72
73
  | `e2e up\|status\|down <f>` | Stack de verificação (docker compose) |
73
74
  | `doctor --worktrees [--prune]` | Reconcilia git × disco × features; só `--prune` remove algo |
74
- | `report-check <f> [task] [--json]` | Audita relatório de sub-agent vs. trabalho entregue |
75
+ | `report-check <f> [task...] [--json]` | Audita relatório de sub-agent vs. trabalho entregue |
75
76
  | `telemetry [--feature] [--limit]` | Caminho de execução da sessão a partir de `events.jsonl` |
76
- | `cost [f] [--by task\|phase]` | Custo (bytes/duração) a partir do mesmo log |
77
+ | `cost [f] [--by task\|phase] [--metrics]` | Custo (bytes/duração) e medições do loop (turnos, rerun, releitura) do mesmo log |
77
78
 
78
79
  Prefixo de todo comando: `morph-spec`. Flags/exemplos: `.morph/framework/CLI.md`.
79
80
 
@@ -109,6 +110,6 @@ Drift entre `agents.json` e os `.md` gerados é detectado por `morph-spec doctor
109
110
 
110
111
  ## Regra de precedência de skills
111
112
 
112
- Prefira `morph-*` a `superpowers:*` equivalentes (`brainstorming`→`morph-brainstorming`, `writing-plans`→`morph-plan`, `executing-plans`→`morph-apply`, `test-driven-development`→seguir `mandate.md`, TDD embutido, `using-git-worktrees`→`morph-spec worktree setup|provision`): `morph-*` aplica Gates e paths corretos de `.morph/`; `superpowers:*` é genérico, sem esse contexto.
113
+ Com o plugin superpowers ativo, `morph-*` vence o equivalente genérico (aplica Gates e paths de `.morph/`), e `using-git-worktrees`→`morph-spec worktree setup|provision`.
113
114
 
114
115
  *MORPH-SPEC v5 by Polymorphism Tech*