@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
@@ -33,9 +33,9 @@
33
33
  * Fail-open like the promoter: no `usecases/` folder means nothing to check.
34
34
  */
35
35
 
36
- import { existsSync, readdirSync, readFileSync } from 'fs';
36
+ import { readFileSync } from 'fs';
37
37
  import { join } from 'path';
38
- import { useCaseId } from './usecase-promoter.js';
38
+ import { isCanonicalUseCaseName, useCaseFilesById } from './acervo-format.js';
39
39
 
40
40
  /**
41
41
  * A post-condition line: `- **P2** — Cada veterinário traz suas especialidades`.
@@ -180,23 +180,18 @@ export function parsePostConditions(content) {
180
180
  * continuation); `postConditions[].text` stays the first line, which is what
181
181
  * the literal-carry check compares against.
182
182
  *
183
+ * Any `UC-{nn}-….md` counts, canonical or not — installed plans rely on it.
184
+ * `nonCanonical` lists the files of that id the promotion will not carry, so
185
+ * `checkUseCases` can say so instead of the finish dropping them in silence.
186
+ *
183
187
  * @param {string} usecasesDir - absolute path to `2-plan/usecases`
184
- * @returns {Map<string, { id: string, file: string, postConditions: Array<{label: string, text: string}>, artifactJustified: Map<string, string>, fullText: Map<string, string> }>}
188
+ * @returns {Map<string, { id: string, file: string, nonCanonical: string[], postConditions: Array<{label: string, text: string}>, artifactJustified: Map<string, string>, fullText: Map<string, string> }>}
185
189
  */
