@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
@@ -1,19 +1,4 @@
1
- // ==============================================================================
2
- // MORPH-SPEC - Neon Auth Configuration Template
3
- // Configuracao de autenticacao com Neon Auth (Better Auth) para .NET
4
- //
5
- // Neon Auth tokens are signed with EdDSA (Ed25519, JWK kty "OKP"). There is no
6
- // OIDC discovery document (/.well-known/openid-configuration) — only the raw
7
- // /.well-known/jwks.json — and Microsoft.IdentityModel.Tokens cannot verify
8
- // OKP/EdDSA signatures on its own. See:
9
- // .morph/framework/standards/backend/integrations/neon-auth/neon-auth.md
10
- // → "## .NET Backend Integration" for the full reference and rationale.
11
- //
12
- // NuGet (add both):
13
- // <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.3" />
14
- // <PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2" /> <!-- pin >= 2.3.1, CVE-2024-30172 -->
15
- // ==============================================================================
16
-
1
+ // Neon Auth signs with EdDSA (Ed25519), which Microsoft.IdentityModel cannot verify: needs BouncyCastle.Cryptography >= 2.3.1 (CVE-2024-30172).
17
2
  using System.Security.Claims;
18
3
  using System.Text;
19
4
  using Microsoft.AspNetCore.Authentication;
@@ -26,32 +11,14 @@ using Org.BouncyCastle.Crypto.Signers;
26
11
 
27
12
  namespace {{Namespace}}.Infrastructure.Auth;
28
13
 
29
- // ==============================================================================
30
- // OPTIONS
31
- // ==============================================================================
32
-
33
14
  public class NeonAuthOptions
34
15
  {
35
16
  public const string SectionName = "NeonAuth";
36
17
 
37
- /// <summary>
38
- /// Neon Auth Base URL (e.g., https://ep-xxx.neonauth.us-east-2.aws.neon.build/neondb/auth).
39
- /// MUST be present in appsettings.Development.json — it's a public value, not a secret.
40
- /// Missing it produces the same symptom as a broken signature validator: every request
41
- /// gets a bare 401 with nothing useful in the logs.
42
- /// </summary>
18
+ // Public value, commit it in appsettings.Development.json: when missing, every request is a bare 401 with nothing in the logs.
43
19
  public string BaseUrl { get; set; } = string.Empty;
44
20
  }
45
21
 
46
- // ==============================================================================
47
- // JWKS PROVIDER (cached — Neon Auth has no OIDC discovery to piggyback on)
48
- // ==============================================================================
49
-
50
- /// <summary>
51
- /// Fetches and caches Neon Auth's raw JWKS. Cached for 15 minutes; a forced refresh
52
- /// happens once when an unknown `kid` is seen, to survive key rotation without
53
- /// waiting out the TTL.
54
- /// </summary>
55
22
  public sealed class NeonAuthJwksProvider(HttpClient httpClient, IOptions<NeonAuthOptions> options)
56
23
  {
57
24
  private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(15);
@@ -65,7 +32,7 @@ public sealed class NeonAuthJwksProvider(HttpClient httpClient, IOptions<NeonAut
65
32
  var key = keySet.Keys.FirstOrDefault(k => k.Kid == kid);
66
33
  if (key is not null) return key;
67
34
 
68
- // Unknown kid — could be key rotation. Force a single refresh before giving up.
35
+ // Unknown kid may be a key rotation: refresh once instead of waiting out the TTL.
69
36
  keySet = await GetKeySetAsync(forceRefresh: true, cancellationToken);
70
37
  return keySet.Keys.FirstOrDefault(k => k.Kid == kid);
71
38
  }
@@ -94,18 +61,8 @@ public sealed class NeonAuthJwksProvider(HttpClient httpClient, IOptions<NeonAut
94
61
  }
95
62
  }
96
63
 
97
- // ==============================================================================
98
- // SERVICE EXTENSIONS
99
- // ==============================================================================
100
-
101
64
  public static class NeonAuthServiceExtensions
