@polymorphism-tech/morph-spec 8.47.0 → 9.1.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 (377) hide show
  1. package/CLAUDE.md +51 -43
  2. package/README.md +39 -38
  3. package/bin/morph-spec.js +41 -113
  4. package/bin/validate.js +1 -147
  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 +86 -21
  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-status.md +4 -4
  17. package/framework/docs/CLI.md +574 -286
  18. package/framework/evals/review-checklist.md +82 -41
  19. package/framework/hooks/README.md +10 -11
  20. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
  21. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
  22. package/framework/hooks/claude-code/core/chain.js +4 -65
  23. package/framework/hooks/claude-code/core/comment-guard.js +3 -17
  24. package/framework/hooks/claude-code/core/denylist.json +33 -3
  25. package/framework/hooks/claude-code/core/enforce-phase-writes.js +15 -63
  26. package/framework/hooks/claude-code/core/gate-guard.js +9 -78
  27. package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
  28. package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
  29. package/framework/hooks/claude-code/core/heavy-release.js +2 -13
  30. package/framework/hooks/claude-code/core/loop-detect.js +2 -31
  31. package/framework/hooks/claude-code/core/pre-bash-denylist.js +36 -2
  32. package/framework/hooks/claude-code/core/protect-spec-files.js +25 -297
  33. package/framework/hooks/claude-code/core/state-sync.js +15 -85
  34. package/framework/hooks/claude-code/core/telemetry-log.js +15 -96
  35. package/framework/hooks/claude-code/core/validate-completion.js +37 -147
  36. package/framework/hooks/claude-code/core/warn-bash-source-write.js +23 -0
  37. package/framework/hooks/claude-code/core/worktree-context.js +15 -114
  38. package/framework/hooks/claude-code/core/worktree-create.js +6 -70
  39. package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
  40. package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
  41. package/framework/hooks/claude-code/statusline.py +55 -41
  42. package/framework/hooks/package.json +3 -0
  43. package/framework/hooks/shared/activity-logger.js +9 -89
  44. package/framework/hooks/shared/agent-reap.js +2 -17
  45. package/framework/hooks/shared/build-check.js +11 -76
  46. package/framework/hooks/shared/claim-registry.js +9 -119
  47. package/framework/hooks/shared/command-tokens.js +168 -44
  48. package/framework/hooks/shared/comment-rules.js +5 -20
  49. package/framework/hooks/shared/comment-scanner.js +11 -43
  50. package/framework/hooks/shared/comment-stripper.js +11 -85
  51. package/framework/hooks/shared/e2e-check.js +0 -0
  52. package/framework/hooks/shared/errata.js +59 -0
  53. package/framework/hooks/shared/feature-locator.js +11 -95
  54. package/framework/hooks/shared/frontend-locations.js +4 -0
  55. package/framework/hooks/shared/gate-question.js +12 -0
  56. package/framework/hooks/shared/git-exclude.js +3 -20
  57. package/framework/hooks/shared/git-paths.js +8 -104
  58. package/framework/hooks/shared/graph-freshness.js +22 -166
  59. package/framework/hooks/shared/graph-refresh.js +21 -149
  60. package/framework/hooks/shared/heavy-command.js +4 -12
  61. package/framework/hooks/shared/hook-response.js +8 -83
  62. package/framework/hooks/shared/morph-cli.js +8 -52
  63. package/framework/hooks/shared/payload-utils.js +10 -132
  64. package/framework/hooks/shared/phase-utils.js +14 -72
  65. package/framework/hooks/shared/redact-secrets.js +4 -15
  66. package/framework/hooks/shared/reparse-scan.js +6 -66
  67. package/framework/hooks/shared/slice-progress.js +99 -0
  68. package/framework/hooks/shared/source-files.js +4 -17
  69. package/framework/hooks/shared/spawn-command.js +23 -0
  70. package/framework/hooks/shared/state-reader.js +51 -247
  71. package/framework/hooks/shared/state-writer.js +9 -54
  72. package/framework/hooks/shared/stdin-reader.js +1 -11
  73. package/framework/hooks/shared/telemetry-logger.js +7 -67
  74. package/framework/hooks/shared/transcript-usage.js +7 -29
  75. package/framework/hooks/shared/worktree-root.js +7 -29
  76. package/framework/mcp/registry.json +3 -4
  77. package/framework/phases.json +23 -39
  78. package/framework/rules/csharp-standards.md +1 -1
  79. package/framework/rules/frontend-standards.md +1 -0
  80. package/framework/rules/hangfire-standards.md +1 -1
  81. package/framework/rules/neon-standards.md +1 -1
  82. package/framework/schemas/config.schema.json +14 -9
  83. package/framework/schemas/feature.schema.json +15 -50
  84. package/framework/skills/README.md +3 -4
  85. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  86. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  87. package/framework/skills/core/morph-apply/SKILL.md +149 -222
  88. package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
  89. package/framework/skills/core/morph-machine/SKILL.md +2 -2
  90. package/framework/skills/core/morph-media/SKILL.md +1 -1
  91. package/framework/skills/core/morph-plan/SKILL.md +137 -299
  92. package/framework/skills/core/morph-review/SKILL.md +111 -136
  93. package/framework/skills/core/morph-standards/SKILL.md +27 -39
  94. package/framework/skills/core/morph-uiux/SKILL.md +94 -152
  95. package/framework/standards/STANDARDS.json +109 -1219
  96. package/framework/standards/STANDARDS.meta.json +1319 -0
  97. package/framework/standards/ai-agents/agent-archetypes.md +2 -2
  98. package/framework/standards/ai-agents/agent-session.md +1 -1
  99. package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
  100. package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
  101. package/framework/standards/ai-agents/evals-with-cache.md +1 -2
  102. package/framework/standards/ai-agents/guardrails.md +1 -1
  103. package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
  104. package/framework/standards/ai-agents/observability-patterns.md +1 -1
  105. package/framework/standards/ai-agents/production.md +1 -1
  106. package/framework/standards/ai-agents/prompt-sources.md +2 -2
  107. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  108. package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
  109. package/framework/standards/ai-agents/setup.md +3 -3
  110. package/framework/standards/ai-agents/sweet-spot.md +2 -2
  111. package/framework/standards/ai-agents/tool-adapters.md +0 -2
  112. package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
  113. package/framework/standards/ai-agents/workflows.md +1 -1
  114. package/framework/standards/architecture/code-comments.md +7 -7
  115. package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
  116. package/framework/standards/backend/api/validation.md +1 -1
  117. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
  118. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
  119. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
  120. package/framework/standards/data/nosql/cache/redis.md +1 -1
  121. package/framework/standards/data/nosql/cosmos-db.md +3 -3
  122. package/framework/standards/frontend/admin-app/forms.md +1 -1
  123. package/framework/standards/frontend/design-system/art-direction.md +88 -0
  124. package/framework/standards/frontend/design-system/premium-finish.md +77 -2
  125. package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
  126. package/framework/standards/integration/mcp/mcp-tools.md +7 -7
  127. package/framework/templates/CLAUDE.md.core.template +3 -3
  128. package/framework/templates/README.md +2 -3
  129. package/framework/templates/REGISTRY.json +15 -179
  130. package/framework/templates/docs/plan.md +72 -0
  131. package/framework/templates/docs/ui-mockups.md +4 -0
  132. package/framework/templates/dotnet/ai-kit/README.md +1 -9
  133. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
  134. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
  135. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
  136. package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
  137. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
  138. package/package.json +1 -1
  139. package/src/commands/context/context-refresh.js +0 -16
  140. package/src/commands/dashboard/dashboard.js +2 -8
  141. package/src/commands/e2e/e2e.js +18 -105
  142. package/src/commands/graph/affected.js +0 -14
  143. package/src/commands/graph/build.js +11 -50
  144. package/src/commands/graph/explain.js +0 -16
  145. package/src/commands/graph/status.js +1 -12
  146. package/src/commands/mcp/mcp-setup.js +4 -62
  147. package/src/commands/observability/cost.js +17 -111
  148. package/src/commands/observability/telemetry.js +1 -14
  149. package/src/commands/project/branch.js +4 -45
  150. package/src/commands/project/claim.js +4 -48
  151. package/src/commands/project/doctor-procs.js +1 -1
  152. package/src/commands/project/doctor.js +53 -307
  153. package/src/commands/project/env.js +1 -27
  154. package/src/commands/project/fleet.js +6 -35
  155. package/src/commands/project/heavy.js +3 -3
  156. package/src/commands/project/init.js +8 -68
  157. package/src/commands/project/install-plugin-cmd.js +0 -1
  158. package/src/commands/project/setup-infra-cmd.js +2 -1
  159. package/src/commands/project/stack-scan.js +58 -0
  160. package/src/commands/project/standards.js +65 -72
  161. package/src/commands/project/status.js +28 -121
  162. package/src/commands/project/update.js +31 -223
  163. package/src/commands/project/worktree.js +109 -883
  164. package/src/commands/scaffold/scaffold.js +0 -25
  165. package/src/commands/state/advance.js +65 -120
  166. package/src/commands/state/approve.js +87 -229
  167. package/src/commands/state/archive.js +23 -139
  168. package/src/commands/state/auto-approval.js +65 -0
  169. package/src/commands/state/create.js +49 -109
  170. package/src/commands/state/delete.js +11 -63
  171. package/src/commands/state/finish.js +105 -494
  172. package/src/commands/state/gate-check.js +74 -183
  173. package/src/commands/state/gate-decision.js +8 -42
  174. package/src/commands/state/handoff.js +4 -31
  175. package/src/commands/state/list.js +21 -91
  176. package/src/commands/state/report-check.js +134 -338
  177. package/src/commands/state/retype.js +12 -28
  178. package/src/commands/validation/index.js +0 -3
  179. package/src/commands/validation/mutate.js +79 -19
  180. package/src/commands/validation/plan-check.js +81 -0
  181. package/src/commands/validation/validate-feature.js +52 -216
  182. package/src/commands/validation/verify.js +45 -160
  183. package/src/core/paths/output-schema.js +9 -155
  184. package/src/core/state/feature-identity.js +1 -25
  185. package/src/core/state/gate-order.js +2 -30
  186. package/src/core/state/lifecycle-audit.js +8 -100
  187. package/src/core/state/state-manager.js +32 -395
  188. package/src/core/templates/template-registry.js +2 -97
  189. package/src/core/templates/template-renderer.js +13 -93
  190. package/src/core/templates/template-seed.js +21 -0
  191. package/src/core/work-types.js +10 -45
  192. package/src/dashboard/app/App.jsx +1 -8
  193. package/src/dashboard/app/components/ActivityFeed.jsx +2 -8
  194. package/src/dashboard/app/components/CommandPalette.jsx +4 -11
  195. package/src/dashboard/app/components/DetailPanel.jsx +1 -5
  196. package/src/dashboard/app/components/EntityLink.jsx +0 -12
  197. package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
  198. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
  199. package/src/dashboard/app/components/Markdown.jsx +0 -9
  200. package/src/dashboard/app/components/PhaseRail.jsx +0 -11
  201. package/src/dashboard/app/components/SearchFilter.jsx +0 -4
  202. package/src/dashboard/app/components/Sidebar.jsx +0 -5
  203. package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
  204. package/src/dashboard/app/hooks/usePref.js +1 -5
  205. package/src/dashboard/app/lib/format.js +0 -2
  206. package/src/dashboard/app/lib/phases.js +3 -9
  207. package/src/dashboard/app/lib/project.js +0 -12
  208. package/src/dashboard/app/pages/Activity.jsx +1 -1
  209. package/src/dashboard/app/pages/AgentMap.jsx +1 -8
  210. package/src/dashboard/app/pages/FeatureDetail.jsx +139 -133
  211. package/src/dashboard/app/pages/Features.jsx +9 -16
  212. package/src/dashboard/app/pages/Guide.jsx +2 -3
  213. package/src/dashboard/app/pages/HookInspector.jsx +2 -23
  214. package/src/dashboard/app/pages/PhasePipeline.jsx +3 -7
  215. package/src/dashboard/app/pages/Portfolio.jsx +0 -1
  216. package/src/dashboard/app/pages/RulesMap.jsx +1 -16
  217. package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
  218. package/src/dashboard/app/pages/Standards.jsx +2 -17
  219. package/src/dashboard/scanner/activity-scanner.js +14 -83
  220. package/src/dashboard/scanner/agents-scanner.js +1 -23
  221. package/src/dashboard/scanner/docs-scanner.js +1 -7
  222. package/src/dashboard/scanner/feature-status.js +2 -5
  223. package/src/dashboard/scanner/features-scanner.js +92 -113
  224. package/src/dashboard/scanner/hooks-scanner.js +5 -28
  225. package/src/dashboard/scanner/index.js +0 -4
  226. package/src/dashboard/scanner/portfolio.js +5 -29
  227. package/src/dashboard/scanner/project-api.js +8 -54
  228. package/src/dashboard/scanner/rules-scanner.js +1 -2
  229. package/src/dashboard/scanner/skills-scanner.js +2 -12
  230. package/src/dashboard/scanner/standards-scanner.js +4 -7
  231. package/src/dashboard/vite-plugin-morph-data.js +5 -19
  232. package/src/lib/ai/ai-checks.js +7 -52
  233. package/src/lib/ai/ai-project-scan.js +14 -108
  234. package/src/lib/ai/code-audit.js +7 -63
  235. package/src/lib/ai/pin-drift.js +8 -51
  236. package/src/lib/ai/registry-audit.js +11 -90
  237. package/src/lib/ci/ci-verify.js +14 -13
  238. package/src/lib/ci/github-ci.js +9 -37
  239. package/src/lib/ci/workflow-scaffold.js +2 -10
  240. package/src/lib/detectors/claude-config-detector.js +13 -74
  241. package/src/lib/detectors/index.js +8 -59
  242. package/src/lib/e2e/compose-env.js +5 -66
  243. package/src/lib/e2e/stack-teardown.js +6 -40
  244. package/src/lib/generators/architecture-scanner.js +18 -156
  245. package/src/lib/generators/handoff-generator.js +37 -112
  246. package/src/lib/generators/scaffold-generator.js +5 -101
  247. package/src/lib/generators/settings-generator.js +11 -122
  248. package/src/lib/git/branch-classifier.js +11 -84
  249. package/src/lib/git/default-branch.js +14 -169
  250. package/src/lib/git/evidence-staleness.js +2 -1
  251. package/src/lib/git/gh.js +2 -17
  252. package/src/lib/git/pending-prs.js +6 -37
  253. package/src/lib/git/pr-files.js +3 -28
  254. package/src/lib/git/pr-index.js +3 -34
  255. package/src/lib/git/tree-fingerprint.js +17 -17
  256. package/src/lib/git/tree-state.js +6 -33
  257. package/src/lib/graph/god-nodes.js +2 -22
  258. package/src/lib/graph/graph-health.js +3 -24
  259. package/src/lib/graph/graph-loader.js +5 -56
  260. package/src/lib/graph/graphify-runtime.js +7 -37
  261. package/src/lib/graph/graphifyignore.js +8 -74
  262. package/src/lib/graph/sanitize.js +16 -136
  263. package/src/lib/heavy/cleanup.js +2 -6
  264. package/src/lib/installers/effects.js +8 -93
  265. package/src/lib/installers/mcp-installer.js +9 -169
  266. package/src/lib/installers/memory-installer.js +8 -33
  267. package/src/lib/installers/templates-reconcile.js +5 -60
  268. package/src/lib/mutation/commands.js +3 -2
  269. package/src/lib/mutation/dotnet-introspection.js +281 -0
  270. package/src/lib/mutation/plan.js +14 -6
  271. package/src/lib/mutation/runner.js +1 -1
  272. package/src/lib/mutation/subset.js +47 -13
  273. package/src/lib/plan/artifact-vocabulary.js +40 -0
  274. package/src/lib/plan/checks.js +208 -0
  275. package/src/lib/plan/plan-parser.js +204 -0
  276. package/src/lib/plan/plan-template.js +18 -0
  277. package/src/lib/projects/registry.js +7 -44
  278. package/src/lib/review-reports.js +204 -0
  279. package/src/lib/risk-detector.js +131 -274
  280. package/src/lib/specs/adr-index.js +38 -85
  281. package/src/lib/specs/design-seed.js +18 -0
  282. package/src/lib/specs/frontmatter.js +31 -0
  283. package/src/lib/specs/gfm-table.js +11 -0
  284. package/src/lib/specs/revocations.js +31 -102
  285. package/src/lib/specs/screen-checklist.js +56 -132
  286. package/src/lib/stack/dotnet-build-ignores.js +43 -0
  287. package/src/lib/stack/stack-profile.js +1 -28
  288. package/src/lib/stack/stack-scan.js +260 -0
  289. package/src/lib/stack-filter.js +4 -27
  290. package/src/lib/standards/registry.js +37 -0
  291. package/src/lib/standards/sections.js +47 -0
  292. package/src/lib/standards/verified-against.js +19 -191
  293. package/src/lib/tasks/eval-node.js +23 -179
  294. package/src/lib/tasks/linux-runner.js +18 -142
  295. package/src/lib/tasks/test-nodes.js +43 -313
  296. package/src/lib/tasks/test-runner.js +107 -447
  297. package/src/lib/telemetry/metrics.js +13 -103
  298. package/src/lib/telemetry/telemetry-reader.js +13 -238
  299. package/src/lib/telemetry/verify-event.js +4 -34
  300. package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
  301. package/src/lib/validators/code-budget/index.js +0 -7
  302. package/src/lib/validators/contracts/contract-compliance-validator.js +4 -26
  303. package/src/lib/validators/contracts/index.js +0 -6
  304. package/src/lib/validators/nextjs/index.js +0 -5
  305. package/src/lib/validators/nextjs/next-component-validator.js +4 -81
  306. package/src/lib/validators/packages/index.js +0 -6
  307. package/src/lib/validators/packages/package-validator.js +6 -56
  308. package/src/lib/validators/reuse/index.js +0 -8
  309. package/src/lib/validators/reuse/reuse-validator.js +24 -137
  310. package/src/lib/validators/reuse/symbol-scanner.js +6 -62
  311. package/src/lib/validators/traceability-check.js +54 -156
  312. package/src/lib/validators/validation-runner.js +20 -125
  313. package/src/lib/validators/verify-runner.js +152 -575
  314. package/src/lib/work-type-detector.js +5 -67
  315. package/src/lib/worktree/docker-audit.js +17 -137
  316. package/src/lib/worktree/env-allocator.js +10 -98
  317. package/src/lib/worktree/feature-truth.js +12 -115
  318. package/src/lib/worktree/finish-backup.js +32 -242
  319. package/src/lib/worktree/fleet-scanner.js +31 -126
  320. package/src/lib/worktree/ignored-triage.js +15 -91
  321. package/src/lib/worktree/vscode-settings.js +2 -3
  322. package/src/lib/worktree/worktree-audit.js +21 -213
  323. package/src/lib/worktree/worktree-include.js +3 -5
  324. package/src/scripts/global-install.js +3 -9
  325. package/src/scripts/install-plugin.js +1 -13
  326. package/src/scripts/setup-infra.js +17 -138
  327. package/src/utils/agents-installer.js +92 -272
  328. package/src/utils/banner.js +4 -25
  329. package/src/utils/claude-md-injector.js +1 -37
  330. package/src/utils/claude-settings-manager.js +1 -87
  331. package/src/utils/file-copier.js +32 -90
  332. package/src/utils/hooks-installer.js +26 -296
  333. package/src/utils/skills-installer.js +4 -102
  334. package/src/utils/version-checker.js +4 -83
  335. package/framework/commands/morph-proposal.md +0 -57
  336. package/framework/evals/trace-schema.md +0 -67
  337. package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
  338. package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
  339. package/framework/hooks/git/pre-commit/specs.sh +0 -50
  340. package/framework/hooks/shared/tasks-validator.js +0 -111
  341. package/framework/rules/morph-task-tracking.md +0 -54
  342. package/framework/rules/morph-workflow.md +0 -244
  343. package/framework/schemas/tasks.schema.json +0 -79
  344. package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
  345. package/framework/skills/core/morph-implement/SKILL.md +0 -194
  346. package/framework/skills/core/morph-proposal/SKILL.md +0 -152
  347. package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
  348. package/framework/templates/docs/proposal.md +0 -192
  349. package/framework/templates/docs/spec.md +0 -57
  350. package/framework/templates/docs/ui-components.md +0 -52
  351. package/framework/templates/docs/ui-design-system.md +0 -76
  352. package/framework/templates/docs/ui-flows.md +0 -167
  353. package/framework/templates/examples/spec-examples.md +0 -91
  354. package/framework/templates/feature/decisions.md +0 -124
  355. package/framework/templates/feature/recap.md +0 -145
  356. package/framework/templates/tasks.template.json +0 -17
  357. package/src/commands/state/usecase.js +0 -143
  358. package/src/commands/tasks/dag.js +0 -181
  359. package/src/commands/utils/index.js +0 -3
  360. package/src/lib/agents/output-collector.js +0 -76
  361. package/src/lib/detectors/design-system-detector.js +0 -188
  362. package/src/lib/evaluator-report.js +0 -104
  363. package/src/lib/generators/design-system-generator.js +0 -508
  364. package/src/lib/specs/acervo-format.js +0 -70
  365. package/src/lib/specs/acervo-lifecycle.js +0 -264
  366. package/src/lib/specs/done-criteria-check.js +0 -200
  367. package/src/lib/specs/usecase-check.js +0 -301
  368. package/src/lib/specs/usecase-promoter.js +0 -301
  369. package/src/lib/specs/usecase-registry.js +0 -237
  370. package/src/lib/tasks/dispatch-planner.js +0 -319
  371. package/src/lib/tasks/dotnet-test-introspection.js +0 -632
  372. package/src/lib/tasks/mermaid-renderer.js +0 -132
  373. package/src/lib/tasks/task-classifier.js +0 -30
  374. package/src/lib/tasks/task-parser.js +0 -209
  375. package/src/lib/tasks/tasks-json-parser.js +0 -296
  376. package/src/lib/tasks/test-scope.js +0 -597
  377. package/src/utils/agent-id-mapper.js +0 -11