186
190
  export function loadUseCases(usecasesDir) {
187
191
  const byId = new Map();
188
- if (!existsSync(usecasesDir)) return byId;
189
-
190
- let names;
191
- try {
192
- names = readdirSync(usecasesDir).sort();
193
- } catch {
194
- return byId; // unreadable folder is "no use cases", never a crash
195
- }
196
-
197
- for (const file of names) {
198
- const id = useCaseId(file);
199
- if (!id || !file.endsWith('.md')) continue;
192
+ for (const [id, files] of useCaseFilesById(usecasesDir, { canonicalOnly: false })) {
193
+ const file = files[files.length - 1];
194
+ const nonCanonical = files.filter((f) => !isCanonicalUseCaseName(f));
200
195
  try {
201
196
  const content = readFileSync(join(usecasesDir, file), 'utf8');
202
197
  const blocks = parsePostConditionBlocks(content);
@@ -206,7 +201,7 @@ export function loadUseCases(usecasesDir) {
206
201
  fullText.set(label, block.text);
207
202
  if (block.justified) artifactJustified.set(label, block.text);
208
203
  }
209
- byId.set(id, { id, file, postConditions: parsePostConditions(content), artifactJustified, fullText });
204
+ byId.set(id, { id, file, nonCanonical, postConditions: parsePostConditions(content), artifactJustified, fullText });
210
205
  } catch { /* one unreadable use case must not sink the rest */ }
211
206
  }
212
207
  return byId;
@@ -241,6 +236,15 @@ export function checkUseCases(tasksData, usecasesDir) {
241
236
 
242
237
  if (byId.size === 0) return { errors, warnings, checked: false };
243
238
 
239
+ for (const uc of byId.values()) {
240
+ for (const file of uc.nonCanonical) {
241
+ warnings.push(
242
+ `${uc.id} está em 2-plan/usecases/${file}, nome fora do padrão UC-{nn}-{slug}.md: ` +
243
+ 'o finish não o promove ao acervo — renomeie'
244
+ );
245
+ }
246
+ }
247
+
244
248
  // Pre-normalize every doneCriteria once: this is O(tasks × postConditions)
245
249
  // and both are small, but the normalization itself is not free.
246
250
  const criteria = tasks.map((t) => ({ id: t?.id, text: normalize(t?.doneCriteria || ''), usecase: t?.usecase }));
@@ -30,22 +30,26 @@
30
30
  * rather than silently destroy the other feature's use case.
31
31
  */
32
32
 
33
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, rmSync } from 'fs';
33
+ import { mkdirSync, readFileSync, writeFileSync, rmSync } from 'fs';
34
34
  import { basename, join, relative } from 'path';
35
35
  import { OUTPUT_SCHEMA } from '../../core/paths/output-schema.js';
36
36
  import { resolveGitCommonDir } from '../../../framework/hooks/shared/git-paths.js';
37
37
  import { reservationOwner } from './usecase-registry.js';
38
+ import { SUPERSEDED, useCaseFilesById, useCaseId } from './acervo-format.js';
39
+
40
+ export { useCaseId };
38
41
 
39
42
  const UC = OUTPUT_SCHEMA.usecases;
40
- const UC_FILENAME_RE = new RegExp(UC.filenamePattern);
41
43
 
42
44
  /** Project-relative path of the permanent use-case archive (git pathspec-ready). */
43
45
  export const SPECS_USECASES_PATHSPEC = join('.morph', 'specs', 'usecases');
44
46
 
45
- /** `UC-07-listar-veterinarios.md` → `UC-07`. Null when the name isn't a use case. */
46
- export function useCaseId(filename) {
47
- const m = /^(UC-\d+)-/.exec(filename);
48
- return m ? m[1] : null;
47
+ const FRONTMATTER_RE = /^(?---\r?\n)([\s\S]*?)(\r?\n---(?:\r?\n|$))/;
48
+
49
+ /** `true` when the document opens with a frontmatter block. */
50
+ export function hasFrontmatter(content) {
51
+ const m = FRONTMATTER_RE.exec(content);
52
+ return !!m && m.index === 0;
49
53
  }
50
54
 
51
55
  /**
@@ -58,33 +62,70 @@ export function useCaseId(filename) {
58
62
  *
59
63
  * @param {string} content
60
64
  * @param {string} key
61
- * @param {string} value
65
+ * @param {string} value - written as-is; quote it first when it is free text
62
66
  * @returns {string}
63
67
  */
64
- function setFrontmatterField(content, key, value) {
65
- const m = /^(?---\r?\n)([\s\S]*?)(\r?\n---(?:\r?\n|$))/.exec(content);
68
+ export function setFrontmatterField(content, key, value) {
69
+ const m = FRONTMATTER_RE.exec(content);
66
70
  if (!m || m.index !== 0) return content;
67
71
 
68
72
  const [full, open, body, close] = m;
69
73
  const eol = full.includes('\r\n') ? '\r\n' : '\n';
70
74
  const fieldLine = new RegExp(`^${key}:[ \\t]*.*$`, 'm');
75
+ // Function replacer: a value carrying `$&` or `$1` is text, not a pattern.
71
76
  const next = fieldLine.test(body)
72
- ? body.replace(fieldLine, `${key}: ${value}`)
77
+ ? body.replace(fieldLine, () => `${key}: ${value}`)
73
78
  : `${body}${eol}${key}: ${value}`;
74
79
 
75
80
  return open + next + close + content.slice(full.length);
76
81
  }
77
82
 
78
- /** Read a single top-level frontmatter field. `null` when absent or there is no frontmatter block. */
79
- function readFrontmatterField(content, key) {
80
- const m = /^(?---\r?\n)([\s\S]*?)(\r?\n---(?:\r?\n|$))/.exec(content);
83
+ /**
84
+ * Drop a single top-level frontmatter field (its whole line, EOL included).
85
+ * Absent field or no frontmatter → content unchanged.
86
+ *
87
+ * @param {string} content
88
+ * @param {string} key
89
+ * @returns {string}
90
+ */
91
+ export function removeFrontmatterField(content, key) {
92
+ const m = FRONTMATTER_RE.exec(content);
93
+ if (!m || m.index !== 0) return content;
94
+
95
+ const [full, open, body, close] = m;
96
+ const lines = body.split(/(?<=\n)/);
97
+ const kept = lines.filter((line) => !new RegExp(`^${key}:`).test(line));
98
+ if (kept.length === lines.length) return content;
99
+ // The last kept line lost its EOL when the removed one was the final line;
100
+ // the closing delimiter carries its own leading EOL, so trim the dangling one.
101
+ const next = kept.join('').replace(/\r?\n$/, '');
102
+ return open + next + close + content.slice(full.length);
103
+ }
104
+
105
+ /**
106
+ * Read a single top-level frontmatter field. `null` when absent or there is no
107
+ * frontmatter block. A double-quoted value is read as the JSON string it is
108
+ * written as (that is how free text such as `scope:` is stored); a
109
+ * single-quoted one as YAML reads it (`''` is a quote).
110
+ */
111
+ export function readFrontmatterField(content, key) {
112
+ const m = FRONTMATTER_RE.exec(content);
81
113
  if (!m || m.index !== 0) return null;
82
114
  const line = new RegExp(`^${key}:[ \\t]*(.+?)[ \\t]*$`, 'm').exec(m[2]);
83
- return line ? line[1].trim() : null;
115
+ if (!line) return null;
116
+ const raw = line[1].trim();
117
+ if (raw.length >= 2 && raw.startsWith('"') && raw.endsWith('"')) {
118
+ try { return JSON.parse(raw); } catch { return raw; }
119
+ }
120
+ if (raw.length >= 2 && raw.startsWith("'") && raw.endsWith("'")) return raw.slice(1, -1).replace(/''/g, "'");
121
+ return raw;
84
122
  }
85
123
 
86
124
  /**
87
- * Rewrite the frontmatter `status:` to `implemented`.
125
+ * Rewrite the frontmatter `status:` to `implemented` — unless the use case is
126
+ * superseded (its status says so, or it carries a `superseded_by`): a
127
+ * promotion, such as a `bug` fix on the part that still holds, must never
128
+ * hand the rest of it back its authority.
88
129
  *
89
130
  * A file with no frontmatter is copied verbatim: the use case belongs to the
90
131
  * author, and inventing a header would be a silent edit of their document.
@@ -93,7 +134,32 @@ function readFrontmatterField(content, key) {
93
134
  * @returns {string}
94
135
  */
95
136
  export function stampImplemented(content) {
96
- return setFrontmatterField(content, 'status', 'implemented');
137
+ const superseded = readFrontmatterField(content, 'status') === SUPERSEDED
138
+ || readFrontmatterField(content, 'superseded_by') !== null;
139
+ return setFrontmatterField(content, 'status', superseded ? SUPERSEDED : 'implemented');
140
+ }
141
+
142
+ /**
143
+ * Carry the acervo's supersession onto an incoming copy that does not declare
144
+ * one — a copy taken before the revocation landed, or stripped of the fields
145
+ * on the way. Without this the promotion would publish the old use case as
146
+ * `implemented` again.
147
+ *
148
+ * @param {string} content - the incoming copy
149
+ * @param {string|null} previous - the acervo entry it replaces
150
+ * @returns {{ content: string, supersededBy: string|null, scope: string|null }|null} null = nothing to carry
151
+ */
152
+ function inheritSupersession(content, previous) {
153
+ if (!previous || readFrontmatterField(previous, 'status') !== SUPERSEDED) return null;
154
+ if (readFrontmatterField(content, 'status') === SUPERSEDED || readFrontmatterField(content, 'superseded_by') !== null) return null;
155
+ if (!hasFrontmatter(content)) return null;
156
+
157
+ const supersededBy = readFrontmatterField(previous, 'superseded_by');
158
+ const scope = readFrontmatterField(previous, 'scope');
159
+ let next = setFrontmatterField(content, 'status', SUPERSEDED);
160
+ if (supersededBy) next = setFrontmatterField(next, 'superseded_by', supersededBy);
161
+ if (scope !== null) next = setFrontmatterField(next, 'scope', JSON.stringify(scope));
162
+ return { content: next, supersededBy, scope };
97
163
  }
98
164
 
99
165
  /**
@@ -138,27 +204,24 @@ function collisionMessage({ id, incoming, existing, existingFeature, feature, re
138
204
  * @param {string} cwd - project root that owns `.morph/specs/`
139
205
  * @param {string} featureDir - the feature folder holding `2-plan/usecases/`
140
206
  * @returns {{ promoted: string[], replaced: string[], refused: Array<{id: string,
141
- * incoming: string, existing: string, existingFeature: string, message: string}> }}
142
- * filenames, always arrays
207
+ * incoming: string, existing: string, existingFeature: string, message: string}>,
208
+ * keptSuperseded: Array<{id: string, file: string, supersededBy: string|null, scope?: string}> }}
209
+ * always arrays; `keptSuperseded` = promoted copies that stayed superseded
210
+ * because the acervo entry they replaced was
143
211
  */
144
212
  export function promoteUseCases(cwd, featureDir) {
145
- const empty = { promoted: [], replaced: [], refused: [] };
213
+ const empty = { promoted: [], replaced: [], refused: [], keptSuperseded: [] };
146
214
  const srcDir = join(featureDir, UC.phaseDir, UC.dirname);
147
215
  const feature = basename(featureDir);
148
216
 
149
- let names;
150
- try {
151
- if (!existsSync(srcDir)) return empty;
152
- names = readdirSync(srcDir).filter((n) => UC_FILENAME_RE.test(n)).sort();
153
- } catch {
154
- return empty;
155
- }
217
+ const names = [...useCaseFilesById(srcDir).values()].flat().sort();
156
218
  if (!names.length) return empty;
157
219
 
158
220
  const destDir = join(cwd, '.morph', 'specs', 'usecases');
159
221
  const promoted = [];
160
222
  const replaced = [];
161
223
  const refused = [];
224
+ const keptSuperseded = [];
162
225
 
163
226
  try {
164
227
  mkdirSync(destDir, { recursive: true });
@@ -173,18 +236,16 @@ export function promoteUseCases(cwd, featureDir) {
173
236
 
174
237
  // Existing archive entries, indexed by use-case ID — the key the overwrite
175
238
  // rule is written against.
176
- const existingById = new Map();
177
- try {
178
- for (const name of readdirSync(destDir)) {
179
- const id = UC_FILENAME_RE.test(name) ? useCaseId(name) : null;
180
- if (id) existingById.set(id, name);
181
- }
182
- } catch { /* unreadable archive — treat as empty, never fatal */ }
239
+ const existingById = new Map([...useCaseFilesById(destDir)].map(([id, files]) => [id, files[files.length - 1]]));
183
240
 
184
241
  for (const name of names) {
185
242
  try {
186
243
  const id = useCaseId(name);
187
244
  const stale = id ? existingById.get(id) : null;
245
+ let previous = null;
246
+ if (stale) {
247
+ try { previous = readFileSync(join(destDir, stale), 'utf8'); } catch { /* unreadable — no signal */ }
248
+ }
188
249
 
189
250
  // Same ID under a different filename: normally the old file is dropped
190
251
  // (a slug reword, or the `bug` flow correcting a published UC), or the
@@ -202,8 +263,7 @@ export function promoteUseCases(cwd, featureDir) {
202
263
  // (no git common dir, or a legacy archive entry predating this stamp) is
203
264
  // fail-open: nothing to compare against, so nothing is refused.
204
265
  if (stale && stale !== name) {
205
- let staleFeature = null;
206
- try { staleFeature = readFrontmatterField(readFileSync(join(destDir, stale), 'utf8'), 'sourceFeature'); } catch { /* unreadable — no signal */ }
266
+ const staleFeature = previous === null ? null : readFrontmatterField(previous, 'sourceFeature');
207
267
  const incomingOwner = id ? reservationOwner(id, { cwd, gitCommonDir }) : null;
208
268
 
209
269
  if (staleFeature && staleFeature !== feature && incomingOwner === feature) {
@@ -224,13 +284,18 @@ export function promoteUseCases(cwd, featureDir) {
224
284
  replaced.push(stale);
225
285
  }
226
286
 
227
- let content = stampImplemented(readFileSync(join(srcDir, name), 'utf8'));
228
- content = stampSourceFeature(content, feature);
287
+ let content = readFileSync(join(srcDir, name), 'utf8');
288
+ const kept = inheritSupersession(content, previous);
289
+ if (kept) {
290
+ content = kept.content;
291
+ keptSuperseded.push({ id, file: name, supersededBy: kept.supersededBy, ...(kept.scope !== null ? { scope: kept.scope } : {}) });
292
+ }
293
+ content = stampSourceFeature(stampImplemented(content), feature);
229
294
  writeFileSync(join(destDir, name), content, 'utf8');
230
295
  promoted.push(name);
231
296
  if (id) existingById.set(id, name);
232
297
  } catch { /* one unreadable use case must not sink the rest */ }
233
298
  }
234
299
 
235
- return { promoted, replaced, refused };
300
+ return { promoted, replaced, refused, keptSuperseded };
236
301
  }
@@ -40,10 +40,9 @@ import {
40
40
  } from 'fs';
41
41
  import { join } from 'path';
42
42
 
43
- /** A UC filename's leading `UC-{nn}` — deliberately NOT imported from
44
- * `usecase-promoter.js`: that module will come to depend on THIS one (to look
45
- * up who reserved a given number), and a two-way import would be circular. */
46
- const UC_ID_RE = /^(UC-(\d+))-/;
43
+ // From acervo-format.js, not usecase-promoter.js: the promoter depends on THIS
44
+ // module (to look up who reserved a number), and a two-way import is circular.
45
+ import { useCaseId } from './acervo-format.js';
47
46
 
48
47
  /** `UC-7` (or any width) -> `UC-07` — the archive's zero-padded convention. */
49
48
  function formatUcId(n) {
@@ -138,9 +137,10 @@ function maxUcNumberInDir(dir) {
138
137
  try {
139
138
  if (!existsSync(dir)) return 0;
140
139
  for (const name of readdirSync(dir)) {
141
- const m = UC_ID_RE.exec(name);
142
- if (!m) continue;
143
- const n = parseInt(m[2], 10);
140
+ // Any `UC-{nn}-` prefix counts, canonical or not: a number already on disk is never handed out again.
141
+ const id = useCaseId(name);
142
+ if (!id) continue;
143
+ const n = parseInt(id.slice('UC-'.length), 10);
144
144
  if (Number.isFinite(n) && n > max) max = n;
145
145
  }
146
146
  } catch { /* unreadable dir — treat as empty, never fatal */ }
@@ -12,6 +12,7 @@ import fs from 'fs-extra';
12
12
  import { readFileSync, existsSync } from 'fs';
13
13
  import { join, dirname } from 'path';
14
14
  import { fileURLToPath } from 'url';
15
+ import { UC_ID_RE } from '../specs/acervo-format.js';
15
16
 
16
17
  const __dirname = dirname(fileURLToPath(import.meta.url));
17
18
  const SCHEMA_PATH = join(__dirname, '..', '..', '..', 'framework', 'schemas', 'tasks.schema.json');
@@ -26,8 +27,6 @@ export function getSchema() {
26
27
  const VALID_STATUS = ['pending', 'in_progress', 'done', 'skipped', 'blocked'];
27
28
  const VALID_EFFORT = ['S', 'M', 'L', 'XL'];
28
29
  const VALID_GROUP = ['backend', 'frontend', 'tests', 'infra', 'docs'];
29
- /** Use-case reference: the ID only (`UC-07`), never the filename. */
30
- const USECASE_RE = /^UC-\d+$/;
31
30
  /** Veredito de reuso por conceito — ver framework/schemas/tasks.schema.json. */
32
31
  const VALID_REUSE_VERDICT = ['REUSAR', 'ESTENDER', 'CRIAR'];
33
32
 
@@ -86,7 +85,7 @@ export function validateTasksJson(data) {
86
85
  // Optional: a task with no observable behaviour references no use case.
87
86
  // When present it must be the bare ID — `morph-spec dag` is the gate that
88
87
  // catches "UC-07-listar.md" or "UC7" before the plan reaches Gate 2.
89
- if (task.usecase !== undefined && (typeof task.usecase !== 'string' || !USECASE_RE.test(task.usecase))) {
88
+ if (task.usecase !== undefined && (typeof task.usecase !== 'string' || !UC_ID_RE.test(task.usecase))) {
90
89
  errors.push(`${ctx}.usecase must match /^UC-\\d+$/ (the ID alone, e.g. "UC-07")`);
91
90
  }
92
91