102
65
  {
103
- /// <summary>
104
- /// Adds Neon Auth JWT Bearer authentication to the project. Validates EdDSA-signed
105
- /// JWTs issued by Neon Auth via a manually-fetched, cached JWKS — see
106
- /// NeonAuthJwksProvider above and the standard's "## .NET Backend Integration" section
107
- /// for why options.Authority / the default IssuerSigningKeyResolver do not work here.
108
- /// </summary>
109
66
  public static IServiceCollection AddNeonAuthentication(
110
67
  this IServiceCollection services,
111
68
  IConfiguration configuration)
@@ -116,9 +73,7 @@ public static class NeonAuthServiceExtensions
116
73
  services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
117
74
  .AddJwtBearer(options =>
118
75
  {
119
- // Do NOT set options.Authority — Neon Auth serves no
120
- // /.well-known/openid-configuration, so Authority-driven metadata refresh
121
- // has nothing valid to fetch. ValidIssuer is set explicitly instead.
76
+ // No options.Authority: Neon Auth serves no /.well-known/openid-configuration to fetch.
122
77
  options.TokenValidationParameters = new TokenValidationParameters
123
78
  {
124
79
  ValidateIssuer = true,
@@ -126,16 +81,12 @@ public static class NeonAuthServiceExtensions
126
81
  ValidateAudience = false,
127
82
  ValidateLifetime = true,
128
83
  NameClaimType = "sub",
129
- // Trust in the signing key is established entirely inside
130
- // SignatureValidator below (fetched from our own cached JWKS) —
131
- // this flag doesn't gate that path.
84
+ // The key is trusted inside SignatureValidator below, not by this flag.
132
85
  ValidateIssuerSigningKey = false,
133
86
  };
134
87
  });
135
88
 
136
- // A second Configure<TDep> pass with DI available — AddJwtBearer's own setup has
137
- // already run, so this only *adds* the SignatureValidator to the
138
- // TokenValidationParameters instance built above.
89
+ // Separate pass because the validator needs NeonAuthJwksProvider from DI.
139
90
  services.AddOptions<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme)
140
91
  .Configure<NeonAuthJwksProvider>((options, jwks) =>
141
92
  {
@@ -143,8 +94,7 @@ public static class NeonAuthServiceExtensions
143
94
  {
144
95
  var jwt = new JsonWebToken(token);
145
96
 
146
- // Pin the algorithm — refuse anything that isn't EdDSA to block
147
- // alg-substitution attacks.
97
+ // Pin the algorithm against alg-substitution attacks.
148
98
  if (!string.Equals(jwt.Alg, "EdDSA", StringComparison.Ordinal))
149
99
  throw new SecurityTokenInvalidSignatureException(
150
100
  $"Unsupported alg '{jwt.Alg}' — Neon Auth tokens must be EdDSA.");
@@ -153,9 +103,7 @@ public static class NeonAuthServiceExtensions
153
103
  if (string.IsNullOrEmpty(kid))
154
104
  throw new SecurityTokenInvalidSignatureException("Token is missing 'kid'.");
155
105
 
156
- // Blocking call: JWKS is cached in-memory, so this is I/O-free on the hot
157
- // path. SignatureValidator has no async overload — only a cold-start /
158
- // key-rotation miss ever awaits real I/O here.
106
+ // SignatureValidator has no async overload; the JWKS is cached, so only a cold miss blocks on I/O.
159
107
  var jwk = jwks.GetKeyAsync(kid, CancellationToken.None).GetAwaiter().GetResult()
160
108
  ?? throw new SecurityTokenInvalidSignatureException(
161
109
  $"No JWKS key found for kid '{kid}'.");
@@ -181,10 +129,7 @@ public static class NeonAuthServiceExtensions
181
129
  throw new SecurityTokenInvalidSignatureException(
182
130
  "Ed25519 signature verification failed.");
183
131
 
184
- // MUST return Microsoft.IdentityModel.JsonWebTokens.JsonWebToken — ASP.NET
185
- // Core 8+ uses JsonWebTokenHandler internally. Returning a
186
- // System.IdentityModel.Tokens.Jwt.JwtSecurityToken here fails with
187
- // IDX10506 ("SignatureValidator returned a token of an unexpected type").
132
+ // Must be JsonWebToken: returning a JwtSecurityToken fails with IDX10506 on ASP.NET Core 8+.
188
133
  return jwt;
189
134
  };
190
135
  });
