@polymorphism-tech/morph-spec 8.46.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 (304) hide show
  1. package/CLAUDE.md +53 -44
  2. package/README.md +3 -4
  3. package/bin/morph-spec.js +38 -55
  4. package/bin/validate.js +5 -134
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +2 -2
  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/create-umbraco-lp.md +2 -0
  12. package/framework/commands/migrate-lp-to-umbraco.md +2 -0
  13. package/framework/commands/morph-apply.md +5 -28
  14. package/framework/commands/morph-archive.md +2 -2
  15. package/framework/commands/morph-hotfix.md +8 -8
  16. package/framework/commands/morph-infra.md +17 -203
  17. package/framework/commands/morph-media.md +3 -0
  18. package/framework/commands/morph-preflight.md +7 -31
  19. package/framework/commands/morph-proposal.md +2 -50
  20. package/framework/commands/morph-status.md +3 -3
  21. package/framework/commands/morph-troubleshoot.md +2 -14
  22. package/framework/docs/CLI.md +585 -286
  23. package/framework/evals/review-checklist.md +82 -41
  24. package/framework/hooks/README.md +10 -11
  25. package/framework/hooks/claude-code/core/comment-guard.js +1 -1
  26. package/framework/hooks/claude-code/core/denylist.json +33 -3
  27. package/framework/hooks/claude-code/core/enforce-phase-writes.js +16 -20
  28. package/framework/hooks/claude-code/core/gate-guard.js +2 -11
  29. package/framework/hooks/claude-code/core/pre-bash-denylist.js +38 -0
  30. package/framework/hooks/claude-code/core/protect-spec-files.js +30 -242
  31. package/framework/hooks/claude-code/core/state-sync.js +17 -19
  32. package/framework/hooks/claude-code/core/telemetry-log.js +3 -3
  33. package/framework/hooks/claude-code/core/validate-completion.js +33 -97
  34. package/framework/hooks/claude-code/core/warn-bash-source-write.js +31 -0
  35. package/framework/hooks/claude-code/core/worktree-context.js +5 -4
  36. package/framework/hooks/claude-code/statusline.py +55 -41
  37. package/framework/hooks/package.json +3 -0
  38. package/framework/hooks/shared/activity-logger.js +1 -1
  39. package/framework/hooks/shared/build-check.js +10 -6
  40. package/framework/hooks/shared/command-tokens.js +169 -0
  41. package/framework/hooks/shared/comment-rules.js +1 -1
  42. package/framework/hooks/shared/comment-scanner.js +10 -23
  43. package/framework/hooks/shared/comment-stripper.js +1 -1
  44. package/framework/hooks/shared/e2e-check.js +67 -31
  45. package/framework/hooks/shared/errata.js +72 -0
  46. package/framework/hooks/shared/feature-locator.js +1 -1
  47. package/framework/hooks/shared/frontend-locations.js +6 -0
  48. package/framework/hooks/shared/gate-question.js +18 -0
  49. package/framework/hooks/shared/phase-utils.js +15 -32
  50. package/framework/hooks/shared/slice-progress.js +118 -0
  51. package/framework/hooks/shared/spawn-command.js +23 -0
  52. package/framework/hooks/shared/state-reader.js +61 -87
  53. package/framework/hooks/shared/state-writer.js +3 -3
  54. package/framework/mcp/registry.json +3 -4
  55. package/framework/phases.json +23 -39
  56. package/framework/rules/ai-agents-standards.md +18 -16
  57. package/framework/rules/asaas-standards.md +3 -1
  58. package/framework/rules/csharp-standards.md +10 -8
  59. package/framework/rules/data-standards.md +5 -5
  60. package/framework/rules/dotnet-templates.md +1 -1
  61. package/framework/rules/frontend-standards.md +4 -2
  62. package/framework/rules/gsap-standards.md +10 -5
  63. package/framework/rules/hangfire-standards.md +3 -1
  64. package/framework/rules/infrastructure-standards.md +4 -2
  65. package/framework/rules/neon-auth-standards.md +3 -1
  66. package/framework/rules/neon-standards.md +6 -4
  67. package/framework/rules/nextjs-standards.md +13 -11
  68. package/framework/rules/nextjs-templates.md +1 -1
  69. package/framework/rules/resend-standards.md +3 -1
  70. package/framework/schemas/config.schema.json +14 -9
  71. package/framework/schemas/feature.schema.json +15 -52
  72. package/framework/skills/README.md +4 -5
  73. package/framework/skills/core/create-umbraco-lp/SKILL.md +3 -3
  74. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +3 -3
  75. package/framework/skills/core/morph-apply/SKILL.md +150 -223
  76. package/framework/skills/core/morph-hotfix/SKILL.md +34 -43
  77. package/framework/skills/core/morph-machine/SKILL.md +4 -4
  78. package/framework/skills/core/morph-media/SKILL.md +3 -3
  79. package/framework/skills/core/morph-plan/SKILL.md +138 -300
  80. package/framework/skills/core/morph-review/SKILL.md +112 -137
  81. package/framework/skills/core/morph-standards/SKILL.md +29 -41
  82. package/framework/skills/core/morph-uiux/SKILL.md +78 -153
  83. package/framework/standards/STANDARDS.json +90 -1219
  84. package/framework/standards/STANDARDS.meta.json +1305 -0
  85. package/framework/standards/ai-agents/evals-with-cache.md +2 -3
  86. package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
  87. package/framework/standards/ai-agents/modalities-speech-to-text.md +36 -26
  88. package/framework/standards/ai-agents/prompt-sources.md +6 -4
  89. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  90. package/framework/standards/ai-agents/rag-custom-pgvector.md +67 -39
  91. package/framework/standards/ai-agents/setup.md +2 -2
  92. package/framework/standards/ai-agents/structured-output.md +1 -1
  93. package/framework/standards/ai-agents/sweet-spot.md +1 -1
  94. package/framework/standards/architecture/code-comments.md +8 -8
  95. package/framework/standards/architecture/vertical-slice/vertical-slice.md +18 -9
  96. package/framework/standards/backend/api/validation.md +135 -47
  97. package/framework/standards/backend/database/postgresql/database.md +11 -7
  98. package/framework/standards/backend/database/vector-search-rag.md +207 -324
  99. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +14 -8
  100. package/framework/standards/backend/dotnet/result-pattern.md +40 -6
  101. package/framework/standards/backend/dotnet/scalar-ui.md +13 -7
  102. package/framework/standards/backend/dotnet/scrutor.md +1 -1
  103. package/framework/standards/backend/dotnet/testing.md +1 -1
  104. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +7 -7
  105. package/framework/standards/backend/integrations/asaas/asaas-api.md +309 -165
  106. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +134 -154
  107. package/framework/standards/data/nosql/cache/redis.md +120 -44
  108. package/framework/standards/data/nosql/cosmos-db.md +114 -71
  109. package/framework/standards/frontend/design-system/animations.md +1 -1
  110. package/framework/standards/frontend/design-system/naming.md +1 -1
  111. package/framework/standards/frontend/nextjs/motion-patterns.md +45 -29
  112. package/framework/standards/frontend/nextjs/naming-conventions.md +1 -1
  113. package/framework/standards/infrastructure/docker/coolify-deploy.md +8 -6
  114. package/framework/standards/infrastructure/neon/neon-pgvector.md +120 -75
  115. package/framework/standards/infrastructure/neon/neon-setup.md +53 -31
  116. package/framework/standards/integration/mcp/mcp-tools.md +7 -7
  117. package/framework/templates/README.md +2 -3
  118. package/framework/templates/REGISTRY.json +6 -170
  119. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +2 -2
  120. package/framework/templates/code/dotnet/extensions/database-extensions.cs +1 -1
  121. package/framework/templates/code/dotnet/extensions/result-extensions.cs +27 -0
  122. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +2 -2
  123. package/framework/templates/docs/plan.md +72 -0
  124. package/framework/templates/dotnet/ai-kit/README.md +1 -9
  125. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +1 -1
  126. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
  127. package/framework/templates/infrastructure/docker/coolify.template.json +3 -3
  128. package/framework/templates/infrastructure/github/README.md +2 -1
  129. package/framework/templates/infrastructure/github/workflows/deploy-coolify.yml.hbs +2 -1
  130. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
  131. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
  132. package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
  133. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
  134. package/package.json +1 -1
  135. package/src/commands/e2e/e2e.js +2 -2
  136. package/src/commands/observability/cost.js +18 -60
  137. package/src/commands/observability/telemetry.js +3 -5
  138. package/src/commands/project/doctor-procs.js +1 -1
  139. package/src/commands/project/doctor.js +9 -9
  140. package/src/commands/project/fleet.js +4 -4
  141. package/src/commands/project/heavy.js +3 -3
  142. package/src/commands/project/init.js +6 -1
  143. package/src/commands/project/setup-infra-cmd.js +2 -1
  144. package/src/commands/project/stack-scan.js +58 -0
  145. package/src/commands/project/standards.js +79 -25
  146. package/src/commands/project/status.js +29 -69
  147. package/src/commands/project/update.js +33 -19
  148. package/src/commands/project/worktree.js +1 -1
  149. package/src/commands/state/advance.js +62 -46
  150. package/src/commands/state/approve.js +95 -138
  151. package/src/commands/state/archive.js +20 -84
  152. package/src/commands/state/auto-approval.js +68 -0
  153. package/src/commands/state/create.js +28 -1
  154. package/src/commands/state/delete.js +1 -1
  155. package/src/commands/state/finish.js +11 -23
  156. package/src/commands/state/gate-check.js +86 -95
  157. package/src/commands/state/gate-decision.js +6 -4
  158. package/src/commands/state/handoff.js +1 -1
  159. package/src/commands/state/list.js +9 -9
  160. package/src/commands/state/report-check.js +157 -311
  161. package/src/commands/state/retype.js +9 -3
  162. package/src/commands/validation/mutate.js +77 -14
  163. package/src/commands/validation/plan-check.js +81 -0
  164. package/src/commands/validation/validate-feature.js +60 -135
  165. package/src/commands/validation/verify.js +41 -82
  166. package/src/core/paths/output-schema.js +20 -67
  167. package/src/core/state/gate-order.js +5 -6
  168. package/src/core/state/lifecycle-audit.js +2 -2
  169. package/src/core/state/state-manager.js +17 -98
  170. package/src/core/templates/template-registry.js +2 -2
  171. package/src/core/templates/template-seed.js +22 -0
  172. package/src/core/work-types.js +10 -10
  173. package/src/dashboard/app/App.jsx +0 -2
  174. package/src/dashboard/app/components/ActivityFeed.jsx +2 -3
  175. package/src/dashboard/app/components/CommandPalette.jsx +7 -8
  176. package/src/dashboard/app/components/EntityLink.jsx +1 -2
  177. package/src/dashboard/app/components/Markdown.jsx +1 -1
  178. package/src/dashboard/app/components/Sidebar.jsx +1 -2
  179. package/src/dashboard/app/hooks/useMorphQuery.js +1 -1
  180. package/src/dashboard/app/lib/phases.js +2 -4
  181. package/src/dashboard/app/pages/Activity.jsx +1 -1
  182. package/src/dashboard/app/pages/FeatureDetail.jsx +139 -129
  183. package/src/dashboard/app/pages/Features.jsx +9 -9
  184. package/src/dashboard/app/pages/Guide.jsx +2 -2
  185. package/src/dashboard/app/pages/PhasePipeline.jsx +3 -4
  186. package/src/dashboard/app/pages/RulesMap.jsx +3 -3
  187. package/src/dashboard/app/pages/SkillsLibrary.jsx +3 -3
  188. package/src/dashboard/app/pages/Standards.jsx +1 -1
  189. package/src/dashboard/scanner/activity-scanner.js +6 -16
  190. package/src/dashboard/scanner/agents-scanner.js +1 -1
  191. package/src/dashboard/scanner/docs-scanner.js +5 -23
  192. package/src/dashboard/scanner/features-scanner.js +95 -60
  193. package/src/dashboard/scanner/index.js +0 -2
  194. package/src/dashboard/scanner/project-api.js +1 -1
  195. package/src/dashboard/scanner/rules-scanner.js +15 -10
  196. package/src/dashboard/scanner/skills-scanner.js +2 -3
  197. package/src/dashboard/scanner/standards-scanner.js +2 -1
  198. package/src/lib/ci/ci-verify.js +17 -1
  199. package/src/lib/ci/github-ci.js +1 -1
  200. package/src/lib/detectors/index.js +3 -8
  201. package/src/lib/generators/handoff-generator.js +34 -65
  202. package/src/lib/generators/settings-generator.js +11 -89
  203. package/src/lib/git/evidence-staleness.js +1 -1
  204. package/src/lib/git/tree-fingerprint.js +19 -7
  205. package/src/lib/git/tree-state.js +2 -2
  206. package/src/lib/heavy/cleanup.js +1 -5
  207. package/src/lib/installers/memory-installer.js +6 -8
  208. package/src/lib/mutation/commands.js +3 -2
  209. package/src/lib/{tasks/dotnet-test-introspection.js → mutation/dotnet-introspection.js} +17 -282
  210. package/src/lib/mutation/plan.js +14 -6
  211. package/src/lib/mutation/runner.js +1 -1
  212. package/src/lib/mutation/subset.js +46 -9
  213. package/src/lib/plan/artifact-vocabulary.js +60 -0
  214. package/src/lib/plan/checks.js +234 -0
  215. package/src/lib/plan/plan-parser.js +209 -0
  216. package/src/lib/plan/plan-template.js +18 -0
  217. package/src/lib/review-reports.js +222 -0
  218. package/src/lib/risk-detector.js +134 -135
  219. package/src/lib/specs/adr-index.js +50 -69
  220. package/src/lib/specs/design-seed.js +18 -0
  221. package/src/lib/specs/frontmatter.js +44 -0
  222. package/src/lib/specs/gfm-table.js +17 -0
  223. package/src/lib/specs/old-format.js +68 -0
  224. package/src/lib/specs/revocations.js +37 -95
  225. package/src/lib/specs/screen-checklist.js +18 -17
  226. package/src/lib/stack/dotnet-build-ignores.js +47 -0
  227. package/src/lib/stack/stack-profile.js +1 -1
  228. package/src/lib/stack/stack-scan.js +267 -0
  229. package/src/lib/standards/registry.js +43 -0
  230. package/src/lib/standards/sections.js +58 -0
  231. package/src/lib/tasks/eval-node.js +4 -12
  232. package/src/lib/tasks/test-nodes.js +1 -74
  233. package/src/lib/tasks/test-runner.js +75 -117
  234. package/src/lib/telemetry/metrics.js +8 -69
  235. package/src/lib/telemetry/telemetry-reader.js +5 -118
  236. package/src/lib/telemetry/verify-event.js +5 -7
  237. package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
  238. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -2
  239. package/src/lib/validators/reuse/reuse-validator.js +24 -48
  240. package/src/lib/validators/reuse/symbol-scanner.js +2 -4
  241. package/src/lib/validators/traceability-check.js +85 -129
  242. package/src/lib/validators/validation-runner.js +8 -88
  243. package/src/lib/validators/verify-runner.js +134 -301
  244. package/src/lib/worktree/docker-audit.js +1 -2
  245. package/src/lib/worktree/fleet-scanner.js +25 -38
  246. package/src/scripts/setup-infra.js +42 -22
  247. package/src/utils/agents-installer.js +92 -13
  248. package/src/utils/claude-settings-manager.js +0 -35
  249. package/src/utils/file-copier.js +34 -6
  250. package/src/utils/hooks-installer.js +16 -9
  251. package/src/utils/skills-installer.js +1 -2
  252. package/framework/commands/commit.md +0 -166
  253. package/framework/evals/trace-schema.md +0 -67
  254. package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
  255. package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
  256. package/framework/hooks/git/pre-commit/specs.sh +0 -50
  257. package/framework/hooks/shared/tasks-validator.js +0 -111
  258. package/framework/rubrics/backend.json +0 -41
  259. package/framework/rubrics/default.json +0 -36
  260. package/framework/rubrics/frontend.json +0 -36
  261. package/framework/rules/morph-task-tracking.md +0 -54
  262. package/framework/rules/morph-workflow.md +0 -244
  263. package/framework/schemas/tasks.schema.json +0 -79
  264. package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
  265. package/framework/skills/core/morph-implement/SKILL.md +0 -194
  266. package/framework/skills/core/morph-proposal/SKILL.md +0 -152
  267. package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
  268. package/framework/templates/docs/proposal.md +0 -192
  269. package/framework/templates/docs/spec.md +0 -57
  270. package/framework/templates/docs/ui-components.md +0 -52
  271. package/framework/templates/docs/ui-design-system.md +0 -76
  272. package/framework/templates/docs/ui-flows.md +0 -167
  273. package/framework/templates/examples/spec-examples.md +0 -91
  274. package/framework/templates/feature/decisions.md +0 -124
  275. package/framework/templates/feature/recap.md +0 -145
  276. package/framework/templates/project/validate.js +0 -97
  277. package/framework/templates/tasks.template.json +0 -17
  278. package/src/commands/state/usecase.js +0 -143
  279. package/src/commands/tasks/dag.js +0 -181
  280. package/src/dashboard/app/pages/Rubrics.jsx +0 -97
  281. package/src/dashboard/scanner/rubrics-scanner.js +0 -12
  282. package/src/lib/agents/output-collector.js +0 -76
  283. package/src/lib/evaluator-report.js +0 -91
  284. package/src/lib/specs/acervo-format.js +0 -70
  285. package/src/lib/specs/acervo-lifecycle.js +0 -264
  286. package/src/lib/specs/done-criteria-check.js +0 -200
  287. package/src/lib/specs/usecase-check.js +0 -301
  288. package/src/lib/specs/usecase-promoter.js +0 -301
  289. package/src/lib/specs/usecase-registry.js +0 -237
  290. package/src/lib/tasks/dispatch-planner.js +0 -319
  291. package/src/lib/tasks/mermaid-renderer.js +0 -132
  292. package/src/lib/tasks/task-classifier.js +0 -30
  293. package/src/lib/tasks/task-parser.js +0 -209
  294. package/src/lib/tasks/tasks-json-parser.js +0 -296
  295. package/src/lib/tasks/test-scope.js +0 -597
  296. package/src/lib/validators/architecture/architecture-validator.js +0 -270
  297. package/src/lib/validators/architecture/index.js +0 -7
  298. package/src/lib/validators/css/css-validator.js +0 -340
  299. package/src/lib/validators/css/index.js +0 -7
  300. package/src/lib/validators/design-system/design-system-validator.js +0 -237
  301. package/src/lib/validators/design-system/index.js +0 -7
  302. package/src/lib/validators/score-aggregator.js +0 -244
  303. package/src/lib/validators/ui/index.js +0 -7
  304. package/src/lib/validators/ui/ui-contrast-validator.js +0 -422
