@polymorphism-tech/morph-spec 8.38.0 → 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 (227) hide show
  1. package/CLAUDE.md +19 -8
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +60 -43
  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 +3 -2
  8. package/framework/MORPH.md +1 -1
  9. package/framework/agents.json +36 -25
  10. package/framework/docs/CLI.md +2 -2
  11. package/framework/evals/rubrics/code-quality.md +4 -4
  12. package/framework/evals/rubrics/condensed.md +2 -2
  13. package/framework/mcp/registry.json +33 -2
  14. package/framework/rules/infrastructure-standards.md +0 -1
  15. package/framework/rules/morph-workflow.md +9 -4
  16. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  17. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  18. package/framework/skills/core/morph-apply/SKILL.md +5 -1
  19. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  20. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  21. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  22. package/framework/skills/core/morph-implement/SKILL.md +6 -1
  23. package/framework/skills/core/morph-media/SKILL.md +1 -1
  24. package/framework/skills/core/morph-plan/SKILL.md +4 -3
  25. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  26. package/framework/skills/core/morph-review/SKILL.md +1 -1
  27. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  28. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  29. package/framework/skills/core/morph-uiux/SKILL.md +6 -5
  30. package/framework/standards/STANDARDS.json +3 -3
  31. package/framework/standards/ai-agents/context-providers.md +16 -26
  32. package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
  33. package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
  34. package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
  35. package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
  36. package/framework/standards/ai-agents/modalities-vision.md +1 -18
  37. package/framework/standards/ai-agents/prompt-sources.md +0 -4
  38. package/framework/standards/architecture/code-comments.md +52 -16
  39. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
  40. package/framework/standards/frontend/design-system/ai-image-generation.md +5 -5
  41. package/framework/standards/integration/mcp/mcp-tools.md +48 -117
  42. package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
  43. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
  44. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
  45. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
  46. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
  47. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
  48. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
  49. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
  50. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
  51. package/framework/templates/code/dotnet/database/migration.cs +49 -83
  52. package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
  53. package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
  54. package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
  55. package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
  56. package/framework/templates/code/dotnet/jobs/job.cs +2 -97
  57. package/framework/templates/code/dotnet/test.cs +2 -87
  58. package/framework/templates/code/typescript/contracts.ts +140 -167
  59. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
  60. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
  61. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
  62. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
  63. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
  64. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
  65. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
  66. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
  67. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
  68. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
  69. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
  70. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
  71. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
  72. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
  73. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
  74. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
  75. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
  76. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
  77. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
  78. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
  79. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
  80. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
  81. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
  82. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
  83. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
  84. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
  85. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
  86. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
  87. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
  88. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
  89. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
  90. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
  91. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
  92. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
  93. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
  94. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
  95. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
  96. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
  97. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
  98. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
  99. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
  100. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
  101. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
  102. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
  103. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
  104. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
  105. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
  106. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
  107. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
  108. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
  109. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
  110. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
  111. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
  112. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
  113. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
  114. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
  115. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
  116. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
  117. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
  118. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
  119. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
  120. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
  121. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
  122. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
  123. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
  124. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
  125. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
  126. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
  127. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
  128. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
  129. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
  130. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
  131. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
  132. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
  133. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
  134. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
  135. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
  136. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
  137. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
  138. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
  139. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
  140. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
  141. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
  142. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
  143. package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
  144. package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
  145. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
  146. package/framework/templates/integrations/asaas-client.cs +332 -387
  147. package/framework/templates/integrations/asaas-webhook.cs +290 -351
  148. package/framework/templates/integrations/neon-auth-config.cs +9 -105
  149. package/framework/templates/project/validate.js +97 -120
  150. package/package.json +2 -2
  151. package/src/commands/dashboard/dashboard.js +80 -53
  152. package/src/commands/mcp/mcp-setup.js +37 -9
  153. package/src/commands/project/doctor.js +3 -5
  154. package/src/commands/project/init.js +6 -3
  155. package/src/commands/project/install-plugin-cmd.js +1 -1
  156. package/src/commands/project/update.js +5 -0
  157. package/src/dashboard/app/App.jsx +80 -16
  158. package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
  159. package/src/dashboard/app/components/CommandPalette.jsx +155 -0
  160. package/src/dashboard/app/components/DetailPanel.jsx +47 -11
  161. package/src/dashboard/app/components/EntityLink.jsx +55 -0
  162. package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
  163. package/src/dashboard/app/components/FlowGraph.jsx +19 -17
  164. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
  165. package/src/dashboard/app/components/Markdown.jsx +47 -0
  166. package/src/dashboard/app/components/PhaseRail.jsx +111 -0
  167. package/src/dashboard/app/components/SearchFilter.jsx +40 -22
  168. package/src/dashboard/app/components/Sidebar.jsx +146 -37
  169. package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
  170. package/src/dashboard/app/hooks/usePref.js +38 -0
  171. package/src/dashboard/app/lib/format.js +69 -0
  172. package/src/dashboard/app/lib/phases.js +56 -0
  173. package/src/dashboard/app/lib/project.js +50 -0
  174. package/src/dashboard/app/pages/Activity.jsx +24 -0
  175. package/src/dashboard/app/pages/AgentMap.jsx +371 -370
  176. package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
  177. package/src/dashboard/app/pages/Features.jsx +400 -0
  178. package/src/dashboard/app/pages/Guide.jsx +146 -0
  179. package/src/dashboard/app/pages/HookInspector.jsx +357 -322
  180. package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
  181. package/src/dashboard/app/pages/Portfolio.jsx +156 -0
  182. package/src/dashboard/app/pages/Rubrics.jsx +101 -0
  183. package/src/dashboard/app/pages/RulesMap.jsx +300 -273
  184. package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
  185. package/src/dashboard/app/pages/Standards.jsx +407 -332
  186. package/src/dashboard/app/styles/index.css +187 -47
  187. package/src/dashboard/index.html +14 -12
  188. package/src/dashboard/package.json +32 -24
  189. package/src/dashboard/scanner/activity-scanner.js +351 -0
  190. package/src/dashboard/scanner/agents-scanner.js +59 -1
  191. package/src/dashboard/scanner/docs-scanner.js +59 -0
  192. package/src/dashboard/scanner/feature-status.js +10 -0
  193. package/src/dashboard/scanner/features-scanner.js +360 -0
  194. package/src/dashboard/scanner/index.js +14 -7
  195. package/src/dashboard/scanner/phases-scanner.js +14 -15
  196. package/src/dashboard/scanner/portfolio.js +153 -0
  197. package/src/dashboard/scanner/project-api.js +195 -0
  198. package/src/dashboard/scanner/rules-scanner.js +42 -37
  199. package/src/dashboard/scanner/skills-scanner.js +105 -43
  200. package/src/dashboard/scanner/standards-scanner.js +2 -0
  201. package/src/dashboard/vite-plugin-morph-data.js +134 -17
  202. package/src/lib/detectors/claude-config-detector.js +2 -7
  203. package/src/lib/detectors/index.js +5 -1
  204. package/src/lib/evaluator-report.js +22 -0
  205. package/src/lib/generators/settings-generator.js +208 -209
  206. package/src/lib/installers/mcp-installer.js +724 -719
  207. package/src/lib/installers/memory-installer.js +1 -2
  208. package/src/lib/projects/registry.js +154 -0
  209. package/src/lib/validators/code-budget/code-budget-validator.js +32 -4
  210. package/src/scripts/install-plugin.js +1 -1
  211. package/src/scripts/setup-infra.js +2 -4
  212. package/src/utils/agents-installer.js +57 -12
  213. package/src/utils/banner.js +1 -1
  214. package/src/commands/index.js +0 -13
  215. package/src/commands/project/checkpoint.js +0 -209
  216. package/src/core/state/phase-state-machine.js +0 -197
  217. package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
  218. package/src/dashboard/app/pages/LiveState.jsx +0 -206
  219. package/src/dashboard/scanner/squads-scanner.js +0 -13
  220. package/src/dashboard/scanner/state-scanner.js +0 -85
  221. package/src/lib/generators/index.js +0 -10
  222. package/src/lib/hooks/hook-executor.js +0 -167
  223. package/src/lib/validators/checkpoint-persistence.js +0 -39
  224. package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
  225. package/src/lib/validators/shared/index.js +0 -12
  226. package/src/lib/validators/shared/issue-counter.js +0 -14
  227. package/src/lib/validators/shared/result-formatter.js +0 -124
