@polymorphism-tech/morph-spec 8.44.0 → 8.46.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 (199) hide show
  1. package/CLAUDE.md +11 -9
  2. package/README.md +10 -10
  3. package/bin/morph-spec.js +11 -13
  4. package/bin/validate.js +379 -379
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +2 -4
  7. package/docs/QUICKSTART.md +6 -6
  8. package/framework/CLAUDE.md +6 -8
  9. package/framework/MORPH.md +39 -45
  10. package/framework/agents.json +8 -9
  11. package/framework/commands/morph-apply.md +1 -3
  12. package/framework/commands/morph-hotfix.md +1 -1
  13. package/framework/commands/morph-proposal.md +2 -2
  14. package/framework/docs/CLI.md +15 -14
  15. package/framework/evals/review-checklist.md +90 -0
  16. package/framework/evals/trace-schema.md +2 -12
  17. package/framework/hooks/README.md +2 -3
  18. package/framework/hooks/claude-code/core/heavy-gate.js +1 -1
  19. package/framework/hooks/claude-code/core/protect-spec-files.js +2 -2
  20. package/framework/hooks/claude-code/core/telemetry-log.js +2 -2
  21. package/framework/hooks/claude-code/core/trace-autogen.js +1 -22
  22. package/framework/hooks/claude-code/core/validate-completion.js +3 -56
  23. package/framework/hooks/claude-code/statusline.sh +11 -11
  24. package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
  25. package/framework/hooks/git/pre-commit/agents.sh +25 -25
  26. package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
  27. package/framework/hooks/git/pre-commit/specs.sh +50 -50
  28. package/framework/hooks/git/pre-push/run-tests.sh +44 -44
  29. package/framework/hooks/shared/build-check.js +1 -1
  30. package/framework/hooks/shared/comment-scanner.js +1 -1
  31. package/framework/hooks/shared/phase-utils.js +0 -5
  32. package/framework/hooks/shared/stdin-reader.js +26 -26
  33. package/framework/phases.json +4 -6
  34. package/framework/rubrics/backend.json +1 -1
  35. package/framework/rubrics/default.json +1 -1
  36. package/framework/rubrics/frontend.json +1 -1
  37. package/framework/rules/morph-workflow.md +6 -6
  38. package/framework/schemas/config.schema.json +4 -0
  39. package/framework/schemas/feature.schema.json +5 -29
  40. package/framework/schemas/tasks.schema.json +6 -2
  41. package/framework/skills/README.md +1 -2
  42. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  43. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  44. package/framework/skills/core/morph-apply/SKILL.md +145 -246
  45. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  46. package/framework/skills/core/morph-hotfix/SKILL.md +5 -3
  47. package/framework/skills/core/morph-implement/SKILL.md +101 -201
  48. package/framework/skills/core/morph-machine/SKILL.md +3 -3
  49. package/framework/skills/core/morph-media/SKILL.md +1 -1
  50. package/framework/skills/core/morph-plan/SKILL.md +171 -287
  51. package/framework/skills/core/morph-proposal/SKILL.md +4 -5
  52. package/framework/skills/core/morph-review/SKILL.md +122 -430
  53. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  54. package/framework/skills/core/morph-standards/SKILL.md +2 -2
  55. package/framework/skills/core/morph-uiux/SKILL.md +2 -3
  56. package/framework/standards/STANDARDS.json +10 -10
  57. package/framework/standards/ai-agents/agent-archetypes.md +1 -1
  58. package/framework/standards/ai-agents/agent-session.md +1 -1
  59. package/framework/standards/ai-agents/agent-spec.md +1 -1
  60. package/framework/standards/ai-agents/batching.md +1 -1
  61. package/framework/standards/ai-agents/context-providers.md +1 -1
  62. package/framework/standards/ai-agents/conversational-agent-with-phases.md +1 -1
  63. package/framework/standards/ai-agents/cost-and-budget.md +1 -1
  64. package/framework/standards/ai-agents/durable-workflows-hitl.md +1 -1
  65. package/framework/standards/ai-agents/guardrails.md +1 -1
  66. package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
  67. package/framework/standards/ai-agents/mcp-server.md +1 -1
  68. package/framework/standards/ai-agents/mcp-tools.md +1 -1
  69. package/framework/standards/ai-agents/media-pipeline.md +1 -1
  70. package/framework/standards/ai-agents/media-video.md +1 -1
  71. package/framework/standards/ai-agents/middleware-patterns.md +1 -1
  72. package/framework/standards/ai-agents/modalities-image-gen.md +1 -1
  73. package/framework/standards/ai-agents/modalities-speech-to-text.md +1 -1
  74. package/framework/standards/ai-agents/modalities-text-to-speech.md +1 -1
  75. package/framework/standards/ai-agents/modalities-vision.md +1 -1
  76. package/framework/standards/ai-agents/multi-agent-patterns.md +1 -1
  77. package/framework/standards/ai-agents/observability-patterns.md +3 -3
  78. package/framework/standards/ai-agents/production.md +2 -2
  79. package/framework/standards/ai-agents/prompt-sources.md +1 -1
  80. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  81. package/framework/standards/ai-agents/rag-custom-pgvector.md +1 -1
  82. package/framework/standards/ai-agents/service-tier-flex.md +1 -1
  83. package/framework/standards/ai-agents/setup.md +1 -1
  84. package/framework/standards/ai-agents/structured-output.md +1 -1
  85. package/framework/standards/ai-agents/sweet-spot.md +1 -1
  86. package/framework/standards/ai-agents/testing-ai.md +1 -1
  87. package/framework/standards/ai-agents/tool-adapters.md +1 -1
  88. package/framework/standards/ai-agents/vector-data-extensions.md +1 -1
  89. package/framework/standards/ai-agents/workflows.md +1 -1
  90. package/framework/templates/CLAUDE.md.core.template +1 -1
  91. package/framework/templates/IDE-SNIPPETS.md +262 -262
  92. package/framework/templates/README.md +1 -1
  93. package/framework/templates/REGISTRY.json +0 -81
  94. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  95. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  96. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  97. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  98. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  99. package/framework/templates/docs/spec.md +19 -205
  100. package/framework/templates/docs/ui-components.md +52 -52
  101. package/framework/templates/docs/ui-design-system.md +76 -76
  102. package/framework/templates/docs/ui-flows.md +167 -167
  103. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  104. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  105. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  106. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  107. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  108. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  109. package/framework/templates/infrastructure/github/README.md +56 -56
  110. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  111. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  112. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  113. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  114. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  115. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  116. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  117. package/framework/templates/state.template.json +17 -17
  118. package/framework/templates/ui/design-system.css +226 -226
  119. package/package.json +2 -2
  120. package/src/commands/e2e/e2e.js +1 -1
  121. package/src/commands/project/heavy.js +1 -1
  122. package/src/commands/project/status.js +3 -3
  123. package/src/commands/state/approve.js +7 -49
  124. package/src/commands/state/gate-check.js +16 -106
  125. package/src/commands/state/gate-decision.js +1 -1
  126. package/src/commands/state/report-check.js +42 -17
  127. package/src/commands/validation/mutate.js +1 -1
  128. package/src/commands/validation/scout-check.js +28 -0
  129. package/src/commands/validation/validate-feature.js +5 -32
  130. package/src/commands/validation/verify.js +98 -48
  131. package/src/core/paths/output-schema.js +0 -2
  132. package/src/core/state/state-manager.js +1 -2
  133. package/src/core/templates/template-renderer.js +477 -477
  134. package/src/dashboard/app/lib/format.js +5 -0
  135. package/src/dashboard/app/main.jsx +13 -13
  136. package/src/dashboard/app/pages/FeatureDetail.jsx +16 -22
  137. package/src/dashboard/app/pages/Features.jsx +7 -7
  138. package/src/dashboard/app/pages/PhasePipeline.jsx +1 -1
  139. package/src/dashboard/app/pages/Rubrics.jsx +7 -11
  140. package/src/dashboard/scanner/docs-scanner.js +1 -2
  141. package/src/dashboard/scanner/features-scanner.js +3 -4
  142. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  143. package/src/dashboard/vite.config.js +24 -24
  144. package/src/lib/agents/output-collector.js +0 -1
  145. package/src/lib/ci/ci-verify.js +118 -0
  146. package/src/lib/ci/github-ci.js +192 -0
  147. package/src/lib/ci/workflow-scaffold.js +122 -0
  148. package/src/lib/detectors/design-system-detector.js +188 -188
  149. package/src/lib/evaluator-report.js +52 -334
  150. package/src/lib/generators/design-system-generator.js +508 -508
  151. package/src/lib/generators/handoff-generator.js +4 -5
  152. package/src/lib/installers/memory-installer.js +2 -2
  153. package/src/lib/mutation/manifest.js +8 -2
  154. package/src/lib/mutation/report.js +5 -3
  155. package/src/lib/mutation/runner.js +9 -7
  156. package/src/lib/risk-detector.js +60 -122
  157. package/src/lib/scout/scout-check.js +293 -0
  158. package/src/lib/tasks/task-classifier.js +30 -30
  159. package/src/lib/tasks/task-parser.js +209 -209
  160. package/src/lib/tasks/tasks-json-parser.js +7 -5
  161. package/src/lib/tasks/test-nodes.js +1 -1
  162. package/src/lib/tasks/test-runner.js +1 -1
  163. package/src/lib/telemetry/verify-event.js +3 -2
  164. package/src/lib/validators/css/css-validator.js +340 -340
  165. package/src/lib/validators/reuse/reuse-validator.js +6 -8
  166. package/src/lib/validators/verify-runner.js +1 -1
  167. package/src/scripts/setup-infra.js +1 -1
  168. package/src/utils/agent-id-mapper.js +11 -11
  169. package/src/utils/agents-installer.js +5 -9
  170. package/src/utils/claude-settings-manager.js +300 -300
  171. package/src/utils/hooks-installer.js +3 -10
  172. package/src/utils/skills-installer.js +1 -1
  173. package/framework/evals/proof-rubrics/README.md +0 -76
  174. package/framework/evals/proof-rubrics/eval-de-modelo.md +0 -80
  175. package/framework/evals/proof-rubrics/fiacao.md +0 -54
  176. package/framework/evals/proof-rubrics/guard-cego.md +0 -52
  177. package/framework/evals/proof-rubrics/media-claims.md +0 -109
  178. package/framework/evals/proof-rubrics/mutation-proof.md +0 -107
  179. package/framework/evals/proof-rubrics/observabilidade-genai.md +0 -119
  180. package/framework/evals/proof-rubrics/verde-nao-e-coberto.md +0 -48
  181. package/framework/evals/rubrics/README.md +0 -45
  182. package/framework/evals/rubrics/architecture.md +0 -103
  183. package/framework/evals/rubrics/code-quality.md +0 -98
  184. package/framework/evals/rubrics/condensed.md +0 -57
  185. package/framework/evals/rubrics/contracts.md +0 -67
  186. package/framework/evals/rubrics/test-coverage.md +0 -99
  187. package/framework/evals/scenarios/dotnet-crud.yaml +0 -42
  188. package/framework/evals/scenarios/nextjs-auth.yaml +0 -38
  189. package/framework/evals/suites/standard.yaml +0 -5
  190. package/framework/hooks/claude-code/__pycache__/statusline.cpython-313.pyc +0 -0
  191. package/framework/hooks/claude-code/core/post-edit-typecheck.js +0 -127
  192. package/framework/skills/core/morph-eval/SKILL.md +0 -64
  193. package/framework/templates/.idea/morph-templates.xml +0 -92
  194. package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
  195. package/framework/templates/docs/clarifications.md +0 -253
  196. package/framework/templates/docs/onboarding.md +0 -122
  197. package/framework/templates/docs/schema-analysis.md +0 -119
  198. package/framework/templates/docs/user-stories.md +0 -34
  199. package/src/commands/state/score.js +0 -147