@@ -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-';
@@ -15,7 +15,7 @@ const NODE_W = 260;
15
15
  // ─── Standard resolution ──────────────────────────────────────────────────────
16
16
 
17
17
  /**
18
- * A rule's @-import → the standard it names in the registry. `id` is what the
18
+ * A rule's route → the standard it names in the registry. `id` is what the
19
19
  * Standards page selects by; with no registry match the path itself is passed,
20
20
  * so the link still says which file the rule meant.
21
21
  */
@@ -196,7 +196,7 @@ function RuleDetail({ rule, byPath }) {
196
196
  )}
197
197
  </Section>
198
198
 
199
- <Section title="Standards importados">
199
+ <Section title="Standards roteados (resumo)">
200
200
  {rule.standardRefs.length > 0 ? (
201
201
  <div className="flex flex-wrap gap-1.5">
202
202
  {rule.standardRefs.map((ref) => {
@@ -209,7 +209,7 @@ function RuleDetail({ rule, byPath }) {
209
209
  })}
210
210
  </div>
211
211
  ) : (
212
- <p className="text-xs">Esta regra não importa nenhum standard.</p>
212
+ <p className="text-xs">Esta regra não roteia nenhum standard.</p>
213
213
  )}
214
214
  </Section>
215
215
 
@@ -315,10 +315,10 @@ export function SkillDetail({ skill, phases = morphData.phases?.list || [] }) {
315
315
  {skill.description && <p className="leading-relaxed text-[var(--color-fg)]">{skill.description}</p>}
316
316
 
317
317
  <dl className="grid grid-cols-[max-content_1fr] gap-x-4 gap-y-2 rounded-xl bg-white/[0.03] px-4 py-3 shadow-[inset_0_0_0_1px_var(--color-hair)]">
318
- {skill.whenToInvoke && (
318
+ {skill.whenToUse && (
319
319
  <>
320
320
  <dt className="text-xs leading-6 text-[var(--color-faint)]">Quando usar</dt>
321
- <dd className="leading-6">{skill.whenToInvoke}</dd>
321
+ <dd className="leading-6">{skill.whenToUse}</dd>
322
322
  </>
323
323
  )}
324
324
  <dt className="text-xs leading-6 text-[var(--color-faint)]">Fase</dt>
@@ -389,7 +389,7 @@ export default function SkillsLibrary() {
389
389
  const isMatch = useCallback((s) => {
390
390
  const q = search.trim().toLowerCase();
391
391
  if (q) {
392
- const hay = [s.id, s.name, s.description, s.whenToInvoke].filter(Boolean).join(' ').toLowerCase();
392
+ const hay = [s.id, s.name, s.description, s.whenToUse].filter(Boolean).join(' ').toLowerCase();
393
393
  if (!hay.includes(q)) return false;
394
394
  }
395
395
  if (phaseFilter) {
@@ -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;