@@ -195,10 +140,6 @@ public static class NeonAuthServiceExtensions
195
140
  }
196
141
  }
197
142
 
198
- // ==============================================================================
199
- // USER CONTEXT SERVICE
200
- // ==============================================================================
201
-
202
143
  public interface ICurrentUserService
203
144
  {
204
145
  string? UserId { get; }
@@ -218,10 +159,6 @@ public class CurrentUserService(IHttpContextAccessor httpContextAccessor) : ICur
218
159
  httpContextAccessor.HttpContext?.User.Identity?.IsAuthenticated ?? false;
219
160
  }
220
161
 
221
- // ==============================================================================
222
- // MIDDLEWARE PIPELINE
223
- // ==============================================================================
224
-
225
162
  public static class NeonAuthMiddlewareExtensions
226
163
  {
227
164
  public static IApplicationBuilder UseNeonAuthentication(this IApplicationBuilder app)
@@ -231,36 +168,3 @@ public static class NeonAuthMiddlewareExtensions
231
168
  return app;
232
169
  }
233
170
  }
234
-
235
- // ==============================================================================
236
- // APPSETTINGS.DEVELOPMENT.JSON — mandatory, public value, safe to commit
237
- // ==============================================================================
238
-
239
- /*
240
- {
241
- "NeonAuth": {
242
- "BaseUrl": "https://ep-xxx.neonauth.us-east-2.aws.neon.build/neondb/auth"
243
- }
244
- }
245
- */
246
-
247
- // ==============================================================================
248
- // PROGRAM.CS EXAMPLE
249
- // ==============================================================================
250
-
251
- /*
252
- var builder = WebApplication.CreateBuilder(args);
253
-
254
- // Add Neon Auth authentication (registers the JWKS provider + EdDSA signature validator)
255
- builder.Services.AddNeonAuthentication(builder.Configuration);
256
- builder.Services.AddHttpContextAccessor();
257
- builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
258
-
259
- var app = builder.Build();
260
-
261
- app.UseNeonAuthentication();
262
-
263
- app.MapControllers().RequireAuthorization();
264
-
265
- app.Run();
266
- */
@@ -1,120 +1,97 @@
1
- #!/usr/bin/env node
2
- /**
3
- * bin/validate.js — generated by morph-spec setup-infra
4
- *
5
- * Thin validator entry-point called by morph-spec validate-completion hook:
6
- * node bin/validate.js <validator> [--json]
7
- *
8
- * Supported validators: architecture, nextjs-component,
9
- * packages, design-system, ui-contrast, css
10
- *
11
- * Exits 0 on pass, 1 on validation errors.
12
- * --json flag prints structured JSON to stdout.
13
- */
14
-
15
- const validator = process.argv[2];
16
- const isJson = process.argv.includes('--json');
17
-
18
- if (!validator) {
19
- console.error('Usage: node bin/validate.js <validator> [--json]');
20
- process.exit(1);
21
- }
22
-
23
- const { execSync } = await import('child_process');
24
- const { existsSync } = await import('fs');
25
- const { join } = await import('path');
26
-
27
- const cwd = process.cwd();
28
-
29
- const results = {
30
- validator,
31
- errors: 0,
32
- warnings: 0,
33
- passed: true,
34
- issues: [],
35
- };
36
-
37
- /**
38
- * Run a shell command and capture output.
39
- * Returns { success, stdout, stderr }.
40
- */
41
- function runCommand(cmd, options = {}) {
42
- try {
43
- const stdout = execSync(cmd, { cwd, stdio: ['pipe', 'pipe', 'pipe'], ...options }).toString();
44
- return { success: true, stdout, stderr: '' };
45
- } catch (err) {
46
- return {
47
- success: false,
48
- stdout: err.stdout?.toString() || '',
49
- stderr: err.stderr?.toString() || err.message,
50
- };
51
- }
52
- }
53
-
54
- // ── Stack detection ───────────────────────────────────────────────────────────
55
-
56
- const hasFrontend = existsSync(join(cwd, 'src/frontend')) || existsSync(join(cwd, 'frontend'));
57
- const hasBackend = existsSync(join(cwd, 'src/backend')) || existsSync(join(cwd, 'backend'));
58
- const hasSln = existsSync(join(cwd, 'src/backend/backend.sln')) ||
59
- existsSync(join(cwd, 'src/backend/Backend.sln'));
60
-
61
- const frontendDir = existsSync(join(cwd, 'src/frontend')) ? 'src/frontend' : 'frontend';
62
- const backendDir = existsSync(join(cwd, 'src/backend')) ? 'src/backend' : 'backend';
63
-
64
- // ── Validator dispatch ────────────────────────────────────────────────────────
65
-
66
- switch (validator) {
67
- case 'architecture': {
68
- if (hasBackend && hasSln) {
69
- const slnFile = hasSln ? (
70
- existsSync(join(cwd, 'src/backend/backend.sln')) ? 'backend.sln' : 'Backend.sln'
71
- ) : '.';
72
- const result = runCommand(`dotnet build ${slnFile} --no-restore -warnaserror`, { cwd: join(cwd, backendDir) });
73
- if (!result.success) {
74
- results.errors++;
75
- results.passed = false;
76
- results.issues.push({ level: 'error', message: `dotnet build failed: ${result.stderr.split('\n')[0]}` });
77
- }
78
- }
79
- break;
80
- }
81
-
82
- case 'nextjs-component':
83
- case 'packages':
84
- case 'design-system': {
85
- if (hasFrontend) {
86
- const result = runCommand('npx tsc --noEmit', { cwd: join(cwd, frontendDir) });
87
- if (!result.success) {
88
- results.errors++;
89
- results.passed = false;
90
- results.issues.push({ level: 'error', message: `TypeScript errors found. Run: npx tsc --noEmit` });
91
- }
92
- }
93
- break;
94
- }
95
-
96
- case 'ui-contrast':
97
- case 'css': {
98
- // CSS/contrast validators are visual — report as informational
99
- results.issues.push({ level: 'info', message: `${validator} requires manual review` });
100
- break;
101
- }
102
-
103
- default:
104
- results.issues.push({ level: 'warning', message: `Unknown validator: ${validator}` });
105
- break;
106
- }
107
-
108
- // ── Output ────────────────────────────────────────────────────────────────────
109
-
110
- if (isJson) {
111
- console.log(JSON.stringify(results, null, 2));
112
- } else {
113
- const icon = results.passed ? '✅' : '❌';
114
- console.log(`${icon} Validator: ${validator} — ${results.passed ? 'PASSED' : `FAILED (${results.errors} errors)`}`);
115
- for (const issue of results.issues) {
116
- console.log(` [${issue.level}] ${issue.message}`);
117
- }
118
- }
119
-
120
- process.exit(results.passed ? 0 : 1);
1
+ #!/usr/bin/env node
2
+
3
+ const validator = process.argv[2];
4
+ const isJson = process.argv.includes('--json');
5
+
6
+ if (!validator) {
7
+ console.error('Usage: node bin/validate.js <validator> [--json]');
8
+ process.exit(1);
9
+ }
10
+
11
+ const { execSync } = await import('child_process');
12
+ const { existsSync } = await import('fs');
13
+ const { join } = await import('path');
14
+
15
+ const cwd = process.cwd();
16
+
17
+ const results = {
18
+ validator,
19
+ errors: 0,
20
+ warnings: 0,
21
+ passed: true,
22
+ issues: [],
23
+ };
24
+
25
+ function runCommand(cmd, options = {}) {
26
+ try {
27
+ const stdout = execSync(cmd, { cwd, stdio: ['pipe', 'pipe', 'pipe'], ...options }).toString();
28
+ return { success: true, stdout, stderr: '' };
29
+ } catch (err) {
30
+ return {
31
+ success: false,
32
+ stdout: err.stdout?.toString() || '',
33
+ stderr: err.stderr?.toString() || err.message,
34
+ };
35
+ }
36
+ }
37
+
38
+ const hasFrontend = existsSync(join(cwd, 'src/frontend')) || existsSync(join(cwd, 'frontend'));
39
+ const hasBackend = existsSync(join(cwd, 'src/backend')) || existsSync(join(cwd, 'backend'));
40
+ const hasSln = existsSync(join(cwd, 'src/backend/backend.sln')) ||
41
+ existsSync(join(cwd, 'src/backend/Backend.sln'));
42
+
43
+ const frontendDir = existsSync(join(cwd, 'src/frontend')) ? 'src/frontend' : 'frontend';
44
+ const backendDir = existsSync(join(cwd, 'src/backend')) ? 'src/backend' : 'backend';
45
+
46
+ switch (validator) {
47
+ case 'architecture': {
48
+ if (hasBackend && hasSln) {
49
+ const slnFile = hasSln ? (
50
+ existsSync(join(cwd, 'src/backend/backend.sln')) ? 'backend.sln' : 'Backend.sln'
51
+ ) : '.';
52
+ const result = runCommand(`dotnet build ${slnFile} --no-restore -warnaserror`, { cwd: join(cwd, backendDir) });
53
+ if (!result.success) {
54
+ results.errors++;
55
+ results.passed = false;
56
+ results.issues.push({ level: 'error', message: `dotnet build failed: ${result.stderr.split('\n')[0]}` });
57
+ }
58
+ }
59
+ break;
60
+ }
61
+
62
+ case 'nextjs-component':
63
+ case 'packages':
64
+ case 'design-system': {
65
+ if (hasFrontend) {
66
+ const result = runCommand('npx tsc --noEmit', { cwd: join(cwd, frontendDir) });
67
+ if (!result.success) {
68
+ results.errors++;
69
+ results.passed = false;
70
+ results.issues.push({ level: 'error', message: `TypeScript errors found. Run: npx tsc --noEmit` });
71
+ }
72
+ }
73
+ break;
74
+ }
75
+
76
+ case 'ui-contrast':
77
+ case 'css': {
78
+ results.issues.push({ level: 'info', message: `${validator} requires manual review` });
79
+ break;
80
+ }
81
+
82
+ default:
83
+ results.issues.push({ level: 'warning', message: `Unknown validator: ${validator}` });
84
+ break;
85
+ }
86
+
87
+ if (isJson) {
88
+ console.log(JSON.stringify(results, null, 2));
89
+ } else {
90
+ const icon = results.passed ? '✅' : '❌';
91
+ console.log(`${icon} Validator: ${validator} — ${results.passed ? 'PASSED' : `FAILED (${results.errors} errors)`}`);
92
+ for (const issue of results.issues) {
93
+ console.log(` [${issue.level}] ${issue.message}`);
94
+ }
95
+ }
96
+
97
+ process.exit(results.passed ? 0 : 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polymorphism-tech/morph-spec",
3
- "version": "8.37.1",
3
+ "version": "8.40.0",
4
4
  "description": "MORPH-SPEC: NLH (Natural Language Harness) for spec-driven development with Claude Code",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -20,7 +20,6 @@
20
20
  "morph",
21
21
  "polymorphism"
22
22
  ],
