@polymorphism-tech/morph-spec 8.47.0 → 9.0.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 (222) hide show
  1. package/CLAUDE.md +48 -39
  2. package/README.md +3 -4
  3. package/bin/morph-spec.js +37 -51
  4. package/bin/validate.js +3 -111
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +1 -1
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/CLAUDE.md +41 -101
  9. package/framework/MORPH.md +95 -252
  10. package/framework/agents.json +75 -18
  11. package/framework/commands/morph-apply.md +5 -28
  12. package/framework/commands/morph-archive.md +1 -1
  13. package/framework/commands/morph-hotfix.md +8 -8
  14. package/framework/commands/morph-infra.md +1 -1
  15. package/framework/commands/morph-preflight.md +2 -3
  16. package/framework/commands/morph-proposal.md +2 -50
  17. package/framework/commands/morph-status.md +3 -3
  18. package/framework/docs/CLI.md +585 -286
  19. package/framework/evals/review-checklist.md +82 -41
  20. package/framework/hooks/README.md +10 -11
  21. package/framework/hooks/claude-code/core/comment-guard.js +1 -1
  22. package/framework/hooks/claude-code/core/denylist.json +33 -3
  23. package/framework/hooks/claude-code/core/enforce-phase-writes.js +16 -20
  24. package/framework/hooks/claude-code/core/gate-guard.js +2 -11
  25. package/framework/hooks/claude-code/core/pre-bash-denylist.js +38 -0
  26. package/framework/hooks/claude-code/core/protect-spec-files.js +30 -242
  27. package/framework/hooks/claude-code/core/state-sync.js +7 -21
  28. package/framework/hooks/claude-code/core/telemetry-log.js +3 -3
  29. package/framework/hooks/claude-code/core/validate-completion.js +33 -97
  30. package/framework/hooks/claude-code/core/warn-bash-source-write.js +31 -0
  31. package/framework/hooks/claude-code/core/worktree-context.js +5 -4
  32. package/framework/hooks/claude-code/statusline.py +55 -41
  33. package/framework/hooks/package.json +3 -0
  34. package/framework/hooks/shared/activity-logger.js +1 -1
  35. package/framework/hooks/shared/build-check.js +10 -6
  36. package/framework/hooks/shared/command-tokens.js +169 -0
  37. package/framework/hooks/shared/comment-rules.js +1 -1
  38. package/framework/hooks/shared/comment-scanner.js +10 -23
  39. package/framework/hooks/shared/comment-stripper.js +1 -1
  40. package/framework/hooks/shared/e2e-check.js +64 -27
  41. package/framework/hooks/shared/errata.js +72 -0
  42. package/framework/hooks/shared/feature-locator.js +1 -1
  43. package/framework/hooks/shared/frontend-locations.js +6 -0
  44. package/framework/hooks/shared/gate-question.js +18 -0
  45. package/framework/hooks/shared/phase-utils.js +15 -32
  46. package/framework/hooks/shared/slice-progress.js +118 -0
  47. package/framework/hooks/shared/spawn-command.js +23 -0
  48. package/framework/hooks/shared/state-reader.js +61 -87
  49. package/framework/hooks/shared/state-writer.js +2 -3
  50. package/framework/mcp/registry.json +3 -4
  51. package/framework/phases.json +23 -39
  52. package/framework/schemas/config.schema.json +14 -9
  53. package/framework/schemas/feature.schema.json +15 -50
  54. package/framework/skills/README.md +3 -4
  55. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  56. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  57. package/framework/skills/core/morph-apply/SKILL.md +149 -222
  58. package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
  59. package/framework/skills/core/morph-machine/SKILL.md +2 -2
  60. package/framework/skills/core/morph-media/SKILL.md +1 -1
  61. package/framework/skills/core/morph-plan/SKILL.md +137 -299
  62. package/framework/skills/core/morph-review/SKILL.md +111 -136
  63. package/framework/skills/core/morph-standards/SKILL.md +27 -39
  64. package/framework/skills/core/morph-uiux/SKILL.md +77 -152
  65. package/framework/standards/STANDARDS.json +86 -1218
  66. package/framework/standards/STANDARDS.meta.json +1305 -0
  67. package/framework/standards/ai-agents/evals-with-cache.md +1 -2
  68. package/framework/standards/ai-agents/setup.md +1 -1
  69. package/framework/standards/architecture/code-comments.md +6 -6
  70. package/framework/standards/integration/mcp/mcp-tools.md +7 -7
  71. package/framework/templates/README.md +2 -3
  72. package/framework/templates/REGISTRY.json +6 -170
  73. package/framework/templates/docs/plan.md +72 -0
  74. package/framework/templates/dotnet/ai-kit/README.md +1 -9
  75. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
  76. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
  77. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
  78. package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
  79. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
  80. package/package.json +1 -1
  81. package/src/commands/e2e/e2e.js +2 -2
  82. package/src/commands/observability/cost.js +18 -60
  83. package/src/commands/project/doctor-procs.js +1 -1
  84. package/src/commands/project/doctor.js +9 -9
  85. package/src/commands/project/fleet.js +4 -4
  86. package/src/commands/project/heavy.js +3 -3
  87. package/src/commands/project/init.js +4 -0
  88. package/src/commands/project/setup-infra-cmd.js +2 -1
  89. package/src/commands/project/stack-scan.js +58 -0
  90. package/src/commands/project/standards.js +79 -25
  91. package/src/commands/project/status.js +29 -69
  92. package/src/commands/project/update.js +17 -1
  93. package/src/commands/project/worktree.js +1 -1
  94. package/src/commands/state/advance.js +62 -46
  95. package/src/commands/state/approve.js +81 -141
  96. package/src/commands/state/archive.js +20 -84
  97. package/src/commands/state/auto-approval.js +68 -0
  98. package/src/commands/state/create.js +28 -1
  99. package/src/commands/state/delete.js +1 -1
  100. package/src/commands/state/finish.js +11 -23
  101. package/src/commands/state/gate-check.js +85 -84
  102. package/src/commands/state/gate-decision.js +6 -4
  103. package/src/commands/state/handoff.js +1 -1
  104. package/src/commands/state/list.js +9 -9
  105. package/src/commands/state/report-check.js +157 -311
  106. package/src/commands/state/retype.js +9 -3
  107. package/src/commands/validation/mutate.js +77 -14
  108. package/src/commands/validation/plan-check.js +81 -0
  109. package/src/commands/validation/validate-feature.js +60 -135
  110. package/src/commands/validation/verify.js +40 -72
  111. package/src/core/paths/output-schema.js +19 -66
  112. package/src/core/state/gate-order.js +5 -6
  113. package/src/core/state/lifecycle-audit.js +2 -2
  114. package/src/core/state/state-manager.js +17 -98
  115. package/src/core/templates/template-registry.js +2 -2
  116. package/src/core/templates/template-seed.js +22 -0
  117. package/src/core/work-types.js +10 -10
  118. package/src/dashboard/app/components/ActivityFeed.jsx +2 -3
  119. package/src/dashboard/app/components/CommandPalette.jsx +5 -5
  120. package/src/dashboard/app/components/EntityLink.jsx +1 -1
  121. package/src/dashboard/app/hooks/useMorphQuery.js +1 -1
  122. package/src/dashboard/app/lib/phases.js +2 -4
  123. package/src/dashboard/app/pages/Activity.jsx +1 -1
  124. package/src/dashboard/app/pages/FeatureDetail.jsx +139 -129
  125. package/src/dashboard/app/pages/Features.jsx +9 -9
  126. package/src/dashboard/app/pages/Guide.jsx +2 -2
  127. package/src/dashboard/app/pages/PhasePipeline.jsx +3 -4
  128. package/src/dashboard/app/pages/Standards.jsx +1 -1
  129. package/src/dashboard/scanner/activity-scanner.js +5 -15
  130. package/src/dashboard/scanner/agents-scanner.js +1 -1
  131. package/src/dashboard/scanner/docs-scanner.js +2 -2
  132. package/src/dashboard/scanner/features-scanner.js +95 -60
  133. package/src/dashboard/scanner/project-api.js +1 -1
  134. package/src/dashboard/scanner/standards-scanner.js +2 -1
  135. package/src/lib/ci/ci-verify.js +17 -1
  136. package/src/lib/ci/github-ci.js +1 -1
  137. package/src/lib/detectors/index.js +3 -8
  138. package/src/lib/generators/handoff-generator.js +34 -65
  139. package/src/lib/generators/settings-generator.js +11 -89
  140. package/src/lib/git/evidence-staleness.js +1 -1
  141. package/src/lib/git/tree-fingerprint.js +19 -7
  142. package/src/lib/git/tree-state.js +2 -2
  143. package/src/lib/heavy/cleanup.js +1 -5
  144. package/src/lib/installers/memory-installer.js +6 -8
  145. package/src/lib/mutation/commands.js +3 -2
  146. package/src/lib/{tasks/dotnet-test-introspection.js → mutation/dotnet-introspection.js} +17 -282
  147. package/src/lib/mutation/plan.js +14 -6
  148. package/src/lib/mutation/runner.js +1 -1
  149. package/src/lib/mutation/subset.js +46 -9
  150. package/src/lib/plan/artifact-vocabulary.js +60 -0
  151. package/src/lib/plan/checks.js +234 -0
  152. package/src/lib/plan/plan-parser.js +209 -0
  153. package/src/lib/plan/plan-template.js +18 -0
  154. package/src/lib/review-reports.js +222 -0
  155. package/src/lib/risk-detector.js +131 -140
  156. package/src/lib/specs/adr-index.js +50 -69
  157. package/src/lib/specs/design-seed.js +18 -0
  158. package/src/lib/specs/frontmatter.js +44 -0
  159. package/src/lib/specs/gfm-table.js +17 -0
  160. package/src/lib/specs/old-format.js +68 -0
  161. package/src/lib/specs/revocations.js +37 -95
  162. package/src/lib/specs/screen-checklist.js +18 -17
  163. package/src/lib/stack/dotnet-build-ignores.js +47 -0
  164. package/src/lib/stack/stack-scan.js +267 -0
  165. package/src/lib/standards/registry.js +43 -0
  166. package/src/lib/standards/sections.js +58 -0
  167. package/src/lib/tasks/eval-node.js +4 -12
  168. package/src/lib/tasks/test-nodes.js +1 -74
  169. package/src/lib/tasks/test-runner.js +75 -117
  170. package/src/lib/telemetry/metrics.js +8 -69
  171. package/src/lib/telemetry/telemetry-reader.js +5 -118
  172. package/src/lib/telemetry/verify-event.js +5 -7
  173. package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
  174. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -2
  175. package/src/lib/validators/reuse/reuse-validator.js +22 -45
  176. package/src/lib/validators/traceability-check.js +85 -129
  177. package/src/lib/validators/verify-runner.js +118 -235
  178. package/src/lib/worktree/docker-audit.js +1 -2
  179. package/src/lib/worktree/fleet-scanner.js +25 -38
  180. package/src/scripts/setup-infra.js +9 -0
  181. package/src/utils/agents-installer.js +76 -9
  182. package/src/utils/claude-settings-manager.js +0 -35
  183. package/src/utils/file-copier.js +27 -1
  184. package/src/utils/hooks-installer.js +16 -9
  185. package/src/utils/skills-installer.js +1 -2
  186. package/framework/evals/trace-schema.md +0 -67
  187. package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
  188. package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
  189. package/framework/hooks/git/pre-commit/specs.sh +0 -50
  190. package/framework/hooks/shared/tasks-validator.js +0 -111
  191. package/framework/rules/morph-task-tracking.md +0 -54
  192. package/framework/rules/morph-workflow.md +0 -244
  193. package/framework/schemas/tasks.schema.json +0 -79
  194. package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
  195. package/framework/skills/core/morph-implement/SKILL.md +0 -194
  196. package/framework/skills/core/morph-proposal/SKILL.md +0 -152
  197. package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
  198. package/framework/templates/docs/proposal.md +0 -192
  199. package/framework/templates/docs/spec.md +0 -57
  200. package/framework/templates/docs/ui-components.md +0 -52
  201. package/framework/templates/docs/ui-design-system.md +0 -76
  202. package/framework/templates/docs/ui-flows.md +0 -167
  203. package/framework/templates/examples/spec-examples.md +0 -91
  204. package/framework/templates/feature/decisions.md +0 -124
  205. package/framework/templates/feature/recap.md +0 -145
  206. package/framework/templates/tasks.template.json +0 -17
  207. package/src/commands/state/usecase.js +0 -143
  208. package/src/commands/tasks/dag.js +0 -181
  209. package/src/lib/agents/output-collector.js +0 -76
  210. package/src/lib/evaluator-report.js +0 -104
  211. package/src/lib/specs/acervo-format.js +0 -70
  212. package/src/lib/specs/acervo-lifecycle.js +0 -264
  213. package/src/lib/specs/done-criteria-check.js +0 -200
  214. package/src/lib/specs/usecase-check.js +0 -301
  215. package/src/lib/specs/usecase-promoter.js +0 -301
  216. package/src/lib/specs/usecase-registry.js +0 -237
  217. package/src/lib/tasks/dispatch-planner.js +0 -319
  218. package/src/lib/tasks/mermaid-renderer.js +0 -132
  219. package/src/lib/tasks/task-classifier.js +0 -30
  220. package/src/lib/tasks/task-parser.js +0 -209
  221. package/src/lib/tasks/tasks-json-parser.js +0 -296
  222. package/src/lib/tasks/test-scope.js +0 -597