@@ -0,0 +1,156 @@
1
+ import { useState } from 'react';
2
+ import { Link } from 'react-router-dom';
3
+ import { FolderSimplePlus, X, ArrowRight } from '@phosphor-icons/react';
4
+ import { FeatureRow, isWorking } from './Features.jsx';
5
+ import { useProjects, projectHref, announceProjectsChanged } from '../lib/project.js';
6
+ import { useNow } from '../hooks/usePref.js';
7
+ import { timeAgo } from '../lib/format.js';
8
+
9
+ const ACTIVE_SHOWN = 4;
10
+
11
+ async function send(method, url, body) {
12
+ const res = await fetch(url, {
13
+ method,
14
+ headers: body ? { 'Content-Type': 'application/json' } : undefined,
15
+ body: body ? JSON.stringify(body) : undefined,
16
+ });
17
+ const out = await res.json().catch(() => ({}));
18
+ if (!res.ok) throw new Error(out.error || `HTTP ${res.status}`);
19
+ return out;
20
+ }
21
+
22
+ function ProjectBlock({ p, now, onRemove }) {
23
+ const s = p.summary;
24
+ const working = s ? s.activeFeatures.filter((f) => isWorking(f, now)).length : 0;
25
+ // What waits on you is already listed at the top of the page; here, the rest, latest first.
26
+ const rest = s ? s.activeFeatures.filter((f) => !f.awaiting) : [];
27
+ const shown = [...rest].sort((a, b) => (b.lastActivity?.ts || '').localeCompare(a.lastActivity?.ts || '')).slice(0, ACTIVE_SHOWN);
28
+ return (
29
+ <section className="bezel" aria-label={p.name}>
30
+ <div className="bezel-core overflow-hidden">
31
+ <header className="flex flex-col gap-3 px-5 py-4 md:flex-row md:items-center md:justify-between">
32
+ <div className="min-w-0">
33
+ <Link to={projectHref(p.id)} className="text-lg font-semibold tracking-tight hover:underline underline-offset-4">{p.name}</Link>
34
+ <p className="mt-0.5 truncate font-mono text-xs text-[var(--color-faint)]" title={p.dir}>{p.dir}</p>
35
+ </div>
36
+ <div className="flex flex-wrap items-center gap-x-5 gap-y-1 whitespace-nowrap text-sm md:shrink-0">
37
+ {!s && <span className="text-[var(--color-faint)]">lendo…</span>}
38
+ {s && (
39
+ <>
40
+ <span className="text-[var(--color-dim)]"><span className="font-mono text-[var(--color-fg)]">{s.active}</span> ativas</span>
41
+ {s.awaiting > 0 && <span className="text-[var(--color-warn)]"><span className="font-mono">{s.awaiting}</span> esperando você</span>}
42
+ {working > 0 && <span className="text-[var(--color-phase-implement)]">agente trabalhando</span>}
43
+ <span className="text-xs text-[var(--color-faint)]">{s.lastActivityTs ? `atividade ${timeAgo(s.lastActivityTs, now)}` : `${s.total} features`}</span>
44
+ </>
45
+ )}
46
+ <Link to={projectHref(p.id)} aria-label={`Abrir ${p.name}`}
47
+ className="fluid flex h-8 w-8 items-center justify-center rounded-full bg-white/[0.05] text-[var(--color-dim)] transition-transform hover:translate-x-0.5 hover:text-[var(--color-fg)]">
48
+ <ArrowRight weight="light" size={15} />
49
+ </Link>
50
+ {!p.launch && (
51
+ <button type="button" onClick={() => onRemove(p)} aria-label={`Remover ${p.name} do portfólio`} title="Remover do portfólio (não apaga nada)"
52
+ className="fluid flex h-8 w-8 items-center justify-center rounded-full text-[var(--color-faint)] transition-colors hover:bg-white/[0.05] hover:text-[var(--color-dim)]">
53
+ <X weight="light" size={14} />
54
+ </button>
55
+ )}
56
+ </div>
57
+ </header>
58
+ {s && shown.length > 0 && (
59
+ <ul className="divide-y divide-[var(--color-hair)] border-t border-[var(--color-hair)]">
60
+ {shown.map((f) => <FeatureRow key={f.name} f={f} now={now} project={p.id} />)}
61
+ </ul>
62
+ )}
63
+ {s && rest.length > ACTIVE_SHOWN && (
64
+ <Link to={projectHref(p.id)} className="block border-t border-[var(--color-hair)] px-5 py-2.5 text-sm text-[var(--color-dim)] hover:text-[var(--color-fg)]">
65
+ Ver as {s.active} features ativas
66
+ </Link>
67
+ )}
68
+ {s && s.active === 0 && (
69
+ <p className="border-t border-[var(--color-hair)] px-5 py-3 text-sm text-[var(--color-faint)]">
70
+ {s.legacyStateOnly ? 'Projeto no modelo de estado antigo: rode morph-spec update nele.' : 'Nada em andamento.'}
71
+ </p>
72
+ )}
73
+ </div>
74
+ </section>
75
+ );
76
+ }
77
+
78
+ export function PortfolioView({ data, now = Date.now(), onAdd = () => {}, onRemove = () => {}, busy = null, error = null }) {
79
+ const projects = data.projects || [];
80
+ const ready = projects.filter((p) => p.summary);
81
+ const totalActive = ready.reduce((n, p) => n + p.summary.active, 0);
82
+ const totalAwaiting = ready.reduce((n, p) => n + p.summary.awaiting, 0);
83
+ const awaiting = ready.flatMap((p) => p.summary.activeFeatures.filter((f) => f.awaiting).map((f) => ({ f, p })));
84
+ const ordered = [...projects].sort((a, b) => (b.summary?.lastActivityTs || '').localeCompare(a.summary?.lastActivityTs || ''));
85
+
86
+ return (
87
+ <div className="mx-auto max-w-6xl px-4 pb-24 pt-10 md:px-10 md:pt-14">
88
+ <header className="rise mb-10">
89
+ <h1 className="text-4xl font-semibold tracking-tight md:text-5xl">Portfólio</h1>
90
+ <p className="mt-3 text-sm text-[var(--color-dim)]">
91
+ {projects.length} {projects.length === 1 ? 'projeto' : 'projetos'}, {totalActive} features ativas
92
+ {totalAwaiting > 0 && <span className="text-[var(--color-warn)]">, {totalAwaiting} esperando você</span>}
93
+ </p>
94
+ </header>
95
+
96
+ {awaiting.length > 0 && (
97
+ <section className="rise-2 mb-10">
98
+ <h2 className="mb-3 px-1 text-sm font-medium">Esperando você</h2>
99
+ <div className="bezel">
100
+ <ul className="bezel-core divide-y divide-[var(--color-hair)] overflow-hidden">
101
+ {awaiting.map(({ f, p }) => <FeatureRow key={`${p.id}:${f.name}`} f={f} now={now} project={p.id} projectName={p.name} />)}
102
+ </ul>
103
+ </div>
104
+ </section>
105
+ )}
106
+
107
+ <div className="rise-3 space-y-6">
108
+ {ordered.map((p) => <ProjectBlock key={p.id} p={p} now={now} onRemove={onRemove} />)}
109
+ </div>
110
+
111
+ <section className="mt-14" aria-labelledby="found">
112
+ <h2 id="found" className="px-1 text-sm font-medium">Outros projetos com morph-spec</h2>
113
+ <p className="mb-4 mt-1 px-1 text-sm text-[var(--color-dim)]">
114
+ Encontrados nas pastas vizinhas dos projetos acima. Projetos também entram sozinhos quando você roda
115
+ <code className="mx-1 font-mono text-[var(--color-fg)]">morph-spec init</code>,
116
+ <code className="mx-1 font-mono text-[var(--color-fg)]">update</code> ou
117
+ <code className="mx-1 font-mono text-[var(--color-fg)]">dashboard</code> neles.
118
+ </p>
119
+ {error && <p className="mb-3 px-1 text-sm text-[var(--color-bad)]">{error}</p>}
120
+ {(data.suggestions || []).length === 0 && <p className="px-1 text-sm text-[var(--color-faint)]">Nenhum outro encontrado.</p>}
121
+ <ul className="grid grid-cols-1 gap-2 md:grid-cols-2">
122
+ {(data.suggestions || []).map((s) => (
123
+ <li key={s.dir} className="flex min-w-0 items-center justify-between gap-3 rounded-xl px-4 py-3 shadow-[inset_0_0_0_1px_var(--color-hair)]">
124
+ <div className="min-w-0">
125
+ <p className="truncate text-sm text-[var(--color-fg)]">{s.name}</p>
126
+ <p className="truncate font-mono text-xs text-[var(--color-faint)]" title={s.dir}>{s.dir}</p>
127
+ </div>
128
+ <button type="button" onClick={() => onAdd(s.dir)} disabled={busy === s.dir}
129
+ className="fluid inline-flex shrink-0 items-center gap-1.5 rounded-full bg-white/[0.06] px-3 py-1.5 text-sm text-[var(--color-fg)] transition-colors hover:bg-white/[0.1] active:scale-[0.98] disabled:opacity-50">
130
+ <FolderSimplePlus weight="light" size={15} /> {busy === s.dir ? 'Adicionando…' : 'Adicionar'}
131
+ </button>
132
+ </li>
133
+ ))}
134
+ </ul>
135
+ </section>
136
+ </div>
137
+ );
138
+ }
139
+
140
+ export default function Portfolio() {
141
+ const { data, error } = useProjects();
142
+ const now = useNow();
143
+ const [busy, setBusy] = useState(null);
144
+ const [actionError, setActionError] = useState(null);
145
+ const run = async (dir, fn) => {
146
+ setBusy(dir); setActionError(null);
147
+ // The server also pushes the change; reading again here does not depend on it.
148
+ try { await fn(); announceProjectsChanged(); } catch (err) { setActionError(err.message); } finally { setBusy(null); }
149
+ };
150
+ const onAdd = (dir) => run(dir, () => send('POST', '/api/morph/projects', { dir }));
151
+ const onRemove = (p) => run(p.dir, () => send('DELETE', `/api/morph/projects?dir=${encodeURIComponent(p.dir)}`));
152
+
153
+ if (error) return <p className="px-10 pt-16 text-sm text-[var(--color-bad)]">Não consegui ler o portfólio: {error}</p>;
154
+ if (!data) return <p className="px-10 pt-16 text-sm text-[var(--color-faint)]">Lendo os projetos…</p>;
155
+ return <PortfolioView data={data} now={now} onAdd={onAdd} onRemove={onRemove} busy={busy} error={actionError} />;
156
+ }
@@ -0,0 +1,101 @@
1
+ import morphData from 'virtual:morph-data';
2
+ import KnowledgeMarkdown from '../components/KnowledgeMarkdown.jsx';
3
+ import { useSelection } from '../components/EntityLink.jsx';
4
+
5
+ const DOCS = morphData.docs || { rubrics: [], proofRubrics: [] };
6
+ const README = DOCS.rubrics.find((r) => r.id === 'README');
7
+ const DIMENSIONS = DOCS.rubrics.filter((r) => r.id !== 'README' && r.id !== 'condensed');
8
+ const CONDENSED = DOCS.rubrics.find((r) => r.id === 'condensed');
9
+ const VALIDATORS = morphData.rubrics || [];
10
+
11
+ const GROUPS = [
12
+ { title: 'Dimensões do avaliador', hint: 'o score composto sai destas quatro', items: DIMENSIONS.map((r) => ({ key: `dim:${r.id}`, title: r.title, doc: r })) },
13
+ { title: 'Versão curta', hint: 'usada na avaliação por task', items: CONDENSED ? [{ key: 'dim:condensed', title: CONDENSED.title, doc: CONDENSED }] : [] },
14
+ { title: 'Provas', hint: 'o que o avaliador precisa ver acontecer', items: DOCS.proofRubrics.filter((r) => r.id !== 'README').map((r) => ({ key: `proof:${r.id}`, title: r.title, doc: r })) },
15
+ { title: 'Validadores determinísticos', hint: 'pesos por categoria no verify', items: VALIDATORS.map((v) => ({ key: `val:${v.id}`, title: `Rubrica ${v.id}`, validator: v })) },
16
+ ].filter((g) => g.items.length);
17
+
18
+ const ALL = GROUPS.flatMap((g) => g.items);
19
+
20
+ function ValidatorRubric({ v }) {
21
+ return (
22
+ <article className="prose-morph">
23
+ <h1>Rubrica {v.id}</h1>
24
+ {v.description && <p>{v.description}</p>}
25
+ <table>
26
+ <thead><tr><th>Categoria</th><th>Peso</th><th>Validadores</th><th>O que mede</th></tr></thead>
27
+ <tbody>
28
+ {Object.entries(v.categories).map(([name, c]) => (
29
+ <tr key={name}>
30
+ <td>{name}</td>
31
+ <td className="font-mono">{c.weight ?? '—'}</td>
32
+ <td className="font-mono text-xs">{(c.validators || []).join(', ') || '—'}</td>
33
+ <td>{c.description || ''}</td>
34
+ </tr>
35
+ ))}
36
+ </tbody>
37
+ </table>
38
+ <p>Aprova com <strong>{v.thresholds?.approve ?? '—'}</strong>; abaixo de <strong>{v.thresholds?.retry ?? '—'}</strong> a task volta para correção.</p>
39
+ </article>
40
+ );
41
+ }
42
+
43
+ export function RubricsView({ sel, onSel }) {
44
+ const current = ALL.find((i) => i.key === sel) || null;
45
+ return (
46
+ <div className="mx-auto max-w-7xl px-4 pb-24 pt-10 md:px-10 md:pt-14">
47
+ <header className="rise mb-8">
48
+ <h1 className="text-4xl font-semibold tracking-tight md:text-5xl">Rubricas</h1>
49
+ <p className="mt-3 max-w-2xl text-sm leading-relaxed text-[var(--color-dim)]">
50
+ Com o que o trabalho é julgado. O avaliador independente pontua cada dimensão de 0 a 10, e o Gate 3 exige score composto 9.
51
+ Os validadores rodam no <span className="font-mono text-[var(--color-fg)]">morph-spec verify</span>, sem modelo.
52
+ </p>
53
+ </header>
54
+ <div className="rise-2 grid gap-6 lg:grid-cols-[17rem_minmax(0,1fr)]">
55
+ <nav aria-label="Rubricas" className="lg:sticky lg:top-4 lg:self-start">
56
+ <button
57
+ type="button"
58
+ onClick={() => onSel(null)}
59
+ aria-current={!current ? 'page' : undefined}
60
+ className={['fluid mb-4 block w-full rounded-lg px-3 py-2 text-left text-sm transition-colors', !current ? 'bg-white/[0.08] text-[var(--color-fg)]' : 'text-[var(--color-dim)] hover:bg-white/[0.04]'].join(' ')}
61
+ >
62
+ Como a nota é calculada
63
+ </button>
64
+ {GROUPS.map((g) => (
65
+ <div key={g.title} className="mb-5">
66
+ <p className="px-3 text-xs text-[var(--color-faint)]">{g.title}</p>
67
+ <p className="mb-1.5 px-3 text-[11px] text-[var(--color-faint)]">{g.hint}</p>
68
+ <ul>
69
+ {g.items.map((i) => (
70
+ <li key={i.key}>
71
+ <button
72
+ type="button"
73
+ onClick={() => onSel(i.key)}
74
+ aria-current={current?.key === i.key ? 'page' : undefined}
75
+ className={['fluid block w-full truncate rounded-lg px-3 py-1.5 text-left text-sm transition-colors', current?.key === i.key ? 'bg-white/[0.08] text-[var(--color-fg)]' : 'text-[var(--color-dim)] hover:bg-white/[0.04] hover:text-[var(--color-fg)]'].join(' ')}
76
+ title={i.title}
77
+ >
78
+ {i.title}
79
+ </button>
80
+ </li>
81
+ ))}
82
+ </ul>
83
+ </div>
84
+ ))}
85
+ </nav>
86
+ <div className="bezel min-w-0">
87
+ <div className="bezel-core min-w-0 px-6 py-8 md:px-12 md:py-10">
88
+ {!current && (README ? <KnowledgeMarkdown text={README.content} /> : <p className="text-sm text-[var(--color-dim)]">Escolha uma rubrica.</p>)}
89
+ {current?.doc && <KnowledgeMarkdown text={current.doc.content} />}
90
+ {current?.validator && <ValidatorRubric v={current.validator} />}
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ );
96
+ }
97
+
98
+ export default function Rubrics() {
99
+ const [sel, setSel] = useSelection();
100
+ return <RubricsView sel={sel} onSel={setSel} />;
101
+ }