23
- "main": "src/index.js",
24
23
  "bin": {
25
24
  "morph-spec": "bin/morph-spec.js"
26
25
  },
@@ -49,6 +48,7 @@
49
48
  "freshness": "node scripts/check-freshness.js",
50
49
  "verify": "npm run generate:check && npm run generate-standards:check && npm run validate && npm run freshness && npm test",
51
50
  "test": "node scripts/run-tests.mjs",
51
+ "test:changed": "node scripts/test-changed.mjs",
52
52
  "test:coverage": "c8 --reporter=text --reporter=html --reporter=lcov node scripts/run-tests.mjs",
53
53
  "test:coverage:summary": "c8 --reporter=text-summary node scripts/run-tests.mjs",
54
54
  "docs": "jsdoc -c jsdoc.json",
@@ -1,53 +1,80 @@
1
- import { spawn } from 'child_process';
2
- import { existsSync } from 'fs';
3
- import { resolve, dirname } from 'path';
4
- import { fileURLToPath } from 'url';
5
- import chalk from 'chalk';
6
-
7
- const __dirname = dirname(fileURLToPath(import.meta.url));
8
-
9
- export async function dashboardCommand(options = {}) {
10
- const port = options.port || '3200';
11
- const dashboardDir = resolve(__dirname, '..', '..', 'dashboard');
12
- const nodeModules = resolve(dashboardDir, 'node_modules');
13
-
14
- // Check if dashboard dependencies are installed
15
- if (!existsSync(nodeModules)) {
16
- console.log(chalk.yellow('Installing dashboard dependencies...'));
17
- const install = spawn('npm', ['install'], {
18
- cwd: dashboardDir,
19
- stdio: 'inherit',
20
- shell: true,
21
- });
22
- await new Promise((res, rej) => {
23
- install.on('close', (code) => (code === 0 ? res() : rej(new Error(`npm install failed with code ${code}`))));
24
- });
25
- }
26
-
27
- console.log(chalk.cyan(`\n Starting morph-spec dashboard at http://localhost:${port}\n`));
28
-
29
- const env = {
30
- ...process.env,
31
- MORPH_PROJECT_DIR: process.cwd(),
32
- };
33
-
34
- const viteArgs = ['vite', '--port', port];
35
- if (options.open !== false) viteArgs.push('--open');
36
-
37
- const vite = spawn('npx', viteArgs, {
38
- cwd: dashboardDir,
39
- stdio: 'inherit',
40
- shell: true,
41
- env,
42
- });
43
-
44
- vite.on('error', (err) => {
45
- console.error(chalk.red(`Failed to start dashboard: ${err.message}`));
46
- process.exit(1);
47
- });
48
-
49
- process.on('SIGINT', () => {
50
- vite.kill();
51
- process.exit(0);
52
- });
53
- }
1
+ import { spawn } from 'child_process';
2
+ import { createHash } from 'crypto';
3
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
4
+ import { resolve, dirname, join } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import chalk from 'chalk';
7
+ import { registerProject } from '../../lib/projects/registry.js';
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+
11
+ /** Where the hash of the package.json that produced node_modules is kept. */
12
+ export const DEPS_STAMP = '.morph-deps-stamp';
13
+
14
+ /** Hash of the dashboard's package.json — the dependency set it declares. */
15
+ export function depsHash(dashboardDir) {
16
+ return createHash('sha256').update(readFileSync(join(dashboardDir, 'package.json'))).digest('hex');
17
+ }
18
+
19
+ /**
20
+ * Does the dashboard need an `npm install`? Not only when node_modules is
21
+ * missing: an install made before a dependency was added fails at import time,
22
+ * so node_modules must have been produced by THIS package.json.
23
+ */
24
+ export function needsInstall(dashboardDir) {
25
+ const stamp = join(dashboardDir, 'node_modules', DEPS_STAMP);
26
+ if (!existsSync(stamp)) return true;
27
+ try {
28
+ return readFileSync(stamp, 'utf8').trim() !== depsHash(dashboardDir);
29
+ } catch {
30
+ return true;
31
+ }
32
+ }
33
+
34
+ export async function dashboardCommand(options = {}) {
35
+ const port = options.port || '3200';
36
+ const dashboardDir = resolve(__dirname, '..', '..', 'dashboard');
37
+
38
+ if (needsInstall(dashboardDir)) {
39
+ console.log(chalk.yellow('Installing dashboard dependencies...'));
40
+ const install = spawn('npm', ['install'], {
41
+ cwd: dashboardDir,
42
+ stdio: 'inherit',
43
+ shell: true,
44
+ });
45
+ await new Promise((res, rej) => {
46
+ install.on('close', (code) => (code === 0 ? res() : rej(new Error(`npm install failed with code ${code}`))));
47
+ });
48
+ writeFileSync(join(dashboardDir, 'node_modules', DEPS_STAMP), depsHash(dashboardDir));
49
+ }
50
+
51
+ // Opening the dashboard in a project puts it in the portfolio.
52
+ registerProject(process.cwd(), 'dashboard');
53
+
54
+ console.log(chalk.cyan(`\n Starting morph-spec dashboard at http://localhost:${port}\n`));
55
+
56
+ const env = {
57
+ ...process.env,
58
+ MORPH_PROJECT_DIR: process.cwd(),
59
+ };
60
+
61
+ const viteArgs = ['vite', '--port', port];
62
+ if (options.open !== false) viteArgs.push('--open');
63
+
64
+ const vite = spawn('npx', viteArgs, {
65
+ cwd: dashboardDir,
66
+ stdio: 'inherit',
67
+ shell: true,
68
+ env,
69
+ });
70
+
71
+ vite.on('error', (err) => {
72
+ console.error(chalk.red(`Failed to start dashboard: ${err.message}`));
73
+ process.exit(1);
74
+ });
75
+
76
+ process.on('SIGINT', () => {
77
+ vite.kill();
78
+ process.exit(0);
79
+ });
80
+ }
@@ -9,7 +9,7 @@
9
9
  * @module graph-affected