@@ -67,3 +67,8 @@ export function splitFrontmatter(text) {
67
67
  }
68
68
  return { meta, body: text.slice(m[0].length) };
69
69
  }
70
+
71
+ export function verdictLabel(f) {
72
+ if (!f?.verdict) return null;
73
+ return f.blockers ? `${f.verdict} · ${f.blockers} bloq.` : f.verdict;
74
+ }
@@ -1,13 +1,13 @@
1
- import { StrictMode } from 'react';
2
- import { createRoot } from 'react-dom/client';
3
- import { BrowserRouter } from 'react-router-dom';
4
- import App from './App.jsx';
5
- import './styles/index.css';
6
-
7
- createRoot(document.getElementById('root')).render(
8
- <StrictMode>
9
- <BrowserRouter>
10
- <App />
11
- </BrowserRouter>
12
- </StrictMode>
13
- );
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { BrowserRouter } from 'react-router-dom';
4
+ import App from './App.jsx';
5
+ import './styles/index.css';
6
+
7
+ createRoot(document.getElementById('root')).render(
8
+ <StrictMode>
9
+ <BrowserRouter>
10
+ <App />
11
+ </BrowserRouter>
12
+ </StrictMode>
13
+ );
@@ -13,7 +13,7 @@ import { usePref, useNow } from '../hooks/usePref.js';
13
13
  import { Segmented, isWorking } from './Features.jsx';
