@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,332 +1,407 @@
1
- import { useState, useMemo, useCallback } from 'react';
2
- import { Handle, Position } from '@xyflow/react';
3
- import morphData from 'virtual:morph-data';
4
- import FlowGraph from '../components/FlowGraph.jsx';
5
- import DetailPanel from '../components/DetailPanel.jsx';
6
- import SearchFilter from '../components/SearchFilter.jsx';
7
- import { useDetailPanel } from '../hooks/useDetailPanel.js';
8
-
9
- // ─── Category Color Palette ───────────────────────────────────────────────────
10
-
11
- const CATEGORY_COLORS = {
12
- 'ai-agents': '#34d399',
13
- 'architecture': '#f97316',
14
- 'backend': '#3b82f6',
15
- 'data': '#8b5cf6',
16
- 'frontend': '#f472b6',
17
- 'infrastructure': '#fbbf24',
18
- 'integration': '#06b6d4',
19
- };
20
-
21
- const CATEGORY_BG = {
22
- 'ai-agents': '#064e3b',
23
- 'architecture': '#431407',
24
- 'backend': '#1e3a5f',
25
- 'data': '#2e1065',
26
- 'frontend': '#500724',
27
- 'infrastructure': '#451a03',
28
- 'integration': '#083344',
29
- };
30
-
31
- function getCategoryColor(category) {
32
- return CATEGORY_COLORS[category] || '#94a3b8';
33
- }
34
- function getCategoryBg(category) {
35
- return CATEGORY_BG[category] || '#1e293b';
36
- }
37
-
38
- // ─── Node Components ──────────────────────────────────────────────────────────
39
-
40
- function StandardNode({ data }) {
41
- const color = getCategoryColor(data.category);
42
- const bg = getCategoryBg(data.category);
43
- return (
44
- <div
45
- className="rounded-lg px-3 py-2 min-w-[160px] max-w-[190px] cursor-pointer select-none"
46
- style={{
47
- background: bg,
48
- border: `1.5px solid ${data.dimmed ? bg : color}`,
49
- boxShadow: data.dimmed ? 'none' : `0 0 8px ${color}33`,
50
- opacity: data.dimmed ? 0.25 : 1,
51
- }}
52
- >
53
- <Handle type="target" position={Position.Left} style={{ background: color, width: 7, height: 7 }} />
54
- <Handle type="source" position={Position.Right} style={{ background: color, width: 7, height: 7 }} />
55
- <div className="text-white text-xs font-semibold leading-tight truncate" title={data.label}>
56
- {data.label}
57
- </div>
58
- <div className="text-[10px] mt-0.5 font-mono truncate" style={{ color }}>
59
- {data.category}
60
- {data.subcategory ? ` / ${data.subcategory}` : ''}
61
- </div>
62
- </div>
63
- );
64
- }
65
-
66
- function AgentRefNode({ data }) {
67
- return (
68
- <div
69
- className="rounded px-2 py-1 select-none cursor-default"
70
- style={{
71
- background: '#1e293b',
72
- border: '1px solid #334155',
73
- opacity: data.dimmed ? 0.2 : 1,
74
- }}
75
- >
76
- <Handle type="source" position={Position.Right} style={{ background: '#475569', width: 6, height: 6 }} />
77
- <div className="text-gray-400 text-[10px] font-mono truncate max-w-[110px]" title={data.label}>
78
- {data.label}
79
- </div>
80
- </div>
81
- );
82
- }
83
-
84
- const nodeTypes = { standardNode: StandardNode, agentRefNode: AgentRefNode };
85
-
86
- // ─── Graph Builder ────────────────────────────────────────────────────────────
87
-
88
- function buildGraph(standards, search, categoryFilter) {
89
- const nodes = [];
90
- const edges = [];
91
-
92
- const filtered = standards.filter((s) => {
93
- const q = search.toLowerCase();
94
- const matchSearch = !q || s.name.toLowerCase().includes(q) || s.id.toLowerCase().includes(q) || s.category.toLowerCase().includes(q);
95
- const matchCat = !categoryFilter || s.category === categoryFilter;
96
- return matchSearch && matchCat;
97
- });
98
- const filteredIds = new Set(filtered.map((s) => s.id));
99
- const hasFilter = search || categoryFilter;
100
-
101
- // Group by category
102
- const byCategory = {};
103
- for (const std of standards) {
104
- if (!byCategory[std.category]) byCategory[std.category] = [];
105
- byCategory[std.category].push(std);
106
- }
107
-
108
- const STD_X = 180;
109
- const AGENT_X = 0;
110
- const ROW_H = 75;
111
- const CAT_GAP = 40;
112
-
113
- let globalY = 0;
114
-
115
- // Track all agent refs that need separate nodes
116
- const agentNodeMap = {}; // agentId → { x, y }
117
- const agentYTracker = {};
118
-
119
- for (const [cat, catStandards] of Object.entries(byCategory)) {
120
- catStandards.forEach((std, i) => {
121
- const isDimmed = hasFilter && !filteredIds.has(std.id);
122
- nodes.push({
123
- id: `std-${std.id}`,
124
- type: 'standardNode',
125
- position: { x: STD_X, y: globalY + i * ROW_H },
126
- data: {
127
- label: std.name,
128
- category: std.category,
129
- subcategory: std.subcategory,
130
- stdRef: std,
131
- dimmed: isDimmed,
132
- },
133
- });
134
-
135
- // Agent ref nodes on the left
136
- std.referencedBy.forEach((ref, j) => {
137
- const agentNodeId = `agent-${ref.agentId}`;
138
- if (!agentNodeMap[agentNodeId]) {
139
- const ay = agentYTracker[ref.agentId] ?? globalY + i * ROW_H;
140
- agentYTracker[ref.agentId] = ay + 55;
141
- agentNodeMap[agentNodeId] = { x: AGENT_X, y: ay };
142
- nodes.push({
143
- id: agentNodeId,
144
- type: 'agentRefNode',
145
- position: { x: AGENT_X, y: ay },
146
- data: { label: ref.agentId, dimmed: isDimmed },
147
- });
148
- }
149
- const edgeLabel = [ref.scope, ref.priority].filter(Boolean).join('/');
150
- edges.push({
151
- id: `edge-${ref.agentId}-${std.id}-${j}`,
152
- source: agentNodeId,
153
- target: `std-${std.id}`,
154
- type: 'smoothstep',
155
- label: edgeLabel || undefined,
156
- labelStyle: { fill: '#64748b', fontSize: 9 },
157
- labelBgStyle: { fill: '#0f172a', fillOpacity: 0.85 },
158
- style: {
159
- stroke: isDimmed ? '#1e293b' : getCategoryColor(std.category),
160
- strokeWidth: 1,
161
- opacity: isDimmed ? 0.15 : 0.4,
162
- },
163
- });
164
- });
165
- });
166
-
167
- globalY += catStandards.length * ROW_H + CAT_GAP;
168
- }
169
-
170
- return { nodes, edges };
171
- }
172
-
173
- // ─── Detail Content ───────────────────────────────────────────────────────────
174
-
175
- function StandardDetail({ std }) {
176
- const [showContent, setShowContent] = useState(false);
177
- const color = getCategoryColor(std.category);
178
-
179
- return (
180
- <div className="text-sm text-gray-200 space-y-4">
181
- <div>
182
- <p className="text-gray-400 text-xs">ID: <span className="font-mono text-gray-300">{std.id}</span></p>
183
- <p className="text-gray-400 text-xs mt-0.5">Path: <span className="font-mono text-gray-300">{std.path}</span></p>
184
- </div>
185
-
186
- <div className="flex flex-wrap gap-1">
187
- <span className="px-2 py-0.5 rounded text-xs font-bold" style={{ background: getCategoryBg(std.category), color, border: `1px solid ${color}` }}>
188
- {std.category}
189
- </span>
190
- {std.subcategory && (
191
- <span className="px-2 py-0.5 rounded text-xs bg-gray-800 border border-gray-700 text-gray-400">
192
- {std.subcategory}
193
- </span>
194
- )}
195
- </div>
196
-
197
- {std.digest && (
198
- <div>
199
- <p className="text-gray-500 text-xs uppercase tracking-wide mb-1.5">Digest</p>
200
- <p className="text-gray-300 text-xs leading-relaxed">{std.digest}</p>
201
- </div>
202
- )}
203
-
204
- {std.tags?.length > 0 && (
205
- <div>
206
- <p className="text-gray-500 text-xs uppercase tracking-wide mb-1.5">Tags</p>
207
- <div className="flex flex-wrap gap-1">
208
- {std.tags.map((t) => (
209
- <span key={t} className="px-1.5 py-0.5 bg-gray-800 border border-gray-700 rounded text-xs text-gray-400">{t}</span>
210
- ))}
211
- </div>
212
- </div>
213
- )}
214
-
215
- {std.stacks?.length > 0 && (
216
- <div>
217
- <p className="text-gray-500 text-xs uppercase tracking-wide mb-1.5">Stacks</p>
218
- <div className="flex flex-wrap gap-1">
219
- {std.stacks.map((s) => (
220
- <span key={s} className="px-2 py-0.5 bg-green-900/50 border border-green-700 rounded text-xs text-green-300">{s}</span>
221
- ))}
222
- </div>
223
- </div>
224
- )}
225
-
226
- {std.referencedBy?.length > 0 && (
227
- <div>
228
- <p className="text-gray-500 text-xs uppercase tracking-wide mb-1.5">Referenced By ({std.referencedBy.length})</p>
229
- <div className="space-y-1">
230
- {std.referencedBy.map((ref, i) => (
231
- <div key={i} className="flex items-center justify-between text-xs px-2 py-1 bg-gray-800 rounded">
232
- <span className="font-mono text-gray-300 truncate">{ref.agentId}</span>
233
- <div className="flex gap-1 ml-2 flex-shrink-0">
234
- {ref.scope && <span className="px-1.5 py-0.5 bg-gray-700 rounded text-gray-400">{ref.scope}</span>}
235
- {ref.priority && <span className="px-1.5 py-0.5 bg-gray-700 rounded text-gray-400">{ref.priority}</span>}
236
- </div>
237
- </div>
238
- ))}
239
- </div>
240
- </div>
241
- )}
242
-
243
- <div>
244
- <button
245
- onClick={() => setShowContent((v) => !v)}
246
- className="w-full text-left px-3 py-1.5 bg-gray-800 hover:bg-gray-700 border border-gray-700 rounded text-xs text-gray-300 transition-colors"
247
- >
248
- {showContent ? 'Hide Content' : 'Show Full Content'}
249
- </button>
250
- {showContent && (
251
- <pre className="mt-2 p-3 bg-gray-950 border border-gray-800 rounded text-[9px] text-gray-400 overflow-x-auto whitespace-pre-wrap font-mono leading-relaxed max-h-64 overflow-y-auto">
252
- {std.content}
253
- </pre>
254
- )}
255
- </div>
256
- </div>
257
- );
258
- }
259
-
260
- // ─── Main Page ────────────────────────────────────────────────────────────────
261
-
262
- export default function Standards() {
263
- const { standards } = morphData;
264
- const [search, setSearch] = useState('');
265
- const [categoryFilter, setCategoryFilter] = useState('');
266
- const { panel, openPanel, closePanel } = useDetailPanel();
267
-
268
- const allCategories = useMemo(
269
- () => [...new Set(standards.list.map((s) => s.category))].sort(),
270
- [standards.list]
271
- );
272
-
273
- const { nodes, edges } = useMemo(
274
- () => buildGraph(standards.list, search, categoryFilter),
275
- [standards.list, search, categoryFilter]
276
- );
277
-
278
- const handleNodeClick = useCallback(
279
- (_event, node) => {
280
- if (node.type === 'standardNode') {
281
- const std = node.data.stdRef;
282
- if (std) openPanel(std.name, <StandardDetail std={std} />);
283
- }
284
- },
285
- [openPanel]
286
- );
287
-
288
- const filterConfig = [
289
- { name: 'category', label: 'Category', options: allCategories, value: categoryFilter },
290
- ];
291
-
292
- return (
293
- <div className="flex flex-col h-full">
294
- <SearchFilter
295
- placeholder="Search standards by name, ID, or category..."
296
- filters={filterConfig}
297
- onSearchChange={setSearch}
298
- onFilterChange={(name, value) => {
299
- if (name === 'category') setCategoryFilter(value);
300
- }}
301
- />
302
-
303
- <div className="flex-1 relative">
304
- <FlowGraph
305
- nodes={nodes}
306
- edges={edges}
307
- nodeTypes={nodeTypes}
308
- onNodeClick={handleNodeClick}
309
- fitView
310
- />
311
-
312
- <div className="absolute top-3 left-3 bg-gray-900/90 border border-gray-700 rounded-lg p-3 text-xs space-y-1.5 z-10">
313
- <p className="text-gray-400 font-semibold uppercase tracking-wide text-[10px] mb-2">Categories</p>
314
- {Object.entries(CATEGORY_COLORS).map(([cat, color]) => (
315
- <div key={cat} className="flex items-center gap-2">
316
- <span className="inline-block w-3 h-3 rounded-sm flex-shrink-0" style={{ background: getCategoryBg(cat), border: `1px solid ${color}` }} />
317
- <span className="text-gray-300">{cat}</span>
318
- </div>
319
- ))}
320
- </div>
321
-
322
- <div className="absolute top-3 right-3 bg-gray-900/90 border border-gray-700 rounded-lg px-3 py-1.5 text-xs text-gray-400 z-10">
323
- {standards.list.length} standards · v{standards.version}
324
- </div>
325
- </div>
326
-
327
- <DetailPanel open={panel.open} title={panel.title} onClose={closePanel}>
328
- {panel.content}
329
- </DetailPanel>
330
- </div>
331
- );
332
- }
1
+ import { useState, useMemo, useCallback } from 'react';
2
+ import { Handle, Position } from '@xyflow/react';
3
+ import { Robot } from '@phosphor-icons/react';
4
+ import morphData from 'virtual:morph-data';
5
+ import FlowGraph from '../components/FlowGraph.jsx';
6
+ import DetailPanel from '../components/DetailPanel.jsx';
7
+ import SearchFilter from '../components/SearchFilter.jsx';
8
+ import Markdown from '../components/Markdown.jsx';
9
+ import EntityLink, { useSelection, standardKey } from '../components/EntityLink.jsx';
10
+
11
+ // ─── Colors ───────────────────────────────────────────────────────────────────
12
+
13
+ /** Token palette the categories draw from, in order; the categories come from the data. */
14
+ const PALETTE = [
15
+ 'var(--color-phase-implement)',
16
+ 'var(--color-phase-uiux)',
17
+ 'var(--color-phase-proposal)',
18
+ 'var(--color-phase-plan)',
19
+ 'var(--color-ok)',
20
+ 'var(--color-warn)',
21
+ 'var(--color-phase-review)',
22
+ 'var(--color-dim)',
23
+ ];
24
+
25
+ const tint = (color, pct = 14) => `color-mix(in srgb, ${color} ${pct}%, var(--color-core))`;
26
+
27
+ /** category → color, stable by category name so a color never jumps between renders. */
28
+ function categoryColors(standards) {
29
+ const cats = [...new Set(standards.map((s) => s.category || 'outros'))].sort();
30
+ return Object.fromEntries(cats.map((c, i) => [c, PALETTE[i % PALETTE.length]]));
31
+ }
32
+
33
+ /** Resolve `?sel=` to a standard: by id first, then by the path a task cites. */
34
+ export function findStandard(list, sel) {
35
+ if (!sel) return null;
36
+ return list.find((s) => s.id === sel) || list.find((s) => s.path === standardKey(sel)) || null;
37
+ }
38
+
39
+ // ─── Nodes ────────────────────────────────────────────────────────────────────
40
+
41
+ const HANDLE = { width: 7, height: 7, border: 0 };
42
+ const NODE_W = 230;
43
+ const NODE_H = 52;
44
+
45
+ function StandardNode({ data }) {
46
+ return (
47
+ <div
48
+ className="cursor-pointer select-none rounded-xl px-3 py-1.5 transition-opacity"
49
+ style={{
50
+ width: NODE_W, height: NODE_H,
51
+ background: tint(data.color, data.selected ? 30 : 12),
52
+ boxShadow: `inset 0 0 0 ${data.selected ? 2 : 1.25}px ${data.color}`,
53
+ opacity: data.dimmed ? 0.18 : 1,
54
+ }}
55
+ >
56
+ <Handle type="target" position={Position.Top} style={{ ...HANDLE, background: data.color }} />
57
+ <div className="truncate text-[13px] font-medium leading-tight text-[var(--color-fg)]" title={data.label}>{data.label}</div>
58
+ <div className="mt-0.5 truncate text-xs text-[var(--color-dim)]" title={data.sub}>{data.sub}</div>
59
+ </div>
60
+ );
61
+ }
62
+
63
+ function AgentNode({ data }) {
64
+ return (
65
+ <div
66
+ className="flex cursor-pointer select-none items-center gap-2 rounded-full px-4 py-2 transition-opacity"
67
+ style={{
68
+ width: NODE_W,
69
+ background: data.active ? tint('var(--color-fg)', 16) : 'var(--color-raised)',
70
+ boxShadow: `inset 0 0 0 ${data.active ? 2 : 1}px ${data.active ? 'var(--color-fg)' : 'var(--color-hair-strong)'}`,
71
+ opacity: data.dimmed ? 0.3 : 1,
72
+ }}
73
+ >
74
+ <Robot weight="light" size={16} className="shrink-0 text-[var(--color-dim)]" />
75
+ <div className="min-w-0">
76
+ <div className="truncate font-mono text-[13px] text-[var(--color-fg)]" title={data.title}>morph-{data.agentId}</div>
77
+ <div className="text-xs text-[var(--color-dim)]">{data.count} standards</div>
78
+ </div>
79
+ <Handle type="source" position={Position.Bottom} style={{ ...HANDLE, background: 'var(--color-dim)' }} />
80
+ </div>
81
+ );
82
+ }
83
+
84
+ function CategoryNode({ data }) {
85
+ return (
86
+ <div
87
+ className="pointer-events-none rounded-2xl"
88
+ style={{ width: data.width, height: data.height, background: tint(data.color, 5), boxShadow: `inset 0 0 0 1px color-mix(in srgb, ${data.color} 40%, transparent)` }}
89
+ >
90
+ <div className="flex items-baseline gap-2 px-4 pt-3">
91
+ <span className="text-sm font-semibold" style={{ color: data.color }}>{data.label}</span>
92
+ <span className="text-xs text-[var(--color-dim)]">{data.count}</span>
93
+ </div>
94
+ </div>
95
+ );
96
+ }
97
+
98
+ const nodeTypes = { standardNode: StandardNode, agentNode: AgentNode, categoryNode: CategoryNode };
99
+
100
+ // ─── Layout ───────────────────────────────────────────────────────────────────
101
+
102
+ const CELL_W = NODE_W + 14;
103
+ const CELL_H = NODE_H + 12;
104
+ const PAD = 16;
105
+ const HEADER = 40;
106
+ const BLOCK_GAP = 36;
107
+ const MAX_ROW_W = 1700;
108
+
109
+ /** How many columns a category block gets: tall categories spread sideways. */
110
+ function colsFor(n) {
111
+ if (n > 12) return 3;
112
+ if (n > 5) return 2;
113
+ return 1;
114
+ }
115
+
116
+ function buildGraph({ standards, agents, colors, isMatch, hasFilter, persona, selectedId }) {
117
+ const nodes = [];
118
+ const edges = [];
119
+
120
+ const byCat = new Map();
121
+ for (const s of standards) {
122
+ const c = s.category || 'outros';
123
+ if (!byCat.has(c)) byCat.set(c, []);
124
+ byCat.get(c).push(s);
125
+ }
126
+ const cats = [...byCat.entries()]
127
+ .map(([cat, list]) => [cat, [...list].sort((a, b) => (a.subcategory || '').localeCompare(b.subcategory || '') || a.name.localeCompare(b.name))])
128
+ .sort((a, b) => b[1].length - a[1].length || a[0].localeCompare(b[0]));
129
+
130
+ // Pack the category blocks into rows no wider than MAX_ROW_W.
131
+ const blocks = cats.map(([cat, list]) => {
132
+ const cols = colsFor(list.length);
133
+ const rows = Math.ceil(list.length / cols);
134
+ return { cat, list, cols, w: cols * CELL_W + PAD * 2 - 14, h: HEADER + rows * CELL_H + PAD - 12 };
135
+ });
136
+ const rows = [];
137
+ for (const b of blocks) {
138
+ const row = rows[rows.length - 1];
139
+ if (row && row.w + BLOCK_GAP + b.w <= MAX_ROW_W) { row.items.push(b); row.w += BLOCK_GAP + b.w; row.h = Math.max(row.h, b.h); }
140
+ else rows.push({ items: [b], w: b.w, h: b.h });
141
+ }
142
+ const totalW = Math.max(...rows.map((r) => r.w), 0);
143
+
144
+ const std = new Set(); // node ids present
145
+ let y = 180;
146
+ for (const row of rows) {
147
+ let x = (totalW - row.w) / 2;
148
+ for (const b of row.items) {
149
+ const color = colors[b.cat];
150
+ nodes.push({
151
+ id: `cat:${b.cat}`, type: 'categoryNode', position: { x, y },
152
+ data: { label: b.cat, count: b.list.length, color, width: b.w, height: b.h },
153
+ selectable: false, draggable: false, focusable: false, zIndex: -1,
154
+ });
155
+ b.list.forEach((s, i) => {
156
+ const col = i % b.cols;
157
+ const r = Math.floor(i / b.cols);
158
+ nodes.push({
159
+ id: `std:${s.id}`, type: 'standardNode',
160
+ position: { x: x + PAD + col * CELL_W, y: y + HEADER + r * CELL_H },
161
+ data: {
162
+ label: s.name, sub: s.subcategory || s.path, color, stdId: s.id,
163
+ dimmed: hasFilter && !isMatch(s), selected: s.id === selectedId,
164
+ },
165
+ });
166
+ std.add(s.id);
167
+ });
168
+ x += b.w + BLOCK_GAP;
169
+ }
170
+ y += row.h + BLOCK_GAP;
171
+ }
172
+
173
+ // The personas on top, each with an edge to every standard it consults.
174
+ const withStandards = agents.filter((a) => (a.standards || []).some((id) => std.has(id)));
175
+ const agentX0 = (totalW - withStandards.length * (NODE_W + 24)) / 2;
176
+ const byId = new Map(standards.map((s) => [s.id, s]));
177
+ withStandards.forEach((a, i) => {
178
+ const ids = (a.standards || []).filter((id) => std.has(id));
179
+ const active = persona === a.id;
180
+ nodes.push({
181
+ id: `agent:${a.id}`, type: 'agentNode', position: { x: agentX0 + i * (NODE_W + 24), y: 0 },
182
+ data: { agentId: a.id, title: a.title, count: ids.length, active, dimmed: Boolean(persona) && !active },
183
+ });
184
+ for (const id of ids) {
185
+ const s = byId.get(id);
186
+ const lit = persona ? active : !hasFilter || isMatch(s);
187
+ edges.push({
188
+ id: `e:${a.id}->${id}`, source: `agent:${a.id}`, target: `std:${id}`, type: 'default',
189
+ style: { stroke: colors[s.category || 'outros'], strokeWidth: lit && persona ? 1.5 : 1, opacity: lit ? (persona ? 0.85 : 0.22) : 0.04 },
190
+ });
191
+ }
192
+ });
193
+
194
+ return { nodes, edges, categories: cats.map(([c, l]) => [c, l.length]) };
195
+ }
196
+
197
+ // ─── Detail ───────────────────────────────────────────────────────────────────
198
+
199
+ function Section({ title, children }) {
200
+ return (
201
+ <section className="space-y-2">
202
+ <h3 className="text-xs font-medium text-[var(--color-faint)]">{title}</h3>
203
+ {children}
204
+ </section>
205
+ );
206
+ }
207
+
208
+ function jumpTo(anchor) {
209
+ if (typeof document === 'undefined') return;
210
+ const id = decodeURIComponent(String(anchor).replace(/^#/, ''));
211
+ document.getElementById(id)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
212
+ }
213
+
214
+ export function StandardDetail({ std, color = 'var(--color-dim)' }) {
215
+ const aliases = std.aliases || [];
216
+ const anchors = Object.entries(std.anchors || {});
217
+ const refs = std.referencedBy || [];
218
+ const provedBy = std.provedBy || [];
219
+ const stacks = std.stacks || [];
220
+ const verified = typeof std.verifiedAgainst === 'string' ? std.verifiedAgainst.trim() : '';
221
+
222
+ return (
223
+ <div className="space-y-6 text-sm text-[var(--color-dim)]">
224
+ <div className="flex flex-wrap items-center gap-1.5">
225
+ <span className="rounded-full px-2.5 py-0.5 text-xs font-medium" style={{ color, background: tint(color, 18), boxShadow: `inset 0 0 0 1px ${color}` }}>
226
+ {std.category}
227
+ </span>
228
+ {std.subcategory && (
229
+ <span className="rounded-full px-2.5 py-0.5 text-xs shadow-[inset_0_0_0_1px_var(--color-hair-strong)]">{std.subcategory}</span>
230
+ )}
231
+ {stacks.map((s) => (
232
+ <span key={s} className="rounded-full px-2 py-0.5 font-mono text-xs shadow-[inset_0_0_0_1px_var(--color-hair)]">stack {s}</span>
233
+ ))}
234
+ </div>
235
+
236
+ <div className="font-mono text-xs text-[var(--color-faint)]">
237
+ <div>id <span className="text-[var(--color-dim)]">{std.id}</span></div>
238
+ {std.path && <div>arquivo <span className="text-[var(--color-dim)]">standards/{std.path}</span></div>}
239
+ </div>
240
+
241
+ {std.digest && (
242
+ <Section title="Resumo">
243
+ <p className="leading-relaxed text-[var(--color-fg)]">{std.digest}</p>
244
+ </Section>
245
+ )}
246
+
247
+ {aliases.length > 0 && (
248
+ <Section title="Também encontrado por">
249
+ <div className="flex flex-wrap gap-1.5">
250
+ {aliases.map((a, i) => (
251
+ <span key={`${a}-${i}`} className="rounded-full px-2 py-0.5 text-xs shadow-[inset_0_0_0_1px_var(--color-hair)]">{a}</span>
252
+ ))}
253
+ </div>
254
+ </Section>
255
+ )}
256
+
257
+ {anchors.length > 0 && (
258
+ <Section title="Seções">
259
+ <ul className="grid grid-cols-[max-content_1fr] gap-x-3 gap-y-1">
260
+ {anchors.map(([name, href]) => (
261
+ <li key={name} className="contents">
262
+ <span className="font-mono text-xs leading-6 text-[var(--color-faint)]">{name}</span>
263
+ <button
264
+ type="button" onClick={() => jumpTo(href)}
265
+ className="truncate text-left font-mono text-xs leading-6 text-[var(--color-phase-proposal)] hover:underline"
266
+ >
267
+ {href}
268
+ </button>
269
+ </li>
270
+ ))}
271
+ </ul>
272
+ </Section>
273
+ )}
274
+
275
+ <Section title={refs.length ? `Consultado por (${refs.length})` : 'Consultado por'}>
276
+ {refs.length ? (
277
+ <div className="flex flex-wrap gap-1.5">
278
+ {refs.map((r, i) => <EntityLink key={`${r.agentId}-${i}`} kind="persona" id={r.agentId} chip>{`morph-${r.agentId}`}</EntityLink>)}
279
+ </div>
280
+ ) : (
281
+ <p>Nenhuma persona lista este standard; ele é achado por busca (<code className="font-mono text-xs">morph-spec standards --find</code>).</p>
282
+ )}
283
+ </Section>
284
+
285
+ {provedBy.length > 0 && (
286
+ <Section title="Provado por">
287
+ <ul className="space-y-1">
288
+ {provedBy.map((p, i) => (
289
+ <li key={`${p}-${i}`} className="break-all font-mono text-xs text-[var(--color-fg)]">{p}</li>
290
+ ))}
291
+ </ul>
292
+ </Section>
293
+ )}
294
+
295
+ {verified && (
296
+ <Section title="Verificado contra">
297
+ <Markdown text={verified} size="sm" />
298
+ </Section>
299
+ )}
300
+
301
+ {std.content && (
302
+ <Section title="Conteúdo completo">
303
+ <Markdown text={std.content} size="sm" />
304
+ </Section>
305
+ )}
306
+ </div>
307
+ );
308
+ }
309
+
310
+ // ─── Page ─────────────────────────────────────────────────────────────────────
311
+
312
+ export default function Standards() {
313
+ const standards = morphData.standards?.list || [];
314
+ const version = morphData.standards?.version;
315
+ const agents = morphData.agents?.list || [];
316
+ const [sel, setSel] = useSelection();
317
+ const [search, setSearch] = useState('');
318
+ const [categoryFilter, setCategoryFilter] = useState('');
319
+ const [persona, setPersona] = useState('');
320
+
321
+ const colors = useMemo(() => categoryColors(standards), [standards]);
322
+ const selected = useMemo(() => findStandard(standards, sel), [standards, sel]);
323
+
324
+ const isMatch = useCallback((s) => {
325
+ const q = search.trim().toLowerCase();
326
+ if (q) {
327
+ const hay = [s.id, s.name, s.path, s.digest, s.category, s.subcategory, ...(s.aliases || []), ...(s.tags || [])]
328
+ .filter(Boolean).join(' ').toLowerCase();
329
+ if (!hay.includes(q)) return false;
330
+ }
331
+ if (categoryFilter && s.category !== categoryFilter) return false;
332
+ if (persona && !(s.referencedBy || []).some((r) => r.agentId === persona)) return false;
333
+ return true;
334
+ }, [search, categoryFilter, persona]);
335
+
336
+ const hasFilter = Boolean(search.trim() || categoryFilter || persona);
337
+ const matchCount = useMemo(() => standards.filter(isMatch).length, [standards, isMatch]);
338
+ const orphanCount = standards.filter((s) => !(s.referencedBy || []).length).length;
339
+
340
+ const graph = useMemo(
341
+ () => buildGraph({ standards, agents, colors, isMatch, hasFilter, persona, selectedId: selected?.id }),
342
+ [standards, agents, colors, isMatch, hasFilter, persona, selected?.id],
343
+ );
344
+
345
+ const personaOptions = useMemo(
346
+ () => agents.filter((a) => (a.standards || []).length).map((a) => a.id),
347
+ [agents],
348
+ );
349
+
350
+ const close = useCallback(() => setSel(null), [setSel]);
351
+ const onNodeClick = useCallback((_e, node) => {
352
+ if (node.type === 'standardNode') setSel(node.data.stdId);
353
+ else if (node.type === 'agentNode') setPersona((p) => (p === node.data.agentId ? '' : node.data.agentId));
354
+ }, [setSel]);
355
+
356
+ const filters = [
357
+ { name: 'category', label: 'Categoria', options: graph.categories.map(([c]) => c), value: categoryFilter },
358
+ { name: 'persona', label: 'Persona', options: personaOptions, value: persona },
359
+ ];
360
+
361
+ return (
362
+ <div className="flex h-full flex-col">
363
+ <SearchFilter
364
+ placeholder="Buscar standard por nome, id, caminho, resumo ou apelido"
365
+ filters={filters}
366
+ onSearchChange={setSearch}
367
+ onFilterChange={(name, value) => {
368
+ if (name === 'category') setCategoryFilter(value);
369
+ if (name === 'persona') setPersona(value);
370
+ }}
371
+ />
372
+
373
+ <div className="relative flex-1">
374
+ <FlowGraph nodes={graph.nodes} edges={graph.edges} nodeTypes={nodeTypes} onNodeClick={onNodeClick} fitView />
375
+
376
+ <div className="absolute left-3 top-3 z-10 space-y-1.5 rounded-xl bg-[var(--color-ink)]/85 p-3 text-xs text-[var(--color-dim)] shadow-[inset_0_0_0_1px_var(--color-hair)] backdrop-blur">
377
+ <p className="mb-1 text-[13px] font-medium text-[var(--color-fg)]">Categorias</p>
378
+ {graph.categories.map(([cat, n]) => (
379
+ <button
380
+ key={cat} type="button"
381
+ onClick={() => setCategoryFilter((c) => (c === cat ? '' : cat))}
382
+ className="flex w-full items-center gap-2 text-left hover:text-[var(--color-fg)]"
383
+ style={{ opacity: categoryFilter && categoryFilter !== cat ? 0.45 : 1 }}
384
+ >
385
+ <span className="inline-block h-3 w-3 shrink-0 rounded" style={{ background: tint(colors[cat], 30), boxShadow: `inset 0 0 0 1px ${colors[cat]}` }} />
386
+ <span>{cat}</span>
387
+ <span className="ml-auto pl-3 text-[var(--color-faint)]">{n}</span>
388
+ </button>
389
+ ))}
390
+ <p className="border-t border-[var(--color-hair)] pt-2 text-[var(--color-faint)]">clique numa persona para ver os standards dela</p>
391
+ </div>
392
+
393
+ <div className="absolute right-3 top-3 z-10 space-y-0.5 rounded-xl bg-[var(--color-ink)]/85 px-3 py-2 text-xs text-[var(--color-dim)] shadow-[inset_0_0_0_1px_var(--color-hair)] backdrop-blur">
394
+ <div className="text-[13px] text-[var(--color-fg)]">
395
+ {hasFilter ? `${matchCount} de ${standards.length} standards` : `${standards.length} standards`}
396
+ </div>
397
+ <div>{graph.categories.length} categorias{version ? ` · registro v${version}` : ''}</div>
398
+ <div>{orphanCount} sem persona (achados por busca)</div>
399
+ </div>
400
+ </div>
401
+
402
+ <DetailPanel open={Boolean(selected)} title={selected?.name || ''} subtitle={selected?.id || ''} onClose={close}>
403
+ {selected && <StandardDetail std={selected} color={colors[selected.category || 'outros']} />}
404
+ </DetailPanel>
405
+ </div>
406
+ );
407
+ }