@@ -6,42 +6,33 @@ import {
6
6
  } from '@phosphor-icons/react';
7
7
  import PhaseRail, { segmentStates } from '../components/PhaseRail.jsx';
8
8
  import KnowledgeMarkdown from '../components/KnowledgeMarkdown.jsx';
9
- import EntityLink, { standardKey } from '../components/EntityLink.jsx';
9
+ import EntityLink from '../components/EntityLink.jsx';
10
10
  import ActivityFeed from '../components/ActivityFeed.jsx';
11
11
  import { useMorphQuery } from '../hooks/useMorphQuery.js';
12
12
  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, verdictLabel } from '../lib/format.js';
16
+ import { timeAgo, dateTime, num, bytes, verdictLabel } from '../lib/format.js';
17
17
 
18
18
  const TABS = [
19
19
  { id: 'overview', label: 'Visão geral' },
20
- { id: 'tasks', label: 'Tasks' },
20
+ { id: 'slices', label: 'Fatias' },
21
+ { id: 'checks', label: 'Cenários' },
21
22
  { id: 'docs', label: 'Documentos' },
22
23
  { id: 'activity', label: 'Atividade' },
23
24
  ];
24
25
 
25
- const TASK_STATUS = {
26
- done: { label: 'feita', tone: 'text-[var(--color-ok)]', icon: CheckCircle },
27
- completed: { label: 'feita', tone: 'text-[var(--color-ok)]', icon: CheckCircle },
28
- in_progress: { label: 'em andamento', tone: 'text-[var(--color-phase-implement)]', icon: CircleDashed },
29
- blocked: { label: 'bloqueada', tone: 'text-[var(--color-warn)]', icon: PauseCircle },
30
- skipped: { label: 'pulada', tone: 'text-[var(--color-faint)]', icon: Circle },
31
- pending: { label: 'pendente', tone: 'text-[var(--color-faint)]', icon: Circle },
26
+ const SLICE_STATUS = {
27
+ feito: { label: 'feita', tone: 'text-[var(--color-ok)]', icon: CheckCircle },
28
+ parcial: { label: 'parcial', tone: 'text-[var(--color-phase-implement)]', icon: CircleDashed },
29
+ pendente: { label: 'pendente', tone: 'text-[var(--color-faint)]', icon: Circle },
32
30
  };
33
31
 
34
- /** Duration of each task from the telemetry (TaskStart → TaskEnd), newest segment wins. */
35
- function taskDurations(cost) {
36
- const out = {};
37
- for (const t of cost?.tasks || []) if (t.task && t.durationSeconds) out[t.task] = t.durationSeconds;
38
- return out;
39
- }
40
-
41
- /** A pending task whose dependencies are all done can start now. */
42
- function isReady(t, byId) {
43
- if (t.status !== 'pending') return false;
44
- return (t.dependencies || []).every((d) => ['done', 'completed', 'skipped'].includes(byId[d]?.status));
32
+ // An unfinished slice whose dependencies all have a final report can start now.
33
+ function isReady(sl, byId) {
34
+ if (sl.status === 'feito') return false;
35
+ return (sl.depends || []).every((d) => byId[d]?.status === 'feito');
45
36
  }
46
37
 
47
38
  /** `vscode://file/…` opens the file in the editor from the browser. */
@@ -77,16 +68,15 @@ export function nextAction(f) {
77
68
  if (f.status === 'abandoned') return 'Abandonada.';
78
69
  const paused = f.gates.find((g) => g.paused);
79
70
  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 veredito e pelos bloqueadores.';
81
- if (f.phase === 'implement' && f.tasksList?.length) {
82
- const open = f.tasksList.find((t) => t.status === 'in_progress') || f.tasksList.find((t) => t.status === 'pending');
83
- if (open) return `${f.tasks.done} de ${f.tasks.total} tasks feitas. Próxima: ${open.id}, ${open.title}.`;
84
- return 'Todas as tasks feitas. Falta o verify da feature e a revisão.';
71
+ if (f.inEvaluation) return 'Os relatórios do Gate 3 chegaram. A decisão sai dos vereditos, dos bloqueadores e dos cenários provados.';
72
+ if (f.phase === 'implement' && f.slicesList?.length) {
73
+ const open = f.slicesList.find((sl) => sl.status === 'parcial') || f.slicesList.find((sl) => sl.status === 'pendente');
74
+ if (open) return `${f.slices.done} de ${f.slices.total} fatias feitas. Próxima: ${open.id}, ${open.title}.`;
75
+ return 'Todas as fatias feitas. Falta o verify da feature e a revisão.';
85
76
  }
86
77
  const text = {
87
- proposal: 'Proposta em construção. O Gate 1 aprova o escopo.',
78
+ plan: 'Plano sendo escrito: cenários, decisões e fatias. O gate do plano aprova.',
88
79
  uiux: 'Desenho de UI/UX em andamento.',
89
- plan: 'Plano sendo escrito: spec, use cases e tasks. O Gate 2 aprova.',
90
80
  implement: 'Implementação em andamento.',
91
81
  review: 'Em revisão.',
92
82
  };
@@ -149,13 +139,41 @@ function NodeChips({ nodes }) {
149
139
  );
150
140
  }
151
141
 
142
+ const REVIEW_ROLE_LABEL = { verifier: 'Verificador', reviewer: 'Revisor', security: 'Segurança' };
143
+ const REVIEW_STATUS_LABEL = { missing: 'sem relatório', partial: 'parcial', unverdicted: 'sem veredito', ok: null };
144
+
145
+ function ReviewRows({ reviews }) {
146
+ if (!reviews?.length) return null;
147
+ return (
148
+ <ul className="mt-4 space-y-2 text-sm">
149
+ {reviews.map((r) => {
150
+ const state = REVIEW_STATUS_LABEL[r.status] ?? r.verdict;
151
+ const tone = r.verdict === 'SHIP' && r.status === 'ok' ? 'text-[var(--color-ok)]' : r.verdict === 'FIX' ? 'text-[var(--color-bad)]' : 'text-[var(--color-faint)]';
152
+ return (
153
+ <li key={r.role} className="grid grid-cols-[6.5rem_minmax(0,1fr)_auto] items-baseline gap-3">
154
+ <span className="text-[var(--color-dim)]">{REVIEW_ROLE_LABEL[r.role] || r.role}</span>
155
+ <span className={tone}>
156
+ {state}{r.blockers ? ` · ${r.blockers} bloq.` : ''}
157
+ {r.reviewedSha && <span className="ml-2 font-mono text-xs text-[var(--color-faint)]">{r.reviewedSha.slice(0, 7)}</span>}
158
+ </span>
159
+ {r.status !== 'missing' && (
160
+ <Link to={`?tab=docs&doc=${encodeURIComponent(r.path)}`} className="text-xs text-[var(--color-dim)] underline decoration-[var(--color-hair-strong)] underline-offset-2 hover:text-[var(--color-fg)]">abrir</Link>
161
+ )}
162
+ </li>
163
+ );
164
+ })}
165
+ </ul>
166
+ );
167
+ }
168
+
152
169
  function VerdictBlock({ f }) {
153
170
  return (
154
171
  <div>
155
172
  <p className="flex items-baseline gap-2">
156
173
  <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>
174
+ <span className="text-sm text-[var(--color-dim)]">{f.verdict ? `${f.blockers ?? 0} bloqueador(es)` : 'sem veredito do Gate 3'}</span>
158
175
  </p>
176
+ <ReviewRows reviews={f.reviews} />
159
177
  {f.blockerList?.length > 0 && (
160
178
  <ul className="mt-4 space-y-1.5 text-xs">
161
179
  {f.blockerList.map((b) => (
@@ -178,7 +196,6 @@ function CostBlock({ cost }) {
178
196
  ['Tokens da sessão', cost.sessionUsage ? num(tokens(cost.sessionUsage)) : 'desconhecido', cost.sessionUsage ? `${num(cost.sessionUsage.cacheReadTokens)} lidos do cache` : cost.legacyUsageSamples ? 'telemetria v1, sem delta somável' : null],
179
197
  ['Tokens dos sub-agents', cost.agentUsage ? num(tokens(cost.agentUsage)) : 'desconhecido', cost.agentUsage ? `${cost.agentUsage.agents} sub-agents` : null],
180
198
  ['Despachos', String(cost.dispatches?.count ?? 0), cost.dispatches?.promptChars ? `${num(cost.dispatches.promptChars)} caracteres de prompt` : null],
181
- ['Tempo em tasks', duration(cost.taskDurationSeconds), null],
182
199
  ['Lido para o contexto', bytes(cost.bytesTotal), null],
183
200
  ];
184
201
  return (
@@ -211,9 +228,7 @@ function OverviewTab({ f, cost }) {
211
228
  <Panel title="Avaliação"><VerdictBlock f={f} /></Panel>
212
229
  <Panel title="Último verify">
213
230
  {f.verification && (
214
- <p className="mb-3 text-xs text-[var(--color-faint)]">
215
- escopo {f.verification.scope || '—'}, {timeAgo(f.verification.ts)}
216
- </p>
231
+ <p className="mb-3 text-xs text-[var(--color-faint)]">{timeAgo(f.verification.ts)}</p>
217
232
  )}
218
233
  <NodeChips nodes={f.verificationNodes} />
219
234
  </Panel>
@@ -223,81 +238,89 @@ function OverviewTab({ f, cost }) {
223
238
  );
224
239
  }
225
240
 
226
- function reportPathFor(f, taskId) {
227
- const candidates = [`3-implement/reports/${taskId}.md`, `3-implement/evidencias/${taskId}.md`];
228
- return candidates.filter((p) => f.documents.some((d) => d.path === p));
241
+ const CHECK_STATUS_TONE = {
242
+ PROVADO: 'text-[var(--color-ok)]',
243
+ FALHA: 'text-[var(--color-bad)]',
244
+ 'NÃO PROVADO': 'text-[var(--color-warn)]',
245
+ };
246
+
247
+ function ChecksTab({ f }) {
248
+ const cov = f.checksCoverage;
249
+ if (!f.checksList?.length) {
250
+ return <Panel><p className="text-sm text-[var(--color-dim)]">Sem cenários extraídos: o `plan-check` ainda não gravou o `checks.json`.</p></Panel>;
251
+ }
252
+ const proven = cov ? cov.total - cov.missing.length - cov.notProven.length : null;
253
+ return (
254
+ <Panel title={cov ? `${proven} de ${cov.total} provados pelo verificador` : 'Aguardando o relatório do verificador'}>
255
+ <ul className="divide-y divide-[var(--color-hair)]">
256
+ {f.checksList.map((c) => (
257
+ <li key={c.id} className="grid grid-cols-[3rem_minmax(0,1fr)_7rem] gap-3 py-3 text-sm">
258
+ <span className="font-mono text-[var(--color-dim)]">{c.id}</span>
259
+ <div className="min-w-0">
260
+ <p className="text-[var(--color-fg)]">{c.text}</p>
261
+ {c.evidence && <p className="mt-1 truncate text-xs text-[var(--color-faint)]" title={c.evidence}>{c.evidence}</p>}
262
+ </div>
263
+ <div className="text-right">
264
+ <p className={CHECK_STATUS_TONE[c.status] || 'text-[var(--color-faint)]'}>{c.status ?? (cov ? 'sem linha' : '—')}</p>
265
+ {c.proof && <p className="text-xs text-[var(--color-faint)]">prova: {c.proof}</p>}
266
+ </div>
267
+ </li>
268
+ ))}
269
+ </ul>
270
+ </Panel>
271
+ );
272
+ }
273
+
274
+ function reportPathFor(f, sliceId) {
275
+ const path = `3-implement/reports/${sliceId}.md`;
276
+ return f.documents.some((d) => d.path === path) ? path : null;
229
277
  }
230
278
 
231
- function TaskRow({ t, f, open, onToggle, duration: secs, ready }) {
232
- const st = TASK_STATUS[t.status] || TASK_STATUS.pending;
279
+ function SliceRow({ sl, f, open, onToggle, ready }) {
280
+ const st = SLICE_STATUS[sl.status] || SLICE_STATUS.pendente;
233
281
  const StIcon = st.icon;
234
- const reports = reportPathFor(f, t.id);
235
- const verify = t.verification;
282
+ const report = reportPathFor(f, sl.id);
236
283
  return (
237
284
  <li>
238
285
  <button
239
286
  type="button"
240
287
  onClick={onToggle}
241
288
  aria-expanded={open}
242
- className="fluid grid w-full grid-cols-[1.25rem_2.75rem_minmax(0,1fr)] items-center gap-3 px-5 py-3 text-left transition-colors hover:bg-white/[0.025] md:grid-cols-[1.25rem_2.75rem_minmax(0,1fr)_9rem_4.5rem_3rem_1rem]"
289
+ className="fluid grid w-full grid-cols-[1.25rem_2.75rem_minmax(0,1fr)] items-center gap-3 px-5 py-3 text-left transition-colors hover:bg-white/[0.025] md:grid-cols-[1.25rem_2.75rem_minmax(0,1fr)_9rem_1rem]"
243
290
  >
244
291
  <StIcon weight="light" size={17} className={st.tone} aria-label={st.label} />
245
- <span className="font-mono text-xs text-[var(--color-dim)]">{t.id}</span>
292
+ <span className="font-mono text-xs text-[var(--color-dim)]">{sl.id}</span>
246
293
  <span className="flex min-w-0 items-center gap-2">
247
- <span className="truncate text-sm text-[var(--color-fg)]">{t.title}</span>
294
+ <span className="truncate text-sm text-[var(--color-fg)]">{sl.title}</span>
248
295
  {ready && <span className="shrink-0 text-xs text-[var(--color-phase-implement)]">pronta</span>}
249
- {secs ? <span className="shrink-0 text-xs text-[var(--color-faint)]">{duration(secs)}</span> : null}
250
- </span>
251
- <span className="hidden truncate text-xs text-[var(--color-dim)] md:block">{t.agent ? `morph-${t.agent}` : '—'}</span>
252
- <span className="hidden text-xs md:block">
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>}
254
296
  </span>
255
- <span className="hidden text-right font-mono text-xs tabular-nums text-[var(--color-dim)] md:block">{verify?.status ?? '—'}</span>
297
+ <span className="hidden truncate text-xs text-[var(--color-dim)] md:block">{sl.persona ? `morph-${sl.persona}` : '—'}</span>
256
298
  <span className="hidden text-[var(--color-faint)] md:block">{open ? <CaretDown weight="light" size={14} /> : <CaretRight weight="light" size={14} />}</span>
257
299
  </button>
258
300
  {open && (
259
301
  <div className="grid gap-5 px-5 pb-5 pl-[4.75rem] md:grid-cols-2">
260
- <div className="space-y-4 text-sm">
261
- {t.description && <p className="leading-relaxed text-[var(--color-dim)]">{t.description}</p>}
262
- {Array.isArray(t.doneCriteria) && t.doneCriteria.length > 0 && (
263
- <div>
264
- <p className="mb-1.5 text-xs text-[var(--color-faint)]">Critérios de pronto</p>
265
- <ul className="list-disc space-y-1 pl-4 text-[var(--color-dim)] marker:text-[var(--color-faint)]">
266
- {t.doneCriteria.map((c, i) => <li key={i}>{c}</li>)}
267
- </ul>
268
- </div>
269
- )}
270
- {t.notes && <p className="text-[var(--color-dim)]"><span className="text-[var(--color-faint)]">Notas: </span>{t.notes}</p>}
271
- </div>
272
302
  <dl className="space-y-3 text-sm">
273
- {t.agent && (
303
+ {sl.persona && (
274
304
  <div><dt className="text-xs text-[var(--color-faint)]">Persona</dt>
275
- <dd><EntityLink kind="persona" id={t.agent}>morph-{t.agent}</EntityLink></dd></div>
305
+ <dd><EntityLink kind="persona" id={sl.persona}>morph-{sl.persona}</EntityLink></dd></div>
276
306
  )}
277
- {t.standards?.length > 0 && (
278
- <div><dt className="text-xs text-[var(--color-faint)]">Standards</dt>
279
- <dd className="mt-1 flex flex-wrap gap-1.5">
280
- {t.standards.map((s) => (
281
- <EntityLink key={s} kind="standard" id={standardKey(s)} chip title={s}>{standardKey(s).replace(/\.md$/, '')}</EntityLink>
282
- ))}
283
- </dd></div>
307
+ {sl.covers?.length > 0 && (
308
+ <div><dt className="text-xs text-[var(--color-faint)]">Cobre</dt><dd className="font-mono text-xs text-[var(--color-dim)]">{sl.covers.join(', ')}</dd></div>
284
309
  )}
285
- {t.dependencies?.length > 0 && (
286
- <div><dt className="text-xs text-[var(--color-faint)]">Depende de</dt><dd className="font-mono text-xs text-[var(--color-dim)]">{t.dependencies.join(', ')}</dd></div>
310
+ {sl.depends?.length > 0 && (
311
+ <div><dt className="text-xs text-[var(--color-faint)]">Depende de</dt><dd className="font-mono text-xs text-[var(--color-dim)]">{sl.depends.join(', ')}</dd></div>
287
312
  )}
288
- {t.usecase && <div><dt className="text-xs text-[var(--color-faint)]">Use case</dt><dd className="font-mono text-xs text-[var(--color-dim)]">{t.usecase}</dd></div>}
289
- {verify?.nodes && <div><dt className="mb-1 text-xs text-[var(--color-faint)]">Verify da task</dt><dd><NodeChips nodes={verify.nodes} /></dd></div>}
290
- {t.outputs?.length > 0 && (
313
+ </dl>
314
+ <dl className="space-y-3 text-sm">
315
+ {sl.files?.length > 0 && (
291
316
  <div><dt className="text-xs text-[var(--color-faint)]">Arquivos</dt>
292
- <dd className="mt-1 space-y-0.5 font-mono text-[11px] text-[var(--color-dim)]">{t.outputs.map((o) => <p key={o} className="truncate" title={o}>{o}</p>)}</dd></div>
317
+ <dd className="mt-1 space-y-0.5 font-mono text-[11px] text-[var(--color-dim)]">{sl.files.map((o) => <p key={o} className="truncate" title={o}>{o}</p>)}</dd></div>
293
318
  )}
294
- {reports.length > 0 && (
319
+ {report && (
295
320
  <div className="flex flex-wrap gap-2 pt-1">
296
- {reports.map((p) => (
297
- <Link key={p} to={`?tab=docs&doc=${encodeURIComponent(p)}`} className="fluid rounded-full bg-white/[0.06] px-3 py-1.5 text-xs text-[var(--color-fg)] transition-colors hover:bg-white/[0.1]">
298
- Abrir {p.includes('evidencias') ? 'evidências' : 'relatório'}
299
- </Link>
300
- ))}
321
+ <Link to={`?tab=docs&doc=${encodeURIComponent(report)}`} className="fluid rounded-full bg-white/[0.06] px-3 py-1.5 text-xs text-[var(--color-fg)] transition-colors hover:bg-white/[0.1]">
322
+ Abrir relatório
323
+ </Link>
301
324
  </div>
302
325
  )}
303
326
  </dl>
@@ -308,44 +331,37 @@ function TaskRow({ t, f, open, onToggle, duration: secs, ready }) {
308
331
  }
309
332
 
310
333
  const BOARD_COLUMNS = [
311
- { id: 'pending', label: 'Pendentes', match: (t) => !t.status || t.status === 'pending' },
312
- { id: 'in_progress', label: 'Em andamento', match: (t) => t.status === 'in_progress' },
313
- { id: 'blocked', label: 'Bloqueadas', match: (t) => t.status === 'blocked' },
314
- { id: 'done', label: 'Feitas', match: (t) => t.status === 'done' || t.status === 'completed' || t.status === 'skipped' },
334
+ { id: 'pendente', label: 'Pendentes' },
335
+ { id: 'parcial', label: 'Parciais' },
336
+ { id: 'feito', label: 'Feitas' },
315
337
  ];
316
338
 
317
- function TaskCard({ t, f, secs, ready }) {
318
- const reports = reportPathFor(f, t.id);
339
+ function SliceCard({ sl, f, ready }) {
340
+ const report = reportPathFor(f, sl.id);
319
341
  return (
320
342
  <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)]">
321
343
  <p className="flex items-baseline gap-2">
322
- <span className="font-mono text-xs text-[var(--color-dim)]">{t.id}</span>
344
+ <span className="font-mono text-xs text-[var(--color-dim)]">{sl.id}</span>
323
345
  {ready && <span className="text-xs text-[var(--color-phase-implement)]">pronta</span>}
324
346
  </p>
325
- <p className="mt-1 text-sm leading-snug text-[var(--color-fg)]">{t.title}</p>
326
- {t.agent && (
327
- <EntityLink kind="persona" id={t.agent} className="mt-2 inline-block text-xs">morph-{t.agent}</EntityLink>
347
+ <p className="mt-1 text-sm leading-snug text-[var(--color-fg)]">{sl.title}</p>
348
+ {sl.persona && (
349
+ <EntityLink kind="persona" id={sl.persona} className="mt-2 inline-block text-xs">morph-{sl.persona}</EntityLink>
328
350
  )}
329
- {t.dependencies?.length > 0 && <p className="mt-1 text-xs text-[var(--color-faint)]">depende de {t.dependencies.join(', ')}</p>}
330
- <div className="mt-2.5 flex items-center justify-between text-xs">
331
- <span className="flex items-center gap-2">
332
- {t.verification && <span className={t.verification.status === 'pass' ? 'text-[var(--color-ok)]' : 'text-[var(--color-bad)]'}>verify {t.verification.status}</span>}
333
- </span>
334
- <span className="text-[var(--color-faint)]">{secs ? duration(secs) : ''}</span>
335
- </div>
336
- {reports.length > 0 && (
337
- <Link to={`?tab=docs&doc=${encodeURIComponent(reports[0])}`} className="mt-2.5 inline-block text-xs text-[var(--color-dim)] underline decoration-[var(--color-hair-strong)] underline-offset-2 hover:text-[var(--color-fg)]">abrir relatório</Link>
351
+ {sl.depends?.length > 0 && <p className="mt-1 text-xs text-[var(--color-faint)]">depende de {sl.depends.join(', ')}</p>}
352
+ {report && (
353
+ <Link to={`?tab=docs&doc=${encodeURIComponent(report)}`} className="mt-2.5 inline-block text-xs text-[var(--color-dim)] underline decoration-[var(--color-hair-strong)] underline-offset-2 hover:text-[var(--color-fg)]">abrir relatório</Link>
338
354
  )}
339
355
  </li>
340
356
  );
341
357
  }
342
358
 
343
- function TasksBoard({ f, durations, byId }) {
359
+ function SlicesBoard({ f, byId }) {
344
360
  return (
345
361
  <div className="-mx-4 overflow-x-auto px-4 pb-2 md:mx-0 md:px-0">
346
362
  <div className="grid auto-cols-[16rem] grid-flow-col gap-3 md:auto-cols-[minmax(12rem,1fr)]">
347
363
  {BOARD_COLUMNS.map((c) => {
348
- const items = f.tasksList.filter(c.match);
364
+ const items = f.slicesList.filter((sl) => sl.status === c.id);
349
365
  return (
350
366
  <section key={c.id} className="bezel min-w-0" aria-label={`${c.label}: ${items.length}`}>
351
367
  <div className="bezel-core flex h-full min-h-[12rem] flex-col p-2.5">
@@ -354,7 +370,7 @@ function TasksBoard({ f, durations, byId }) {
354
370
  </h3>
355
371
  {items.length === 0
356
372
  ? <p className="px-1.5 text-xs text-[var(--color-faint)]">Nenhuma.</p>
357
- : <ul className="space-y-2">{items.map((t) => <TaskCard key={t.id} t={t} f={f} secs={durations[t.id]} ready={isReady(t, byId)} />)}</ul>}
373
+ : <ul className="space-y-2">{items.map((sl) => <SliceCard key={sl.id} sl={sl} f={f} ready={isReady(sl, byId)} />)}</ul>}
358
374
  </div>
359
375
  </section>
360
376
  );
@@ -364,34 +380,27 @@ function TasksBoard({ f, durations, byId }) {
364
380
  );
365
381
  }
366
382
 
367
- function TasksTab({ f, cost, view = 'list', onView = () => {} }) {
383
+ function SlicesTab({ f, view = 'list', onView = () => {} }) {
368
384
  const [open, setOpen] = useState(null);
369
- if (!f.tasksList.length) {
370
- return <Panel><p className="text-sm text-[var(--color-dim)]">Esta feature ainda não tem <code className="font-mono">2-plan/tasks.json</code>. As tasks nascem na fase de plano.</p></Panel>;
385
+ if (!f.slicesList.length) {
386
+ return <Panel><p className="text-sm text-[var(--color-dim)]">O plano desta feature ainda não foi conferido pelo <code className="font-mono">morph-spec plan-check</code>. As fatias nascem na fase de plano.</p></Panel>;
371
387
  }
372
- const durations = taskDurations(cost);
373
- const byId = Object.fromEntries(f.tasksList.map((t) => [t.id, t]));
374
- const ready = f.tasksList.filter((t) => isReady(t, byId)).length;
388
+ const byId = Object.fromEntries(f.slicesList.map((sl) => [sl.id, sl]));
389
+ const ready = f.slicesList.filter((sl) => isReady(sl, byId)).length;
375
390
  return (
376
391
  <div className="space-y-4">
377
392
  <div className="flex flex-wrap items-center justify-between gap-3">
378
- <Segmented label="Visualização das tasks" value={view} onChange={onView} options={[{ id: 'list', label: 'Lista', icon: Rows }, { id: 'board', label: 'Quadro', icon: Kanban }]} />
393
+ <Segmented label="Visualização das fatias" value={view} onChange={onView} options={[{ id: 'list', label: 'Lista', icon: Rows }, { id: 'board', label: 'Quadro', icon: Kanban }]} />
379
394
  <p className="text-sm text-[var(--color-dim)]">
380
- {f.tasks.done} de {f.tasks.total} feitas{f.tasks.inProgress ? `, ${f.tasks.inProgress} em andamento` : ''}{ready ? `, ${ready} prontas para começar` : ''}
395
+ {f.slices.done} de {f.slices.total} feitas{f.slices.partial ? `, ${f.slices.partial} parciais` : ''}{ready ? `, ${ready} prontas para começar` : ''}
381
396
  </p>
382
397
  </div>
383
- {f.tasksErrors?.length > 0 && (
384
- <p className="flex items-start gap-2 text-sm text-[var(--color-warn)]">
385
- <WarningCircle weight="light" size={17} className="mt-0.5 shrink-0" />
386
- O tasks.json não passa no schema ({f.tasksErrors.length} erros). As tasks aparecem como estão no arquivo.
387
- </p>
388
- )}
389
- {view === 'board' ? <TasksBoard f={f} durations={durations} byId={byId} /> : (
398
+ {view === 'board' ? <SlicesBoard f={f} byId={byId} /> : (
390
399
  <div className="bezel">
391
400
  <ul className="bezel-core divide-y divide-[var(--color-hair)] overflow-hidden">
392
- {f.tasksList.map((t) => (
393
- <TaskRow key={t.id} t={t} f={f} duration={durations[t.id]} ready={isReady(t, byId)}
394
- open={open === t.id} onToggle={() => setOpen(open === t.id ? null : t.id)} />
401
+ {f.slicesList.map((sl) => (
402
+ <SliceRow key={sl.id} sl={sl} f={f} ready={isReady(sl, byId)}
403
+ open={open === sl.id} onToggle={() => setOpen(open === sl.id ? null : sl.id)} />
395
404
  ))}
396
405
  </ul>
397
406
  </div>
@@ -436,7 +445,7 @@ function DocViewer({ project, feature, path, dir }) {
436
445
 
437
446
  function DocsTab({ f, doc, onDoc, project }) {
438
447
  const groups = groupDocuments(f.documents);
439
- const selected = doc || f.documents.find((d) => d.path.endsWith('spec.md'))?.path || f.documents.find((d) => d.path.endsWith('.md'))?.path || null;
448
+ const selected = doc || f.documents.find((d) => d.path.endsWith('plan.md'))?.path || f.documents.find((d) => d.path.endsWith('.md'))?.path || null;
440
449
  return (
441
450
  <div className="grid gap-5 lg:grid-cols-[16rem_minmax(0,1fr)]">
442
451
  <nav aria-label="Documentos da feature" className="lg:sticky lg:top-4 lg:self-start">
@@ -481,7 +490,7 @@ function DocsTab({ f, doc, onDoc, project }) {
481
490
  );
482
491
  }
483
492
 
484
- export function FeatureDetailView({ f, tab, onTab, doc, onDoc, cost, latest = null, tasksView = 'list', onTasksView = () => {}, now = Date.now(), project, projectName }) {
493
+ export function FeatureDetailView({ f, tab, onTab, doc, onDoc, cost, latest = null, slicesView = 'list', onSlicesView = () => {}, now = Date.now(), project, projectName }) {
485
494
  const verdict = verdictLabel(f);
486
495
  const working = latest && isWorking({ ...f, lastActivity: latest }, now);
487
496
  return (
@@ -500,7 +509,7 @@ export function FeatureDetailView({ f, tab, onTab, doc, onDoc, cost, latest = nu
500
509
  </p>
501
510
  </div>
502
511
  <div className="flex shrink-0 gap-6 text-right">
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>
512
+ <div><p className="text-2xl font-semibold tabular-nums">{f.slices.total ? `${f.slices.done}/${f.slices.total}` : '—'}</p><p className="text-xs text-[var(--color-faint)]">fatias</p></div>
504
513
  <div><p className="text-2xl font-semibold">{verdict ?? '—'}</p><p className="text-xs text-[var(--color-faint)]">veredito</p></div>
505
514
  </div>
506
515
  </div>
@@ -546,14 +555,15 @@ export function FeatureDetailView({ f, tab, onTab, doc, onDoc, cost, latest = nu
546
555
  tab === t.id ? 'bg-white/[0.1] text-[var(--color-fg)] shadow-[inset_0_1px_0_rgb(255_255_255_/_0.08)]' : 'text-[var(--color-dim)] hover:text-[var(--color-fg)]',
547
556
  ].join(' ')}
548
557
  >
549
- {t.label}{t.id === 'tasks' && f.tasks.total ? ` ${f.tasks.total}` : ''}{t.id === 'docs' ? ` ${f.documents.length}` : ''}
558
+ {t.label}{t.id === 'slices' && f.slices.total ? ` ${f.slices.total}` : ''}{t.id === 'checks' && f.checksList?.length ? ` ${f.checksList.length}` : ''}{t.id === 'docs' ? ` ${f.documents.length}` : ''}
550
559
  </button>
551
560
  ))}
552
561
  </div>
553
562
 
554
563
  <div className="rise-3">
555
564
  {tab === 'overview' && <OverviewTab f={f} cost={cost} />}
556
- {tab === 'tasks' && <TasksTab f={f} cost={cost} view={tasksView} onView={onTasksView} />}
565
+ {tab === 'slices' && <SlicesTab f={f} view={slicesView} onView={onSlicesView} />}
566
+ {tab === 'checks' && <ChecksTab f={f} />}
557
567
  {tab === 'docs' && <DocsTab f={f} doc={doc} onDoc={onDoc} project={project} />}
558
568
  {tab === 'activity' && (
559
569
  <div className="bezel"><div className="bezel-core px-5 py-5"><ActivityFeed project={project} feature={f.name} /></div></div>
@@ -572,7 +582,7 @@ export default function FeatureDetail() {
572
582
  // The cost rides on the activity endpoint (it owns the event stream); one
573
583
  // line of feed is enough to get it, and it refreshes with the log.
574
584
  const costQuery = useMorphQuery(`${apiBase(project)}/activity?feature=${encodeURIComponent(name)}&limit=1`, { scope: 'activity', project });
575
- const [tasksView, setTasksView] = usePref('tasks.view', 'list');
585
+ const [slicesView, setSlicesView] = usePref('slices.view', 'list');
576
586
  const now = useNow();
577
587
 
578
588
  const onTab = useCallback((id) => setParams((p) => { const n = new URLSearchParams(p); n.set('tab', id); if (id !== 'docs') n.delete('doc'); return n; }), [setParams]);
@@ -592,7 +602,7 @@ export default function FeatureDetail() {
592
602
  <FeatureDetailView
593
603
  f={data} tab={tab} onTab={onTab} doc={doc} onDoc={onDoc}
594
604
  cost={costQuery.data?.cost ?? null} latest={costQuery.data?.items?.[0] ?? null}
595
- tasksView={tasksView} onTasksView={setTasksView} now={now} project={project}
605
+ slicesView={slicesView} onSlicesView={setSlicesView} now={now} project={project}
596
606
  />
597
607
  );
598
608
  }
@@ -41,15 +41,15 @@ function VerifyMark({ verification }) {
41
41
  : <XCircle weight="light" className="text-[var(--color-bad)]" size={16} aria-label="último verify falhou" />;
42
42
  }
43
43
 
44
- function TaskMeter({ tasks, wide = false }) {
45
- if (!tasks?.total) return <span className="text-xs text-[var(--color-faint)]">sem tasks</span>;
46
- const pct = Math.round((tasks.done / tasks.total) * 100);
44
+ function SliceMeter({ slices, wide = false }) {
45
+ if (!slices?.total) return <span className="text-xs text-[var(--color-faint)]">sem fatias</span>;
46
+ const pct = Math.round((slices.done / slices.total) * 100);
47
47
  return (
48
- <div className="flex items-center gap-2" title={`${tasks.done} de ${tasks.total} tasks concluídas`}>
48
+ <div className="flex items-center gap-2" title={`${slices.done} de ${slices.total} fatias feitas`}>
49
49
  <div className={`h-1 overflow-hidden rounded-full bg-white/[0.07] ${wide ? 'flex-1' : 'w-12'}`}>
50
50
  <div className="h-full rounded-full bg-[var(--color-fg)]/70" style={{ width: `${pct}%` }} />
51
51
  </div>
52
- <span className="font-mono text-xs tabular-nums text-[var(--color-dim)]">{tasks.done}/{tasks.total}</span>
52
+ <span className="font-mono text-xs tabular-nums text-[var(--color-dim)]">{slices.done}/{slices.total}</span>
53
53
  </div>
54
54
  );
55
55
  }
@@ -106,9 +106,9 @@ export function FeatureRow({ f, now, project, projectName }) {
106
106
  <PhaseRail feature={f} />
107
107
  {/* One line on a phone; on desktop the wrapper dissolves into the row's grid. */}
108
108
  <div className="flex items-center gap-5 md:contents">
109
- <TaskMeter tasks={f.tasks} />
109
+ <SliceMeter slices={f.slices} />
110
110
  <VerifyMark verification={f.verification} />
111
- <span className="font-mono text-sm tabular-nums text-[var(--color-dim)]" title={verdict ? 'veredito do avaliador' : 'sem veredito'}>
111
+ <span className="font-mono text-sm tabular-nums text-[var(--color-dim)]" title={verdict ? 'veredito do Gate 3' : 'sem veredito'}>
112
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]}>
@@ -165,7 +165,7 @@ function BoardCard({ f, now, project }) {
165
165
  {f.lastActivity?.title && !isFinished(f) && (
166
166
  <p className="mt-1.5 line-clamp-2 text-xs text-[var(--color-dim)]">{f.lastActivity.title}</p>
167
167
  )}
168
- <div className="mt-3"><TaskMeter tasks={f.tasks} wide /></div>
168
+ <div className="mt-3"><SliceMeter slices={f.slices} wide /></div>
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} />
@@ -329,7 +329,7 @@ export function FeaturesView({
329
329
  )}
330
330
  {info.initialized && !info.legacyStateOnly && features.length === 0 && (
331
331
  <Empty title="Nenhuma feature ainda">
332
- No Claude Code, comece uma com <code className="font-mono text-[var(--color-fg)]">/morph-proposal</code>. Ela aparece aqui assim que o <code className="font-mono">feature.json</code> nascer.
332
+ No Claude Code, comece uma com <code className="font-mono text-[var(--color-fg)]">/morph-proposal</code>, que entrevista e escreve o plano. Ela aparece aqui assim que o <code className="font-mono">feature.json</code> nascer.
333
333
  </Empty>
334
334
  )}
335
335
  {features.length > 0 && visible.length === 0 && (
@@ -27,9 +27,9 @@ export function Flow() {
27
27
  <p className="mt-1 max-w-3xl text-sm leading-relaxed text-[var(--color-dim)]">
28
28
  Você descreve o que quer; o Claude conduz cada fase com uma skill, despacha personas especialistas como sub-agents e para nos gates.
29
29
  Com trust <span className="font-mono text-[var(--color-fg)]">auto</span>, um gate só pausa para você quando aparece um sinal de risco.
30
- No fim, um avaliador independente dá a nota.
30
+ No fim, verificador e revisor independentes dão o veredito (SHIP ou FIX).
31
31
  </p>
32
- <ol className="mt-6 grid gap-3 md:grid-cols-5">
32
+ <ol className="mt-6 grid gap-3 md:grid-cols-4">
33
33
  {PHASES.map((p) => {
34
34
  const people = personasIn(p.id);
35
35
  return (
@@ -11,10 +11,9 @@ const phaseColor = (id) => `var(--color-phase-${id})`;
11
11
 
12
12
  /** What each approval gate checks, in the words a reader of the pipeline needs. */
13
13
  const GATE_CHECKS = {
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
- uiux: 'Confere o design das telas (mockups e checklist de tela). É opcional: só existe quando a feature passa pela fase de UI/UX.',
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 veredito (SHIP ou FIX) e só bloqueia com evidência, com o verify verde.',
14
+ plan: 'Confere o plano: o problema, o que fica de fora, os cenários com a prova de cada um e as fatias. Depois dele o plan.md congela (só a errata cresce).',
15
+ uiux: 'Confere o design das telas (mockups e checklist de tela). É opcional: só existe quando a feature passa pela fase de UI/UX, depois do plano.',
16
+ review: 'Confere a avaliação independente em contexto novo: verificador e revisor (e segurança, quando o diff toca auth, segredo ou dado pessoal), cada um com veredito SHIP ou FIX sobre o mesmo commit. Só bloqueia com evidência, com o verify verde e todo cenário provado.',
18
17
  };
19
18
 
20
19
  const GATE_PREFIX = 'gate-';
@@ -30,7 +30,7 @@ function categoryColors(standards) {
30
30
  return Object.fromEntries(cats.map((c, i) => [c, PALETTE[i % PALETTE.length]]));
31
31
  }
32
32
 
33
- /** Resolve `?sel=` to a standard: by id first, then by the path a task cites. */
33
+ // Resolve `?sel=` to a standard: by id first, then by the path a plan cites.
34
34
  export function findStandard(list, sel) {
35
35
  if (!sel) return null;
36
36
  return list.find((s) => s.id === sel) || list.find((s) => s.path === standardKey(sel)) || null;
@@ -15,7 +15,7 @@ import { loadTelemetryAllTrees, aggregateCost, dedupeEvents } from '../../lib/te
15
15
  import { isFailedToolCall } from '../../../framework/hooks/shared/telemetry-logger.js';
16
16
 
17
17
  /** Kinds a person follows by default; the rest are one click away. */
18
- export const DEFAULT_KINDS = ['prompt', 'question', 'skill', 'dispatch', 'handback', 'task', 'verify', 'gate', 'failure'];
18
+ export const DEFAULT_KINDS = ['prompt', 'question', 'skill', 'dispatch', 'handback', 'verify', 'gate', 'failure'];
19
19
  export const ALL_KINDS = [...DEFAULT_KINDS, 'write', 'read', 'command', 'tool'];
20
20
 
21
21
  const GATE_COMMAND = /\bmorph-spec\s+(approve|gate-check|gate-decision|advance|finish)\b/;
@@ -50,14 +50,14 @@ function nodesSummary(nodes) {
50
50
  * @returns {null | {
51
51
  * id: string, ts: string, feature: string|null, phase: string|null,
52
52
  * kind: string, title: string, detail: string|null, status: 'ok'|'fail'|null,
53
- * refs: { persona?: string, skill?: string, task?: string, path?: string },
53
+ * refs: { persona?: string, skill?: string, path?: string },
54
54
  * nodes?: Array<{name: string, status: string|null}>
55
55
  * }}
56
56
  */
57
57
  export function describeEvent(e) {
58
58
  if (!e || typeof e !== 'object' || SILENT_EVENTS.has(e.event)) return null;
59
59
  const base = {
60
- id: e.eventId || `${e.ts}:${e.event}:${e.tool || ''}:${e.task || ''}`,
60
+ id: e.eventId || `${e.ts}:${e.event}:${e.tool || ''}`,
61
61
  ts: e.ts || null,
62
62
  feature: e.feature || null,
63
63
  phase: e.phase || null,
@@ -78,18 +78,10 @@ export function describeEvent(e) {
78
78
  if (text.startsWith('<')) return null;
79
79
  return line('prompt', 'Você escreveu', { detail: clip(text) });
80
80
  }
81
- if (e.event === 'TaskStart') return line('task', `${e.task} começou`, { refs: { task: e.task } });
82
- if (e.event === 'TaskEnd') {
83
- const done = e.status === 'done';
84
- return line('task', done ? `${e.task} concluída` : `${e.task} terminou (${e.status ?? 'sem status'})`, {
85
- status: done ? 'ok' : null, refs: { task: e.task },
86
- });
87
- }
88
81
  if (e.event === 'Verify') {
89
82
  const ok = e.status === 'pass';
90
- const scope = e.task ? e.task : 'feature inteira';
91
- return line('verify', `verify ${scope} ${ok ? 'passou' : 'falhou'}`, {
92
- status: ok ? 'ok' : 'fail', nodes: nodesSummary(e.nodes), refs: e.task ? { task: e.task } : {},
83
+ return line('verify', `verify ${ok ? 'passou' : 'falhou'}`, {
84
+ status: ok ? 'ok' : 'fail', nodes: nodesSummary(e.nodes),
93
85
  detail: e.persisted === false && e.reason ? `não carimbou: ${e.reason}` : null,
94
86
  });
95
87
  }
@@ -344,8 +336,6 @@ export function featureCost(events, feature) {
344
336
  dispatches: c.dispatches,
345
337
  bytesTotal: c.bytesTotal,
346
338
  bytesByKind: c.bytesByKind,
347
- taskDurationSeconds: c.taskDurationSeconds,
348
- tasks: c.tasks,
349
339
  events: c.total,
350
340
  };
351
341
  }
@@ -13,7 +13,7 @@ const GENERIC_DISPATCH = 'morph-{id}';
13
13
  *
14
14
  * v5 has no phase list on the persona: a phase dispatches a persona when its
15
15
  * skill (`phases.json → skill`) names `morph-{personaId}` on a dispatch line
16
- * (morph-uiux → ui-designer, morph-plan → scout, morph-review → evaluator).
16
+ * (morph-uiux → ui-designer, morph-plan → scout, morph-review → verifier, reviewer, security).
17
17
  * A skill that dispatches the generic `morph-{id}` runs every implementer —
18
18
  * the same `isImplementer` the validator and the installer use.
19
19
  *