14
14
  import { apiBase, projectHref } from '../lib/project.js';
15
15
  import { PHASES, GATE_LABEL, STATUS_LABEL, LOCATION_LABEL, phaseMeta } from '../lib/phases.js';
16
- import { timeAgo, dateTime, num, duration, bytes } from '../lib/format.js';
16
+ import { timeAgo, dateTime, num, duration, bytes, verdictLabel } from '../lib/format.js';
17
17
 
18
18
  const TABS = [
19
19
  { id: 'overview', label: 'Visão geral' },
@@ -77,7 +77,7 @@ export function nextAction(f) {
77
77
  if (f.status === 'abandoned') return 'Abandonada.';
78
78
  const paused = f.gates.find((g) => g.paused);
79
79
  if (paused) return `O ${GATE_LABEL[paused.gate] || paused.gate} pausou${paused.reason ? `: ${paused.reason}` : ''}. A decisão é sua, no Claude Code.`;
80
- if (f.inEvaluation) return 'O avaliador independente entregou o relatório. O Gate 3 decide pelo score.';
80
+ if (f.inEvaluation) return 'O avaliador independente entregou o relatório. O Gate 3 decide pelo veredito e pelos bloqueadores.';
81
81
  if (f.phase === 'implement' && f.tasksList?.length) {
82
82
  const open = f.tasksList.find((t) => t.status === 'in_progress') || f.tasksList.find((t) => t.status === 'pending');
83
83
  if (open) return `${f.tasks.done} de ${f.tasks.total} tasks feitas. Próxima: ${open.id}, ${open.title}.`;
@@ -149,24 +149,20 @@ function NodeChips({ nodes }) {
149
149
  );
150
150
  }
151
151
 
152
- function ScoreBlock({ f }) {
153
- const score = f.score ?? f.legacyScore;
154
- const proofs = f.proofScores ? Object.entries(f.proofScores) : [];
152
+ function VerdictBlock({ f }) {
155
153
  return (
156
154
  <div>
157
155
  <p className="flex items-baseline gap-2">
158
- <span className="text-4xl font-semibold tabular-nums tracking-tight">{score ?? '—'}</span>
159
- <span className="text-sm text-[var(--color-dim)]">{score == null ? 'sem relatório do avaliador' : f.score != null ? 'Composite-Score do avaliador' : 'score legado'}</span>
156
+ <span className="text-4xl font-semibold tracking-tight">{f.verdict ?? '—'}</span>
157
+ <span className="text-sm text-[var(--color-dim)]">{f.verdict ? `${f.blockers ?? 0} bloqueador(es)` : 'sem veredito do avaliador'}</span>
160
158
  </p>
161
- {proofs.length > 0 && (
162
- <ul className="mt-4 space-y-1.5">
163
- {proofs.map(([k, v]) => (
164
- <li key={k} className="grid grid-cols-[5.5rem_minmax(0,1fr)_2rem] items-center gap-3 text-xs">
165
- <span className="text-[var(--color-dim)]">{k}</span>
166
- <span className="h-1 overflow-hidden rounded-full bg-white/[0.07]">
167
- <span className="block h-full rounded-full" style={{ width: `${v * 10}%`, background: v >= 9 ? 'var(--color-ok)' : v >= 7 ? 'var(--color-phase-plan)' : 'var(--color-warn)' }} />
168
- </span>
169
- <span className="text-right font-mono tabular-nums text-[var(--color-dim)]">{v}</span>
159
+ {f.blockerList?.length > 0 && (
160
+ <ul className="mt-4 space-y-1.5 text-xs">
161
+ {f.blockerList.map((b) => (
162
+ <li key={b.id} className="grid grid-cols-[3rem_6rem_minmax(0,1fr)] gap-3">
163
+ <span className="font-mono text-[var(--color-dim)]">{b.id}</span>
164
+ <span className="text-[var(--color-warn)]">{b.type}</span>
165
+ <span className="truncate text-[var(--color-dim)]" title={b.evidence}>{b.where}</span>
170
166
  </li>
171
167
  ))}
172
168
  </ul>
@@ -212,7 +208,7 @@ function OverviewTab({ f, cost }) {
212
208
  <Panel title="Gates"><GateList f={f} /></Panel>
213
209
  </div>
214
210
  <div className="space-y-5">
215
- <Panel title="Avaliação"><ScoreBlock f={f} /></Panel>
211
+ <Panel title="Avaliação"><VerdictBlock f={f} /></Panel>
216
212
  <Panel title="Último verify">
217
213
  {f.verification && (
218
214
  <p className="mb-3 text-xs text-[var(--color-faint)]">
@@ -256,7 +252,7 @@ function TaskRow({ t, f, open, onToggle, duration: secs, ready }) {
256
252
  <span className="hidden text-xs md:block">
257
253
  {verify ? <span className={verify.status === 'pass' ? 'text-[var(--color-ok)]' : 'text-[var(--color-bad)]'}>verify {verify.status}</span> : <span className="text-[var(--color-faint)]">—</span>}
258
254
  </span>
259
- <span className="hidden text-right font-mono text-xs tabular-nums text-[var(--color-dim)] md:block">{t.score?.lastScore ?? verify?.score ?? '—'}</span>
255
+ <span className="hidden text-right font-mono text-xs tabular-nums text-[var(--color-dim)] md:block">{verify?.status ?? '—'}</span>
260
256
  <span className="hidden text-[var(--color-faint)] md:block">{open ? <CaretDown weight="light" size={14} /> : <CaretRight weight="light" size={14} />}</span>
261
257
  </button>
262
258
  {open && (
@@ -320,7 +316,6 @@ const BOARD_COLUMNS = [
320
316
 
321
317
  function TaskCard({ t, f, secs, ready }) {
322
318
  const reports = reportPathFor(f, t.id);
323
- const score = t.score?.lastScore ?? t.verification?.score;
324
319
  return (
325
320
  <li className="rounded-xl bg-[var(--color-raised)] px-3.5 py-3 shadow-[inset_0_0_0_1px_var(--color-hair),inset_0_1px_0_rgb(255_255_255_/_0.05)]">
326
321
  <p className="flex items-baseline gap-2">
@@ -335,7 +330,6 @@ function TaskCard({ t, f, secs, ready }) {
335
330
  <div className="mt-2.5 flex items-center justify-between text-xs">
336
331
  <span className="flex items-center gap-2">
337
332
  {t.verification && <span className={t.verification.status === 'pass' ? 'text-[var(--color-ok)]' : 'text-[var(--color-bad)]'}>verify {t.verification.status}</span>}
338
- {score != null && <span className="font-mono text-[var(--color-dim)]">{score}</span>}
339
333
  </span>
340
334
  <span className="text-[var(--color-faint)]">{secs ? duration(secs) : ''}</span>
341
335
  </div>
@@ -488,7 +482,7 @@ function DocsTab({ f, doc, onDoc, project }) {
488
482
  }
489
483
 
490
484
  export function FeatureDetailView({ f, tab, onTab, doc, onDoc, cost, latest = null, tasksView = 'list', onTasksView = () => {}, now = Date.now(), project, projectName }) {
491
- const score = f.score ?? f.legacyScore;
485
+ const verdict = verdictLabel(f);
492
486
  const working = latest && isWorking({ ...f, lastActivity: latest }, now);
493
487
  return (
494
488
  <div className="mx-auto max-w-6xl px-4 pb-24 pt-8 md:px-10 md:pt-12">
@@ -507,7 +501,7 @@ export function FeatureDetailView({ f, tab, onTab, doc, onDoc, cost, latest = nu
507
501
  </div>
508
502
  <div className="flex shrink-0 gap-6 text-right">
509
503
  <div><p className="text-2xl font-semibold tabular-nums">{f.tasks.total ? `${f.tasks.done}/${f.tasks.total}` : '—'}</p><p className="text-xs text-[var(--color-faint)]">tasks</p></div>
510
- <div><p className="text-2xl font-semibold tabular-nums">{score ?? '—'}</p><p className="text-xs text-[var(--color-faint)]">score</p></div>
504
+ <div><p className="text-2xl font-semibold">{verdict ?? '—'}</p><p className="text-xs text-[var(--color-faint)]">veredito</p></div>
511
505
  </div>
512
506
  </div>
513
507
  <div className="mt-8"><PhaseRail feature={f} size="lg" /></div>
@@ -8,7 +8,7 @@ import PhaseRail from '../components/PhaseRail.jsx';
8
8
  import { useMorphQuery } from '../hooks/useMorphQuery.js';
9
9
  import { usePref, useNow } from '../hooks/usePref.js';
10
10
  import { PHASES, phaseMeta, LOCATION_LABEL } from '../lib/phases.js';
11
- import { timeAgo } from '../lib/format.js';
11
+ import { timeAgo, verdictLabel } from '../lib/format.js';
12
12
  import { apiBase, featureHref } from '../lib/project.js';
13
13
  import { isFinished } from '../../scanner/feature-status.js';
14
14
 
@@ -21,7 +21,7 @@ const LOCATION_ICON = { worktree: GitBranch, archive: Archive, root: HouseSimple
21
21
  const SORTS = {
22
22
  updated: { label: 'Mais recentes', cmp: (a, b) => (lastTouch(b) || '').localeCompare(lastTouch(a) || '') },
23
23
  name: { label: 'Nome', cmp: (a, b) => a.name.localeCompare(b.name) },
24
- score: { label: 'Score', cmp: (a, b) => (b.score ?? b.legacyScore ?? -1) - (a.score ?? a.legacyScore ?? -1) },
24
+ verdict: { label: 'Veredito', cmp: (a, b) => (a.blockers ?? 99) - (b.blockers ?? 99) },
25
25
  };
26
26
 
27
27
  function lastTouch(f) {
@@ -83,7 +83,7 @@ function subline(f, now) {
83
83
 
84
84
  export function FeatureRow({ f, now, project, projectName }) {
85
85
  const LocIcon = LOCATION_ICON[f.location] || HouseSimple;
86
- const score = f.score ?? f.legacyScore;
86
+ const verdict = verdictLabel(f);
87
87
  return (
88
88
  <li>
89
89
  <Link
@@ -108,8 +108,8 @@ export function FeatureRow({ f, now, project, projectName }) {
108
108
  <div className="flex items-center gap-5 md:contents">
109
109
  <TaskMeter tasks={f.tasks} />
110
110
  <VerifyMark verification={f.verification} />
111
- <span className="font-mono text-sm tabular-nums text-[var(--color-dim)]" title={score != null ? 'score do avaliador' : 'sem score'}>
112
- {score != null ? score : '—'}
111
+ <span className="font-mono text-sm tabular-nums text-[var(--color-dim)]" title={verdict ? 'veredito do avaliador' : 'sem veredito'}>
112
+ {verdict ?? '—'}
113
113
  </span>
114
114
  <span className="flex items-center gap-1.5 text-xs text-[var(--color-faint)]" title={LOCATION_LABEL[f.location]}>
115
115
  <LocIcon weight="light" size={14} />
@@ -141,7 +141,7 @@ export function Section({ title, hint, features, now, project }) {
141
141
  }
142
142
 
143
143
  function BoardCard({ f, now, project }) {
144
- const score = f.score ?? f.legacyScore;
144
+ const verdict = verdictLabel(f);
145
145
  const paused = f.gates.find((g) => g.paused);
146
146
  return (
147
147
  <li>
@@ -169,7 +169,7 @@ function BoardCard({ f, now, project }) {
169
169
  <div className="mt-2.5 flex items-center justify-between text-xs text-[var(--color-faint)]">
170
170
  <span className="flex items-center gap-2">
171
171
  <VerifyMark verification={f.verification} />
172
- {score != null && <span className="font-mono tabular-nums text-[var(--color-dim)]">{score}</span>}
172
+ {verdict && <span className="font-mono tabular-nums text-[var(--color-dim)]">{verdict}</span>}
173
173
  {f.workType && f.workType !== 'feature' && <span>{f.workType}</span>}
174
174
  </span>
175
175
  <span>{timeAgo(lastTouch(f), now)}</span>
@@ -14,7 +14,7 @@ const GATE_CHECKS = {
14
14
  proposal: 'Confere o escopo da proposta: o problema, o que entra e o que fica de fora, antes de gastar esforço em design ou plano.',
15
15
  uiux: 'Confere o design das telas (mockups e checklist de tela). É opcional: só existe quando a feature passa pela fase de UI/UX.',
16
16
  plan: 'Confere a spec, as tasks e o plano de implementação. Depois dele a spec congela e a implementação começa.',
17
- review: 'Confere a avaliação independente: um avaliador em contexto novo dá o score, que precisa ser pelo menos 9, com o verify verde.',
17
+ review: 'Confere a avaliação independente: um avaliador em contexto novo dá o veredito (SHIP ou FIX) e só bloqueia com evidência, com o verify verde.',
18
18
  };
19
19
 
20
20
  const GATE_PREFIX = 'gate-';
@@ -2,16 +2,12 @@ import morphData from 'virtual:morph-data';
2
2
  import KnowledgeMarkdown from '../components/KnowledgeMarkdown.jsx';
3
3
  import { useSelection } from '../components/EntityLink.jsx';
4
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');
5
+ const DOCS = morphData.docs || { rubrics: [] };
6
+ const CHECKLIST = DOCS.rubrics.find((r) => r.id === 'review-checklist');
9
7
  const VALIDATORS = morphData.rubrics || [];
10
8
 
11
9
  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 })) },
10
+ { title: 'Avaliador do Gate 3', hint: 'veredito e bloqueadores com evidência', items: CHECKLIST ? [{ key: 'dim:review-checklist', title: CHECKLIST.title, doc: CHECKLIST }] : [] },
15
11
  { 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
12
  ].filter((g) => g.items.length);
17
13
 
@@ -35,7 +31,7 @@ function ValidatorRubric({ v }) {
35
31
  ))}
36
32
  </tbody>
37
33
  </table>
38
- <p>Aprova com <strong>{v.thresholds?.approve ?? '—'}</strong>; abaixo de <strong>{v.thresholds?.retry ?? '—'}</strong> a task volta para correção.</p>
34
+ <p>O score é informativo: o nó de validadores do verify reprova por erro de validador, não pelo número.</p>
39
35
  </article>
40
36
  );
41
37
  }
@@ -47,7 +43,7 @@ export function RubricsView({ sel, onSel }) {
47
43
  <header className="rise mb-8">
48
44
  <h1 className="text-4xl font-semibold tracking-tight md:text-5xl">Rubricas</h1>
49
45
  <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.
46
+ Com o que o trabalho é julgado. O avaliador independente decide SHIP ou FIX, e só bloqueia com evidência de UC, teste, regressão ou segurança.
51
47
  Os validadores rodam no <span className="font-mono text-[var(--color-fg)]">morph-spec verify</span>, sem modelo.
52
48
  </p>
53
49
  </header>
@@ -59,7 +55,7 @@ export function RubricsView({ sel, onSel }) {
59
55
  aria-current={!current ? 'page' : undefined}
60
56
  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
57
  >
62
- Como a nota é calculada
58
+ Como o Gate 3 decide
63
59
  </button>
64
60
  {GROUPS.map((g) => (
65
61
  <div key={g.title} className="mb-5">
@@ -85,7 +81,7 @@ export function RubricsView({ sel, onSel }) {
85
81
  </nav>
86
82
  <div className="bezel min-w-0">
87
83
  <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>)}
84
+ {!current && (CHECKLIST ? <KnowledgeMarkdown text={CHECKLIST.content} /> : <p className="text-sm text-[var(--color-dim)]">Escolha uma rubrica.</p>)}
89
85
  {current?.doc && <KnowledgeMarkdown text={current.doc.content} />}
90
86
  {current?.validator && <ValidatorRubric v={current.validator} />}
91
87
  </div>
@@ -53,7 +53,6 @@ export function scanDocs(frameworkDir) {
53
53
 
54
54
  return {
55
55
  guide,
56
- rubrics: mdDir(join(frameworkDir, 'evals', 'rubrics')),
57
- proofRubrics: mdDir(join(frameworkDir, 'evals', 'proof-rubrics')),
56
+ rubrics: mdDir(join(frameworkDir, 'evals')).filter((d) => d.id === 'review-checklist'),
58
57
  };
59
58
  }
@@ -221,8 +221,8 @@ function summarize(name, resolved) {
221
221
  verification: feature.verification
222
222
  ? { status: feature.verification.status ?? null, scope: feature.verification.scope ?? null, ts: feature.verification.ts ?? null }
223
223
  : null,
224
- score: verdict?.score ?? null,
225
- legacyScore: typeof feature.reviewScore === 'number' ? feature.reviewScore : null,
224
+ verdict: verdict?.verdict ?? null,
225
+ blockers: verdict?.verdict ? verdict.blockers.length : null,
226
226
  createdAt: feature.createdAt || null,
227
227
  updatedAt: feature.updatedAt || feature.archivedAt || feature.createdAt || null,
228
228
  archivedAt: feature.archivedAt || null,
@@ -325,13 +325,12 @@ export function readFeatureDetail(projectDir, name, { resolveTree = resolveFeatu
325
325
  tasksList: tasks.list.map((t) => ({
326
326
  ...t,
327
327
  verification: feature.taskVerifications?.[t.id] || null,
328
- score: feature.taskScores?.[t.id] || null,
329
328
  })),
330
329
  tasksErrors: tasks.errors,
331
330
  gateDecisions: feature.gateDecisions || {},
332
331
  verificationNodes: feature.verification?.nodes || null,
333
332
  featureVerification: feature.verifications?.feature || null,
334
- proofScores: verdict?.proofScores ?? null,
333
+ blockerList: verdict?.blockers ?? [],
335
334
  documents: listDocuments(resolved.dir),
336
335
  raw: resolved.feature,
337
336
  };
@@ -1,12 +1,12 @@
1
- import { readFileSync, readdirSync, existsSync } from 'fs';
2
- import { join } from 'path';
3
-
4
- export function scanRubrics(frameworkDir) {
5
- const dir = join(frameworkDir, 'rubrics');
6
- if (!existsSync(dir)) return [];
7
- return readdirSync(dir).filter(f => f.endsWith('.json')).map(f => {
8
- const d = JSON.parse(readFileSync(join(dir, f), 'utf8'));
9
- return { id: d.id, version: d.version || '1.0.0', description: d.description || '',
10
- categories: d.categories || {}, thresholds: d.thresholds || { approve: 9.0, retry: 6.0 } };
11
- });
12
- }
1
+ import { readFileSync, readdirSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+
4
+ export function scanRubrics(frameworkDir) {
5
+ const dir = join(frameworkDir, 'rubrics');
6
+ if (!existsSync(dir)) return [];
7
+ return readdirSync(dir).filter(f => f.endsWith('.json')).map(f => {
8
+ const d = JSON.parse(readFileSync(join(dir, f), 'utf8'));
9
+ return { id: d.id, version: d.version || '1.0.0', description: d.description || '',
10
+ categories: d.categories || {}, thresholds: d.thresholds || { approve: 9.0, retry: 6.0 } };
11
+ });
12
+ }
@@ -1,24 +1,24 @@
1
- import { defineConfig } from 'vite';
2
- import react from '@vitejs/plugin-react';
3
- import tailwindcss from '@tailwindcss/vite';
4
- import morphDataPlugin from './vite-plugin-morph-data.js';
5
- import { fileURLToPath } from 'url';
6
- import { dirname, resolve } from 'path';
7
-
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = dirname(__filename);
10
-
11
- const frameworkDir = resolve(__dirname, '..', '..', 'framework');
12
- const projectDir = process.env.MORPH_PROJECT_DIR || process.cwd();
13
-
14
- export default defineConfig({
15
- root: __dirname,
16
- plugins: [
17
- react(),
18
- tailwindcss(),
19
- morphDataPlugin(frameworkDir, projectDir),
20
- ],
21
- server: {
22
- port: 3200,
23
- },
24
- });
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import tailwindcss from '@tailwindcss/vite';
4
+ import morphDataPlugin from './vite-plugin-morph-data.js';
5
+ import { fileURLToPath } from 'url';
6
+ import { dirname, resolve } from 'path';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+
11
+ const frameworkDir = resolve(__dirname, '..', '..', 'framework');
12
+ const projectDir = process.env.MORPH_PROJECT_DIR || process.cwd();
13
+
14
+ export default defineConfig({
15
+ root: __dirname,
16
+ plugins: [
17
+ react(),
18
+ tailwindcss(),
19
+ morphDataPlugin(frameworkDir, projectDir),
20
+ ],
21
+ server: {
22
+ port: 3200,
23
+ },
24
+ });
@@ -16,7 +16,6 @@ export const SOURCE_WEIGHTS = {
16
16
  proposal: 1.0,
17
17
  stackScan: 0.8,
18
18
  spec: 1.5,
19
- mandate: 1.3,
20
19
  tasks: 1.3,
21
20
  decisions: 1.0,
22
21
  uiDesignSystem: 1.0,
@@ -0,0 +1,118 @@
1
+ import { execFileSync } from 'child_process';
2
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { getHeadSha } from '../git/default-branch.js';
5
+ import { treeMovedSince } from '../git/evidence-staleness.js';
6
+ import { resolveGh } from '../git/gh.js';
7
+ import {
8
+ CI_WORKFLOW_DIR, CI_WORKFLOW_FILE, ciAvailability, downloadResult, findCiWorkflow, pushBranch,
9
+ uncommittedCode, waitForRun, workflowPushProblem,
10
+ } from './github-ci.js';
11
+ import { renderCiWorkflow } from './workflow-scaffold.js';
12
+
13
+ export const CI_WAIT_MS = 50 * 60 * 1000;
14
+ const APPEAR_MS = Number(process.env.MORPH_CI_APPEAR_MS ?? 90 * 1000);
15
+
16
+ export class CiUsageError extends Error {}
17
+
18
+ const NODE_KEYS = ['build', 'tests', 'validators', 'e2e'];
19
+
20
+ function isVerifyResult(result) {
21
+ return Boolean(result) && ['pass', 'fail'].includes(result.status) && result.scope === 'feature'
22
+ && NODE_KEYS.every((k) => typeof result.nodes?.[k]?.status === 'string');
23
+ }
24
+
25
+ function scaffoldWorkflow(root, config) {
26
+ const rel = `${CI_WORKFLOW_DIR}/${CI_WORKFLOW_FILE}`;
27
+ const abs = join(root, rel);
28
+ if (existsSync(abs)) return { error: `${rel} já existe e não chama morph-spec verify publicando o JSON` };
29
+ try {
30
+ mkdirSync(join(root, CI_WORKFLOW_DIR), { recursive: true });
31
+ writeFileSync(abs, renderCiWorkflow(root, config));
32
+ execFileSync('git', ['add', '--', rel], { cwd: root, stdio: 'pipe' });
33
+ execFileSync('git', ['commit', '-m', 'ci: workflow morph-verify (prova do Gate 3 no CI)', '--', rel], { cwd: root, stdio: 'pipe' });
34
+ return { path: rel };
35
+ } catch (err) {
36
+ return { error: `não consegui criar ${rel}: ${(err.stderr?.toString() || err.message).trim()}` };
37
+ }
38
+ }
39
+
40
+ function dispatchWorkflow(root, workflow, branch) {
41
+ try {
42
+ const { bin, prefix } = resolveGh();
43
+ execFileSync(bin, [...prefix, 'workflow', 'run', workflow, '--ref', branch], { cwd: root, stdio: 'pipe', timeout: 15000 });
44
+ return true;
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * `verify {f} --ci`: the feature-scope proof, run by the project's CI over the pushed commit.
52
+ * Every way CI cannot answer is `mode: 'local'` with the reason, and the caller runs the local
53
+ * verify: no CI is never a pause. Only the uploaded `verify --json` becomes a result, so the
54
+ * gate judges a CI run by the same stamp and the same rules as a local one.
55
+ *
56
+ * @returns {Promise<
57
+ * { mode: 'local', reason: string } |
58
+ * { mode: 'pending', reason: string, run: object|null } |
59
+ * { mode: 'ci', result: object, run: object, created: string|null }>}
60
+ * @throws {CiUsageError} code CI would not see (uncommitted work)
61
+ */
62
+ export async function runCiVerify(root, featureName, config, { log = () => {}, waitMs = CI_WAIT_MS, wait = waitForRun } = {}) {
63
+ const avail = ciAvailability(root, featureName, config);
64
+ if (!avail.ok) return { mode: 'local', reason: avail.reason };
65
+
66
+ const dirty = uncommittedCode(root);
67
+ if (dirty === null) return { mode: 'local', reason: 'git status não respondeu' };
68
+ if (dirty.length > 0) {
69
+ const shown = dirty.slice(0, 5).join(', ') + (dirty.length > 5 ? ` (+${dirty.length - 5})` : '');
70
+ throw new CiUsageError(`o CI só vê commits e há trabalho fora deles: ${shown}. Commite na branch da feature (git add <paths> && git commit) e rode de novo, ou rode sem --ci`);
71
+ }
72
+
73
+ let workflow = findCiWorkflow(root);
74
+ let created = null;
75
+ if (!workflow) {
76
+ const problem = workflowPushProblem(avail);
77
+ if (problem) return { mode: 'local', reason: problem };
78
+ const scaffold = scaffoldWorkflow(root, config);
79
+ if (scaffold.error) return { mode: 'local', reason: scaffold.error };
80
+ created = scaffold.path;
81
+ workflow = CI_WORKFLOW_FILE;
82
+ log(`workflow criado e commitado: ${created}`);
83
+ }
84
+
85
+ const pushError = pushBranch(root, avail.branch);
86
+ if (pushError) return { mode: 'local', reason: `push de ${avail.branch} falhou: ${pushError}` };
87
+
88
+ const head = getHeadSha(root);
89
+ const waitOpts = {
90
+ branch: avail.branch,
91
+ workflow,
92
+ isCurrent: (sha) => !treeMovedSince(root, sha, head),
93
+ timeoutMs: waitMs,
94
+ appearMs: APPEAR_MS,
95
+ onPending: (run) => log(`CI rodando: ${run.url}`),
96
+ };
97
+ let outcome = await wait(root, waitOpts);
98
+ if (outcome.state === 'no-run' && dispatchWorkflow(root, workflow, avail.branch)) {
99
+ log(`nenhum run para este commit; disparei ${workflow} manualmente`);
100
+ outcome = await wait(root, waitOpts);
101
+ }
102
+ if (outcome.state === 'no-run') return { mode: 'local', reason: `${workflow} não rodou para este commit` };
103
+ if (outcome.state === 'unknown') return { mode: 'local', reason: 'gh run list não respondeu' };
104
+ if (outcome.state === 'timeout') {
105
+ return { mode: 'pending', reason: `o CI não terminou em ${Math.round(waitMs / 60000)} min`, run: outcome.run ?? null };
106
+ }
107
+
108
+ const { run } = outcome;
109
+ const result = downloadResult(root, run.databaseId);
110
+ if (!isVerifyResult(result)) {
111
+ return {
112
+ mode: 'local',
113
+ reason: `run ${run.conclusion} sem o JSON do verify (${run.url}; gh run view ${run.databaseId} --log-failed)`,
114
+ };
115
+ }
116
+ const { persisted, reason, repeatOf, cleanup, ...verdict } = result;
117
+ return { mode: 'ci', result: verdict, run, created };
118
+ }