10
10
  */
11
11
  import chalk from 'chalk';
12
- import { loadGraph, getFreshness } from '../../lib/graph/graph-loader.js';
12
+ import { loadGraph, getFreshness, describeStale } from '../../lib/graph/graph-loader.js';
13
13
  import { buildIndex, resolveLabel, affected } from '../../lib/graph/sanitize.js';
14
14
 
15
15
  const FALLBACK_HINT = 'Sem resposta do grafo, caia para grep/Glob — o grafo é um subconjunto estrito do grep, então cair para grep não perde recall.';
@@ -79,11 +79,12 @@ export async function graphAffectedCommand(symbol, options = {}) {
79
79
  console.log(chalk.green(`\n✅ ${symbol} — ${consumers.length} consumidor(es)`));
80
80
  console.log(chalk.gray(` declarado em ${resolved.node.source_file}`));
81
81
  if (freshness.state === 'stale') {
82
- console.log(chalk.yellow(' ⚠ grafo stale (o rebuild do hook é assíncrono) — confirme no código antes de agir'));
82
+ console.log(chalk.yellow(` ⚠ grafo stale, confirme no código antes de agir — ${describeStale(projectPath, freshness)}`));
83
83
  }
84
84
  for (const c of consumers) {
85
85
  const loc = c.source_location ? `:${c.source_location}` : '';
86
- console.log(` ${chalk.cyan(c.relation.padEnd(14))} ${c.source_file}${loc} ${chalk.gray(c.label)}`);
86
+ const test = c.test ? chalk.gray(' [teste]') : '';
87
+ console.log(` ${chalk.cyan(c.relation.padEnd(14))} ${c.source_file}${loc} ${chalk.gray(c.label)}${test}`);
87
88
  }
88
89
  console.log('');
89
90
  }