@@ -6,45 +6,34 @@ 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;
32
+ function isReady(sl, byId) {
33
+ if (sl.status === 'feito') return false;
34
+ return (sl.depends || []).every((d) => byId[d]?.status === 'feito');
39
35
  }
40
36
 
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));
45
- }
46
-
47
- /** `vscode://file/…` opens the file in the editor from the browser. */
48
37
  export function editorLink(absPath) {
49
38
  return `vscode://file/${encodeURI(String(absPath).replace(/\\/g, '/'))}`;
50
39
  }
@@ -71,22 +60,20 @@ function EditorButton({ path, label = 'Abrir no VS Code' }) {
71
60
  );
72
61
  }
73
62
 
74
- /** What a person should expect next, in one sentence. */
75
63
  export function nextAction(f) {
76
64
  if (f.status === 'completed') return `Concluída${f.archivedAt ? ` ${timeAgo(f.archivedAt)}` : ''}.`;
77
65
  if (f.status === 'abandoned') return 'Abandonada.';
78
66
  const paused = f.gates.find((g) => g.paused);
79
67
  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.';
68
+ if (f.inEvaluation) return 'Os relatórios do Gate 3 chegaram. A decisão sai dos vereditos, dos bloqueadores e dos cenários provados.';
69
+ if (f.phase === 'implement' && f.slicesList?.length) {
70
+ const open = f.slicesList.find((sl) => sl.status === 'parcial') || f.slicesList.find((sl) => sl.status === 'pendente');
71
+ if (open) return `${f.slices.done} de ${f.slices.total} fatias feitas. Próxima: ${open.id}, ${open.title}.`;
72
+ return 'Todas as fatias feitas. Falta o verify da feature e a revisão.';
85
73
  }
86
74
  const text = {
87
- proposal: 'Proposta em construção. O Gate 1 aprova o escopo.',
75
+ plan: 'Plano sendo escrito: cenários, decisões e fatias. O gate do plano aprova.',
88
76
  uiux: 'Desenho de UI/UX em andamento.',
89
- plan: 'Plano sendo escrito: spec, use cases e tasks. O Gate 2 aprova.',
90
77
  implement: 'Implementação em andamento.',
91
78
  review: 'Em revisão.',
92
79
  };
@@ -149,13 +136,41 @@ function NodeChips({ nodes }) {
149
136
  );
150
137
  }
151
138
 
139
+ const REVIEW_ROLE_LABEL = { verifier: 'Verificador', reviewer: 'Revisor', security: 'Segurança' };
140
+ const REVIEW_STATUS_LABEL = { missing: 'sem relatório', partial: 'parcial', unverdicted: 'sem veredito', ok: null };
141
+
142
+ function ReviewRows({ reviews }) {
143
+ if (!reviews?.length) return null;
144
+ return (
145
+ <ul className="mt-4 space-y-2 text-sm">
146
+ {reviews.map((r) => {
147
+ const state = REVIEW_STATUS_LABEL[r.status] ?? r.verdict;
148
+ const tone = r.verdict === 'SHIP' && r.status === 'ok' ? 'text-[var(--color-ok)]' : r.verdict === 'FIX' ? 'text-[var(--color-bad)]' : 'text-[var(--color-faint)]';
149
+ return (
150
+ <li key={r.role} className="grid grid-cols-[6.5rem_minmax(0,1fr)_auto] items-baseline gap-3">
151
+ <span className="text-[var(--color-dim)]">{REVIEW_ROLE_LABEL[r.role] || r.role}</span>
152
+ <span className={tone}>
153
+ {state}{r.blockers ? ` · ${r.blockers} bloq.` : ''}
154
+ {r.reviewedSha && <span className="ml-2 font-mono text-xs text-[var(--color-faint)]">{r.reviewedSha.slice(0, 7)}</span>}
155
+ </span>
156
+ {r.status !== 'missing' && (
157
+ <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>
158
+ )}
159
+ </li>
160
+ );
161
+ })}
162
+ </ul>
163
+ );
164
+ }
165
+
152
166
  function VerdictBlock({ f }) {
153
167
  return (
154
168
  <div>
155
169
  <p className="flex items-baseline gap-2">
156
170
  <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>
171
+ <span className="text-sm text-[var(--color-dim)]">{f.verdict ? `${f.blockers ?? 0} bloqueador(es)` : 'sem veredito do Gate 3'}</span>
158
172
  </p>
173
+ <ReviewRows reviews={f.reviews} />
159
174
  {f.blockerList?.length > 0 && (
160
175
  <ul className="mt-4 space-y-1.5 text-xs">
161
176
  {f.blockerList.map((b) => (
@@ -178,7 +193,6 @@ function CostBlock({ cost }) {
178
193
  ['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
194
  ['Tokens dos sub-agents', cost.agentUsage ? num(tokens(cost.agentUsage)) : 'desconhecido', cost.agentUsage ? `${cost.agentUsage.agents} sub-agents` : null],
180
195
  ['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
196
  ['Lido para o contexto', bytes(cost.bytesTotal), null],
183
197
  ];
184
198
  return (
@@ -211,9 +225,7 @@ function OverviewTab({ f, cost }) {
211
225
  <Panel title="Avaliação"><VerdictBlock f={f} /></Panel>
212
226
  <Panel title="Último verify">
213
227
  {f.verification && (
214
- <p className="mb-3 text-xs text-[var(--color-faint)]">
215
- escopo {f.verification.scope || '—'}, {timeAgo(f.verification.ts)}
216
- </p>
228
+ <p className="mb-3 text-xs text-[var(--color-faint)]">{timeAgo(f.verification.ts)}</p>
217
229
  )}
218
230
  <NodeChips nodes={f.verificationNodes} />
219
231
  </Panel>
@@ -223,81 +235,89 @@ function OverviewTab({ f, cost }) {
223
235
  );
224
236
  }
225
237
 
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));
238
+ const CHECK_STATUS_TONE = {
239
+ PROVADO: 'text-[var(--color-ok)]',
240
+ FALHA: 'text-[var(--color-bad)]',
241
+ 'NÃO PROVADO': 'text-[var(--color-warn)]',
242
+ };
243
+
244
+ function ChecksTab({ f }) {
245
+ const cov = f.checksCoverage;
246
+ if (!f.checksList?.length) {
247
+ 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>;
248
+ }
249
+ const proven = cov ? cov.total - cov.missing.length - cov.notProven.length : null;
250
+ return (
251
+ <Panel title={cov ? `${proven} de ${cov.total} provados pelo verificador` : 'Aguardando o relatório do verificador'}>
252
+ <ul className="divide-y divide-[var(--color-hair)]">
253
+ {f.checksList.map((c) => (
254
+ <li key={c.id} className="grid grid-cols-[3rem_minmax(0,1fr)_7rem] gap-3 py-3 text-sm">
255
+ <span className="font-mono text-[var(--color-dim)]">{c.id}</span>
256
+ <div className="min-w-0">
257
+ <p className="text-[var(--color-fg)]">{c.text}</p>
258
+ {c.evidence && <p className="mt-1 truncate text-xs text-[var(--color-faint)]" title={c.evidence}>{c.evidence}</p>}
259
+ </div>
260
+ <div className="text-right">
261
+ <p className={CHECK_STATUS_TONE[c.status] || 'text-[var(--color-faint)]'}>{c.status ?? (cov ? 'sem linha' : '—')}</p>
262
+ {c.proof && <p className="text-xs text-[var(--color-faint)]">prova: {c.proof}</p>}
263
+ </div>
264
+ </li>
265
+ ))}
266
+ </ul>
267
+ </Panel>
268
+ );
269
+ }
270
+
271
+ function reportPathFor(f, sliceId) {
272
+ const path = `3-implement/reports/${sliceId}.md`;
273
+ return f.documents.some((d) => d.path === path) ? path : null;
229
274
  }
230
275
 
231
- function TaskRow({ t, f, open, onToggle, duration: secs, ready }) {
232
- const st = TASK_STATUS[t.status] || TASK_STATUS.pending;
276
+ function SliceRow({ sl, f, open, onToggle, ready }) {
277
+ const st = SLICE_STATUS[sl.status] || SLICE_STATUS.pendente;
233
278
  const StIcon = st.icon;
234
- const reports = reportPathFor(f, t.id);
235
- const verify = t.verification;
279
+ const report = reportPathFor(f, sl.id);
236
280
  return (
237
281
  <li>
238
282
  <button
239
283
  type="button"
240
284
  onClick={onToggle}
241
285
  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]"
286
+ 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
287
  >
244
288
  <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>
289
+ <span className="font-mono text-xs text-[var(--color-dim)]">{sl.id}</span>
246
290
  <span className="flex min-w-0 items-center gap-2">
247
- <span className="truncate text-sm text-[var(--color-fg)]">{t.title}</span>
291
+ <span className="truncate text-sm text-[var(--color-fg)]">{sl.title}</span>
248
292
  {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
293
  </span>
255
- <span className="hidden text-right font-mono text-xs tabular-nums text-[var(--color-dim)] md:block">{verify?.status ?? '—'}</span>
294
+ <span className="hidden truncate text-xs text-[var(--color-dim)] md:block">{sl.persona ? `morph-${sl.persona}` : '—'}</span>
256
295
  <span className="hidden text-[var(--color-faint)] md:block">{open ? <CaretDown weight="light" size={14} /> : <CaretRight weight="light" size={14} />}</span>
257
296
  </button>
258
297
  {open && (
259
298
  <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
299
  <dl className="space-y-3 text-sm">
273
- {t.agent && (
300
+ {sl.persona && (
274
301
  <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>
302
+ <dd><EntityLink kind="persona" id={sl.persona}>morph-{sl.persona}</EntityLink></dd></div>
276
303
  )}
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>
304
+ {sl.covers?.length > 0 && (
305
+ <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
306
  )}
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>
307
+ {sl.depends?.length > 0 && (
308
+ <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
309
  )}
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 && (
310
+ </dl>
311
+ <dl className="space-y-3 text-sm">
312
+ {sl.files?.length > 0 && (
291
313
  <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>
314
+ <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
315
  )}
294
- {reports.length > 0 && (
316
+ {report && (
295
317
  <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
- ))}
318
+ <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]">
319
+ Abrir relatório
320
+ </Link>
301
321
  </div>
302
322
  )}
303
323
  </dl>
@@ -308,44 +328,37 @@ function TaskRow({ t, f, open, onToggle, duration: secs, ready }) {
308
328
  }
309
329
 
310
330
  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' },
331
+ { id: 'pendente', label: 'Pendentes' },
332
+ { id: 'parcial', label: 'Parciais' },
333
+ { id: 'feito', label: 'Feitas' },
315
334
  ];
316
335
 
317
- function TaskCard({ t, f, secs, ready }) {
318
- const reports = reportPathFor(f, t.id);
336
+ function SliceCard({ sl, f, ready }) {
337
+ const report = reportPathFor(f, sl.id);
319
338
  return (
320
339
  <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
340
  <p className="flex items-baseline gap-2">
322
- <span className="font-mono text-xs text-[var(--color-dim)]">{t.id}</span>
341
+ <span className="font-mono text-xs text-[var(--color-dim)]">{sl.id}</span>
323
342
  {ready && <span className="text-xs text-[var(--color-phase-implement)]">pronta</span>}
324
343
  </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>
344
+ <p className="mt-1 text-sm leading-snug text-[var(--color-fg)]">{sl.title}</p>
345
+ {sl.persona && (
346
+ <EntityLink kind="persona" id={sl.persona} className="mt-2 inline-block text-xs">morph-{sl.persona}</EntityLink>
328
347
  )}
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>
348
+ {sl.depends?.length > 0 && <p className="mt-1 text-xs text-[var(--color-faint)]">depende de {sl.depends.join(', ')}</p>}
349
+ {report && (
350
+ <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
351
  )}
339
352
  </li>
340
353
  );
341
354
  }
342
355
 
343
- function TasksBoard({ f, durations, byId }) {
356
+ function SlicesBoard({ f, byId }) {
344
357
  return (
345
358
  <div className="-mx-4 overflow-x-auto px-4 pb-2 md:mx-0 md:px-0">
346
359
  <div className="grid auto-cols-[16rem] grid-flow-col gap-3 md:auto-cols-[minmax(12rem,1fr)]">
347
360
  {BOARD_COLUMNS.map((c) => {
348
- const items = f.tasksList.filter(c.match);
361
+ const items = f.slicesList.filter((sl) => sl.status === c.id);
349
362
  return (
350
363
  <section key={c.id} className="bezel min-w-0" aria-label={`${c.label}: ${items.length}`}>
351
364
  <div className="bezel-core flex h-full min-h-[12rem] flex-col p-2.5">
@@ -354,7 +367,7 @@ function TasksBoard({ f, durations, byId }) {
354
367
  </h3>
355
368
  {items.length === 0
356
369
  ? <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>}
370
+ : <ul className="space-y-2">{items.map((sl) => <SliceCard key={sl.id} sl={sl} f={f} ready={isReady(sl, byId)} />)}</ul>}
358
371
  </div>
359
372
  </section>
360
373
  );
@@ -364,34 +377,27 @@ function TasksBoard({ f, durations, byId }) {
364
377
  );
365
378
  }
366
379
 
367
- function TasksTab({ f, cost, view = 'list', onView = () => {} }) {
380
+ function SlicesTab({ f, view = 'list', onView = () => {} }) {
368
381
  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>;
382
+ if (!f.slicesList.length) {
383
+ 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
384
  }
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;
385
+ const byId = Object.fromEntries(f.slicesList.map((sl) => [sl.id, sl]));
386
+ const ready = f.slicesList.filter((sl) => isReady(sl, byId)).length;
375
387
  return (
376
388
  <div className="space-y-4">
377
389
  <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 }]} />
390
+ <Segmented label="Visualização das fatias" value={view} onChange={onView} options={[{ id: 'list', label: 'Lista', icon: Rows }, { id: 'board', label: 'Quadro', icon: Kanban }]} />
379
391
  <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` : ''}
392
+ {f.slices.done} de {f.slices.total} feitas{f.slices.partial ? `, ${f.slices.partial} parciais` : ''}{ready ? `, ${ready} prontas para começar` : ''}
381
393
  </p>
382
394
  </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} /> : (
395
+ {view === 'board' ? <SlicesBoard f={f} byId={byId} /> : (
390
396
  <div className="bezel">
391
397
  <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)} />
398
+ {f.slicesList.map((sl) => (
399
+ <SliceRow key={sl.id} sl={sl} f={f} ready={isReady(sl, byId)}
400
+ open={open === sl.id} onToggle={() => setOpen(open === sl.id ? null : sl.id)} />
395
401
  ))}
396
402
  </ul>
397
403
  </div>
@@ -436,7 +442,7 @@ function DocViewer({ project, feature, path, dir }) {
436
442
 
437
443
  function DocsTab({ f, doc, onDoc, project }) {
438
444
  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;
445
+ const selected = doc || f.documents.find((d) => d.path.endsWith('plan.md'))?.path || f.documents.find((d) => d.path.endsWith('.md'))?.path || null;
440
446
  return (
441
447
  <div className="grid gap-5 lg:grid-cols-[16rem_minmax(0,1fr)]">
442
448
  <nav aria-label="Documentos da feature" className="lg:sticky lg:top-4 lg:self-start">
@@ -481,7 +487,7 @@ function DocsTab({ f, doc, onDoc, project }) {
481
487
  );
482
488
  }
483
489
 
484
- export function FeatureDetailView({ f, tab, onTab, doc, onDoc, cost, latest = null, tasksView = 'list', onTasksView = () => {}, now = Date.now(), project, projectName }) {
490
+ export function FeatureDetailView({ f, tab, onTab, doc, onDoc, cost, latest = null, slicesView = 'list', onSlicesView = () => {}, now = Date.now(), project, projectName }) {
485
491
  const verdict = verdictLabel(f);
486
492
  const working = latest && isWorking({ ...f, lastActivity: latest }, now);
487
493
  return (
@@ -500,7 +506,7 @@ export function FeatureDetailView({ f, tab, onTab, doc, onDoc, cost, latest = nu
500
506
  </p>
501
507
  </div>
502
508
  <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>
509
+ <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
510
  <div><p className="text-2xl font-semibold">{verdict ?? '—'}</p><p className="text-xs text-[var(--color-faint)]">veredito</p></div>
505
511
  </div>
506
512
  </div>
@@ -546,14 +552,15 @@ export function FeatureDetailView({ f, tab, onTab, doc, onDoc, cost, latest = nu
546
552
  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
553
  ].join(' ')}
548
554
  >
549
- {t.label}{t.id === 'tasks' && f.tasks.total ? ` ${f.tasks.total}` : ''}{t.id === 'docs' ? ` ${f.documents.length}` : ''}
555
+ {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
556
  </button>
551
557
  ))}
552
558
  </div>
553
559
 
554
560
  <div className="rise-3">
555
561
  {tab === 'overview' && <OverviewTab f={f} cost={cost} />}
556
- {tab === 'tasks' && <TasksTab f={f} cost={cost} view={tasksView} onView={onTasksView} />}
562
+ {tab === 'slices' && <SlicesTab f={f} view={slicesView} onView={onSlicesView} />}
563
+ {tab === 'checks' && <ChecksTab f={f} />}
557
564
  {tab === 'docs' && <DocsTab f={f} doc={doc} onDoc={onDoc} project={project} />}
558
565
  {tab === 'activity' && (
559
566
  <div className="bezel"><div className="bezel-core px-5 py-5"><ActivityFeed project={project} feature={f.name} /></div></div>
@@ -569,10 +576,9 @@ export default function FeatureDetail() {
569
576
  const tab = TABS.some((t) => t.id === params.get('tab')) ? params.get('tab') : 'overview';
570
577
  const doc = params.get('doc');
571
578
  const { data, error, loading } = useMorphQuery(`${apiBase(project)}/features/${encodeURIComponent(name)}`, { project });
572
- // The cost rides on the activity endpoint (it owns the event stream); one
573
- // line of feed is enough to get it, and it refreshes with the log.
579
+ // The cost rides on the activity endpoint (it owns the event stream).
574
580
  const costQuery = useMorphQuery(`${apiBase(project)}/activity?feature=${encodeURIComponent(name)}&limit=1`, { scope: 'activity', project });
575
- const [tasksView, setTasksView] = usePref('tasks.view', 'list');
581
+ const [slicesView, setSlicesView] = usePref('slices.view', 'list');
576
582
  const now = useNow();
577
583
 
578
584
  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 +598,7 @@ export default function FeatureDetail() {
592
598
  <FeatureDetailView
593
599
  f={data} tab={tab} onTab={onTab} doc={doc} onDoc={onDoc}
594
600
  cost={costQuery.data?.cost ?? null} latest={costQuery.data?.items?.[0] ?? null}
595
- tasksView={tasksView} onTasksView={setTasksView} now={now} project={project}
601
+ slicesView={slicesView} onSlicesView={setSlicesView} now={now} project={project}
596
602
  />
597
603
  );
598
604
  }
@@ -14,7 +14,6 @@ import { isFinished } from '../../scanner/feature-status.js';
14
14
 
15
15
  const COMPLETED_PAGE = 30;
16
16
  const BOARD_DONE_LIMIT = 8;
17
- /** An event this recent means an agent is working on the feature right now. */
18
17
  export const WORKING_MS = 3 * 60 * 1000;
19
18
 
20
19
  const LOCATION_ICON = { worktree: GitBranch, archive: Archive, root: HouseSimple };
@@ -41,15 +40,15 @@ function VerifyMark({ verification }) {
41
40
  : <XCircle weight="light" className="text-[var(--color-bad)]" size={16} aria-label="último verify falhou" />;
42
41
  }
43
42
 
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);
43
+ function SliceMeter({ slices, wide = false }) {
44
+ if (!slices?.total) return <span className="text-xs text-[var(--color-faint)]">sem fatias</span>;
45
+ const pct = Math.round((slices.done / slices.total) * 100);
47
46
  return (
48
- <div className="flex items-center gap-2" title={`${tasks.done} de ${tasks.total} tasks concluídas`}>
47
+ <div className="flex items-center gap-2" title={`${slices.done} de ${slices.total} fatias feitas`}>
49
48
  <div className={`h-1 overflow-hidden rounded-full bg-white/[0.07] ${wide ? 'flex-1' : 'w-12'}`}>
50
49
  <div className="h-full rounded-full bg-[var(--color-fg)]/70" style={{ width: `${pct}%` }} />
51
50
  </div>
52
- <span className="font-mono text-xs tabular-nums text-[var(--color-dim)]">{tasks.done}/{tasks.total}</span>
51
+ <span className="font-mono text-xs tabular-nums text-[var(--color-dim)]">{slices.done}/{slices.total}</span>
53
52
  </div>
54
53
  );
55
54
  }
@@ -73,7 +72,6 @@ export function stageText(f) {
73
72
  return phaseMeta(f.phase).label;
74
73
  }
75
74
 
76
- /** The second line of a feature: what just happened while it is live, else where it stands. */
77
75
  function subline(f, now) {
78
76
  if (!isFinished(f) && f.lastActivity?.title) {
79
77
  return <>{isWorking(f, now) && <WorkingDot />}<span className="truncate">{f.lastActivity.title}, {timeAgo(f.lastActivity.titleTs, now)}</span></>;
@@ -106,9 +104,9 @@ export function FeatureRow({ f, now, project, projectName }) {
106
104
  <PhaseRail feature={f} />
107
105
  {/* One line on a phone; on desktop the wrapper dissolves into the row's grid. */}
108
106
  <div className="flex items-center gap-5 md:contents">
109
- <TaskMeter tasks={f.tasks} />
107
+ <SliceMeter slices={f.slices} />
110
108
  <VerifyMark verification={f.verification} />
111
- <span className="font-mono text-sm tabular-nums text-[var(--color-dim)]" title={verdict ? 'veredito do avaliador' : 'sem veredito'}>
109
+ <span className="font-mono text-sm tabular-nums text-[var(--color-dim)]" title={verdict ? 'veredito do Gate 3' : 'sem veredito'}>
112
110
  {verdict ?? '—'}
113
111
  </span>
114
112
  <span className="flex items-center gap-1.5 text-xs text-[var(--color-faint)]" title={LOCATION_LABEL[f.location]}>
@@ -165,7 +163,7 @@ function BoardCard({ f, now, project }) {
165
163
  {f.lastActivity?.title && !isFinished(f) && (
166
164
  <p className="mt-1.5 line-clamp-2 text-xs text-[var(--color-dim)]">{f.lastActivity.title}</p>
167
165
  )}
168
- <div className="mt-3"><TaskMeter tasks={f.tasks} wide /></div>
166
+ <div className="mt-3"><SliceMeter slices={f.slices} wide /></div>
169
167
  <div className="mt-2.5 flex items-center justify-between text-xs text-[var(--color-faint)]">
170
168
  <span className="flex items-center gap-2">
171
169
  <VerifyMark verification={f.verification} />
@@ -243,10 +241,6 @@ const chipClass = (on) => [
243
241
  on ? 'bg-white/[0.09] text-[var(--color-fg)] shadow-[inset_0_0_0_1px_var(--color-hair-strong)]' : 'text-[var(--color-faint)] shadow-[inset_0_0_0_1px_var(--color-hair)] hover:text-[var(--color-dim)]',
244
242
  ].join(' ');
245
243
 
246
- /**
247
- * The feature home. All state comes in as props so the view renders the same
248
- * with the live API and with the smoke test's fixture.
249
- */
250
244
  export function FeaturesView({
251
245
  data, query, onQuery, completedShown, onShowMore,
252
246
  view = 'list', onView = () => {}, types = [], onTypes = () => {}, sort = 'updated', onSort = () => {},
@@ -329,7 +323,7 @@ export function FeaturesView({
329
323
  )}
330
324
  {info.initialized && !info.legacyStateOnly && features.length === 0 && (
331
325
  <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.
326
+ No Claude Code, comece uma com <code className="font-mono text-[var(--color-fg)]">/morph-plan</code>, que entrevista e escreve o plano. Ela aparece aqui assim que o <code className="font-mono">feature.json</code> nascer.
333
327
  </Empty>
334
328
  )}
335
329
  {features.length > 0 && visible.length === 0 && (
@@ -374,7 +368,6 @@ export default function Features() {
374
368
  const now = useNow();
375
369
  const searchRef = useRef(null);
376
370
 
377
- // `/` jumps to the search, like most dev tools.
378
371
  useEffect(() => {
379
372
  const onKey = (e) => {
380
373
  if (e.key !== '/' || e.ctrlKey || e.metaKey || e.altKey) return;
@@ -18,7 +18,6 @@ function personasIn(phaseId) {
18
18
  return (morphData.agents?.list || []).filter((a) => a.activePhases?.includes(phaseId));
19
19
  }
20
20
 
21
- /** The whole flow on one screen: phase, the skill that runs it, who works in it, the gate that closes it. */
22
21
  export function Flow() {
23
22
  return (
24
23
  <section aria-label="O fluxo do morph-spec" className="bezel">
@@ -27,9 +26,9 @@ export function Flow() {
27
26
  <p className="mt-1 max-w-3xl text-sm leading-relaxed text-[var(--color-dim)]">
28
27
  Você descreve o que quer; o Claude conduz cada fase com uma skill, despacha personas especialistas como sub-agents e para nos gates.
29
28
  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.
29
+ No fim, verificador e revisor independentes dão o veredito (SHIP ou FIX).
31
30
  </p>
32
- <ol className="mt-6 grid gap-3 md:grid-cols-5">
31
+ <ol className="mt-6 grid gap-3 md:grid-cols-4">
33
32
  {PHASES.map((p) => {
34
33
  const people = personasIn(p.id);
35
34
  return (