@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.
- package/CLAUDE.md +51 -43
- package/README.md +39 -38
- package/bin/morph-spec.js +41 -113
- package/bin/validate.js +1 -147
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +41 -101
- package/framework/MORPH.md +95 -252
- package/framework/agents.json +86 -21
- package/framework/commands/morph-apply.md +5 -28
- package/framework/commands/morph-archive.md +1 -1
- package/framework/commands/morph-hotfix.md +8 -8
- package/framework/commands/morph-infra.md +1 -1
- package/framework/commands/morph-preflight.md +2 -3
- package/framework/commands/morph-status.md +4 -4
- package/framework/docs/CLI.md +574 -286
- package/framework/evals/review-checklist.md +82 -41
- package/framework/hooks/README.md +10 -11
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
- package/framework/hooks/claude-code/core/chain.js +4 -65
- package/framework/hooks/claude-code/core/comment-guard.js +3 -17
- package/framework/hooks/claude-code/core/denylist.json +33 -3
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +15 -63
- package/framework/hooks/claude-code/core/gate-guard.js +9 -78
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
- package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
- package/framework/hooks/claude-code/core/heavy-release.js +2 -13
- package/framework/hooks/claude-code/core/loop-detect.js +2 -31
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +36 -2
- package/framework/hooks/claude-code/core/protect-spec-files.js +25 -297
- package/framework/hooks/claude-code/core/state-sync.js +15 -85
- package/framework/hooks/claude-code/core/telemetry-log.js +15 -96
- package/framework/hooks/claude-code/core/validate-completion.js +37 -147
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +23 -0
- package/framework/hooks/claude-code/core/worktree-context.js +15 -114
- package/framework/hooks/claude-code/core/worktree-create.js +6 -70
- package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
- package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
- package/framework/hooks/claude-code/statusline.py +55 -41
- package/framework/hooks/package.json +3 -0
- package/framework/hooks/shared/activity-logger.js +9 -89
- package/framework/hooks/shared/agent-reap.js +2 -17
- package/framework/hooks/shared/build-check.js +11 -76
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +168 -44
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +11 -43
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +59 -0
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +4 -0
- package/framework/hooks/shared/gate-question.js +12 -0
- package/framework/hooks/shared/git-exclude.js +3 -20
- package/framework/hooks/shared/git-paths.js +8 -104
- package/framework/hooks/shared/graph-freshness.js +22 -166
- package/framework/hooks/shared/graph-refresh.js +21 -149
- package/framework/hooks/shared/heavy-command.js +4 -12
- package/framework/hooks/shared/hook-response.js +8 -83
- package/framework/hooks/shared/morph-cli.js +8 -52
- package/framework/hooks/shared/payload-utils.js +10 -132
- package/framework/hooks/shared/phase-utils.js +14 -72
- package/framework/hooks/shared/redact-secrets.js +4 -15
- package/framework/hooks/shared/reparse-scan.js +6 -66
- package/framework/hooks/shared/slice-progress.js +99 -0
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/spawn-command.js +23 -0
- package/framework/hooks/shared/state-reader.js +51 -247
- package/framework/hooks/shared/state-writer.js +9 -54
- package/framework/hooks/shared/stdin-reader.js +1 -11
- package/framework/hooks/shared/telemetry-logger.js +7 -67
- package/framework/hooks/shared/transcript-usage.js +7 -29
- package/framework/hooks/shared/worktree-root.js +7 -29
- package/framework/mcp/registry.json +3 -4
- package/framework/phases.json +23 -39
- package/framework/rules/csharp-standards.md +1 -1
- package/framework/rules/frontend-standards.md +1 -0
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/neon-standards.md +1 -1
- package/framework/schemas/config.schema.json +14 -9
- package/framework/schemas/feature.schema.json +15 -50
- package/framework/skills/README.md +3 -4
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +149 -222
- package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
- package/framework/skills/core/morph-machine/SKILL.md +2 -2
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +137 -299
- package/framework/skills/core/morph-review/SKILL.md +111 -136
- package/framework/skills/core/morph-standards/SKILL.md +27 -39
- package/framework/skills/core/morph-uiux/SKILL.md +94 -152
- package/framework/standards/STANDARDS.json +109 -1219
- package/framework/standards/STANDARDS.meta.json +1319 -0
- package/framework/standards/ai-agents/agent-archetypes.md +2 -2
- package/framework/standards/ai-agents/agent-session.md +1 -1
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
- package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
- package/framework/standards/ai-agents/evals-with-cache.md +1 -2
- package/framework/standards/ai-agents/guardrails.md +1 -1
- package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
- package/framework/standards/ai-agents/observability-patterns.md +1 -1
- package/framework/standards/ai-agents/production.md +1 -1
- package/framework/standards/ai-agents/prompt-sources.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
- package/framework/standards/ai-agents/setup.md +3 -3
- package/framework/standards/ai-agents/sweet-spot.md +2 -2
- package/framework/standards/ai-agents/tool-adapters.md +0 -2
- package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
- package/framework/standards/ai-agents/workflows.md +1 -1
- package/framework/standards/architecture/code-comments.md +7 -7
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
- package/framework/standards/backend/api/validation.md +1 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
- package/framework/standards/data/nosql/cache/redis.md +1 -1
- package/framework/standards/data/nosql/cosmos-db.md +3 -3
- package/framework/standards/frontend/admin-app/forms.md +1 -1
- package/framework/standards/frontend/design-system/art-direction.md +88 -0
- package/framework/standards/frontend/design-system/premium-finish.md +77 -2
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/standards/integration/mcp/mcp-tools.md +7 -7
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/README.md +2 -3
- package/framework/templates/REGISTRY.json +15 -179
- package/framework/templates/docs/plan.md +72 -0
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/framework/templates/dotnet/ai-kit/README.md +1 -9
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
- package/package.json +1 -1
- package/src/commands/context/context-refresh.js +0 -16
- package/src/commands/dashboard/dashboard.js +2 -8
- package/src/commands/e2e/e2e.js +18 -105
- package/src/commands/graph/affected.js +0 -14
- package/src/commands/graph/build.js +11 -50
- package/src/commands/graph/explain.js +0 -16
- package/src/commands/graph/status.js +1 -12
- package/src/commands/mcp/mcp-setup.js +4 -62
- package/src/commands/observability/cost.js +17 -111
- package/src/commands/observability/telemetry.js +1 -14
- package/src/commands/project/branch.js +4 -45
- package/src/commands/project/claim.js +4 -48
- package/src/commands/project/doctor-procs.js +1 -1
- package/src/commands/project/doctor.js +53 -307
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +6 -35
- package/src/commands/project/heavy.js +3 -3
- package/src/commands/project/init.js +8 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/setup-infra-cmd.js +2 -1
- package/src/commands/project/stack-scan.js +58 -0
- package/src/commands/project/standards.js +65 -72
- package/src/commands/project/status.js +28 -121
- package/src/commands/project/update.js +31 -223
- package/src/commands/project/worktree.js +109 -883
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +65 -120
- package/src/commands/state/approve.js +87 -229
- package/src/commands/state/archive.js +23 -139
- package/src/commands/state/auto-approval.js +65 -0
- package/src/commands/state/create.js +49 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +105 -494
- package/src/commands/state/gate-check.js +74 -183
- package/src/commands/state/gate-decision.js +8 -42
- package/src/commands/state/handoff.js +4 -31
- package/src/commands/state/list.js +21 -91
- package/src/commands/state/report-check.js +134 -338
- package/src/commands/state/retype.js +12 -28
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +79 -19
- package/src/commands/validation/plan-check.js +81 -0
- package/src/commands/validation/validate-feature.js +52 -216
- package/src/commands/validation/verify.js +45 -160
- package/src/core/paths/output-schema.js +9 -155
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +2 -30
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +32 -395
- package/src/core/templates/template-registry.js +2 -97
- package/src/core/templates/template-renderer.js +13 -93
- package/src/core/templates/template-seed.js +21 -0
- package/src/core/work-types.js +10 -45
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +2 -8
- package/src/dashboard/app/components/CommandPalette.jsx +4 -11
- package/src/dashboard/app/components/DetailPanel.jsx +1 -5
- package/src/dashboard/app/components/EntityLink.jsx +0 -12
- package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
- package/src/dashboard/app/components/Markdown.jsx +0 -9
- package/src/dashboard/app/components/PhaseRail.jsx +0 -11
- package/src/dashboard/app/components/SearchFilter.jsx +0 -4
- package/src/dashboard/app/components/Sidebar.jsx +0 -5
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
- package/src/dashboard/app/hooks/usePref.js +1 -5
- package/src/dashboard/app/lib/format.js +0 -2
- package/src/dashboard/app/lib/phases.js +3 -9
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/pages/Activity.jsx +1 -1
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +139 -133
- package/src/dashboard/app/pages/Features.jsx +9 -16
- package/src/dashboard/app/pages/Guide.jsx +2 -3
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +3 -7
- package/src/dashboard/app/pages/Portfolio.jsx +0 -1
- package/src/dashboard/app/pages/RulesMap.jsx +1 -16
- package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
- package/src/dashboard/app/pages/Standards.jsx +2 -17
- package/src/dashboard/scanner/activity-scanner.js +14 -83
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +1 -7
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +92 -113
- package/src/dashboard/scanner/hooks-scanner.js +5 -28
- package/src/dashboard/scanner/index.js +0 -4
- package/src/dashboard/scanner/portfolio.js +5 -29
- package/src/dashboard/scanner/project-api.js +8 -54
- package/src/dashboard/scanner/rules-scanner.js +1 -2
- package/src/dashboard/scanner/skills-scanner.js +2 -12
- package/src/dashboard/scanner/standards-scanner.js +4 -7
- package/src/dashboard/vite-plugin-morph-data.js +5 -19
- package/src/lib/ai/ai-checks.js +7 -52
- package/src/lib/ai/ai-project-scan.js +14 -108
- package/src/lib/ai/code-audit.js +7 -63
- package/src/lib/ai/pin-drift.js +8 -51
- package/src/lib/ai/registry-audit.js +11 -90
- package/src/lib/ci/ci-verify.js +14 -13
- package/src/lib/ci/github-ci.js +9 -37
- package/src/lib/ci/workflow-scaffold.js +2 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +8 -59
- package/src/lib/e2e/compose-env.js +5 -66
- package/src/lib/e2e/stack-teardown.js +6 -40
- package/src/lib/generators/architecture-scanner.js +18 -156
- package/src/lib/generators/handoff-generator.js +37 -112
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +11 -122
- package/src/lib/git/branch-classifier.js +11 -84
- package/src/lib/git/default-branch.js +14 -169
- package/src/lib/git/evidence-staleness.js +2 -1
- package/src/lib/git/gh.js +2 -17
- package/src/lib/git/pending-prs.js +6 -37
- package/src/lib/git/pr-files.js +3 -28
- package/src/lib/git/pr-index.js +3 -34
- package/src/lib/git/tree-fingerprint.js +17 -17
- package/src/lib/git/tree-state.js +6 -33
- package/src/lib/graph/god-nodes.js +2 -22
- package/src/lib/graph/graph-health.js +3 -24
- package/src/lib/graph/graph-loader.js +5 -56
- package/src/lib/graph/graphify-runtime.js +7 -37
- package/src/lib/graph/graphifyignore.js +8 -74
- package/src/lib/graph/sanitize.js +16 -136
- package/src/lib/heavy/cleanup.js +2 -6
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +8 -33
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/commands.js +3 -2
- package/src/lib/mutation/dotnet-introspection.js +281 -0
- package/src/lib/mutation/plan.js +14 -6
- package/src/lib/mutation/runner.js +1 -1
- package/src/lib/mutation/subset.js +47 -13
- package/src/lib/plan/artifact-vocabulary.js +40 -0
- package/src/lib/plan/checks.js +208 -0
- package/src/lib/plan/plan-parser.js +204 -0
- package/src/lib/plan/plan-template.js +18 -0
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +204 -0
- package/src/lib/risk-detector.js +131 -274
- package/src/lib/specs/adr-index.js +38 -85
- package/src/lib/specs/design-seed.js +18 -0
- package/src/lib/specs/frontmatter.js +31 -0
- package/src/lib/specs/gfm-table.js +11 -0
- package/src/lib/specs/revocations.js +31 -102
- package/src/lib/specs/screen-checklist.js +56 -132
- package/src/lib/stack/dotnet-build-ignores.js +43 -0
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +260 -0
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +37 -0
- package/src/lib/standards/sections.js +47 -0
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +23 -179
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -313
- package/src/lib/tasks/test-runner.js +107 -447
- package/src/lib/telemetry/metrics.js +13 -103
- package/src/lib/telemetry/telemetry-reader.js +13 -238
- package/src/lib/telemetry/verify-event.js +4 -34
- package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
- package/src/lib/validators/code-budget/index.js +0 -7
- package/src/lib/validators/contracts/contract-compliance-validator.js +4 -26
- package/src/lib/validators/contracts/index.js +0 -6
- package/src/lib/validators/nextjs/index.js +0 -5
- package/src/lib/validators/nextjs/next-component-validator.js +4 -81
- package/src/lib/validators/packages/index.js +0 -6
- package/src/lib/validators/packages/package-validator.js +6 -56
- package/src/lib/validators/reuse/index.js +0 -8
- package/src/lib/validators/reuse/reuse-validator.js +24 -137
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +54 -156
- package/src/lib/validators/validation-runner.js +20 -125
- package/src/lib/validators/verify-runner.js +152 -575
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -137
- package/src/lib/worktree/env-allocator.js +10 -98
- package/src/lib/worktree/feature-truth.js +12 -115
- package/src/lib/worktree/finish-backup.js +32 -242
- package/src/lib/worktree/fleet-scanner.js +31 -126
- package/src/lib/worktree/ignored-triage.js +15 -91
- package/src/lib/worktree/vscode-settings.js +2 -3
- package/src/lib/worktree/worktree-audit.js +21 -213
- package/src/lib/worktree/worktree-include.js +3 -5
- package/src/scripts/global-install.js +3 -9
- package/src/scripts/install-plugin.js +1 -13
- package/src/scripts/setup-infra.js +17 -138
- package/src/utils/agents-installer.js +92 -272
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -87
- package/src/utils/file-copier.js +32 -90
- package/src/utils/hooks-installer.js +26 -296
- package/src/utils/skills-installer.js +4 -102
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -57
- package/framework/evals/trace-schema.md +0 -67
- package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
- package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
- package/framework/hooks/git/pre-commit/specs.sh +0 -50
- package/framework/hooks/shared/tasks-validator.js +0 -111
- package/framework/rules/morph-task-tracking.md +0 -54
- package/framework/rules/morph-workflow.md +0 -244
- package/framework/schemas/tasks.schema.json +0 -79
- package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
- package/framework/skills/core/morph-implement/SKILL.md +0 -194
- package/framework/skills/core/morph-proposal/SKILL.md +0 -152
- package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
- package/framework/templates/docs/proposal.md +0 -192
- package/framework/templates/docs/spec.md +0 -57
- package/framework/templates/docs/ui-components.md +0 -52
- package/framework/templates/docs/ui-design-system.md +0 -76
- package/framework/templates/docs/ui-flows.md +0 -167
- package/framework/templates/examples/spec-examples.md +0 -91
- package/framework/templates/feature/decisions.md +0 -124
- package/framework/templates/feature/recap.md +0 -145
- package/framework/templates/tasks.template.json +0 -17
- package/src/commands/state/usecase.js +0 -143
- package/src/commands/tasks/dag.js +0 -181
- package/src/commands/utils/index.js +0 -3
- package/src/lib/agents/output-collector.js +0 -76
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/evaluator-report.js +0 -104
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/acervo-format.js +0 -70
- package/src/lib/specs/acervo-lifecycle.js +0 -264
- package/src/lib/specs/done-criteria-check.js +0 -200
- package/src/lib/specs/usecase-check.js +0 -301
- package/src/lib/specs/usecase-promoter.js +0 -301
- package/src/lib/specs/usecase-registry.js +0 -237
- package/src/lib/tasks/dispatch-planner.js +0 -319
- package/src/lib/tasks/dotnet-test-introspection.js +0 -632
- package/src/lib/tasks/mermaid-renderer.js +0 -132
- package/src/lib/tasks/task-classifier.js +0 -30
- package/src/lib/tasks/task-parser.js +0 -209
- package/src/lib/tasks/tasks-json-parser.js +0 -296
- package/src/lib/tasks/test-scope.js +0 -597
- 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
|
|
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,
|
|
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: '
|
|
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
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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 '
|
|
81
|
-
if (f.phase === 'implement' && f.
|
|
82
|
-
const open = f.
|
|
83
|
-
if (open) return `${f.
|
|
84
|
-
return 'Todas as
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
|
232
|
-
const st =
|
|
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
|
|
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)
|
|
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)]">{
|
|
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)]">{
|
|
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
|
|
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
|
-
{
|
|
300
|
+
{sl.persona && (
|
|
274
301
|
<div><dt className="text-xs text-[var(--color-faint)]">Persona</dt>
|
|
275
|
-
<dd><EntityLink kind="persona" id={
|
|
302
|
+
<dd><EntityLink kind="persona" id={sl.persona}>morph-{sl.persona}</EntityLink></dd></div>
|
|
276
303
|
)}
|
|
277
|
-
{
|
|
278
|
-
<div><dt className="text-xs text-[var(--color-faint)]">
|
|
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
|
-
{
|
|
286
|
-
<div><dt className="text-xs text-[var(--color-faint)]">Depende de</dt><dd className="font-mono text-xs text-[var(--color-dim)]">{
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
{
|
|
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)]">{
|
|
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
|
-
{
|
|
316
|
+
{report && (
|
|
295
317
|
<div className="flex flex-wrap gap-2 pt-1">
|
|
296
|
-
{
|
|
297
|
-
|
|
298
|
-
|
|
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: '
|
|
312
|
-
{ id: '
|
|
313
|
-
{ id: '
|
|
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
|
|
318
|
-
const
|
|
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)]">{
|
|
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)]">{
|
|
326
|
-
{
|
|
327
|
-
<EntityLink kind="persona" id={
|
|
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
|
-
{
|
|
330
|
-
|
|
331
|
-
<
|
|
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
|
|
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.
|
|
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((
|
|
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
|
|
380
|
+
function SlicesTab({ f, view = 'list', onView = () => {} }) {
|
|
368
381
|
const [open, setOpen] = useState(null);
|
|
369
|
-
if (!f.
|
|
370
|
-
return <Panel><p className="text-sm text-[var(--color-dim)]">
|
|
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
|
|
373
|
-
const
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
393
|
-
<
|
|
394
|
-
open={open ===
|
|
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('
|
|
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,
|
|
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.
|
|
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 === '
|
|
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 === '
|
|
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)
|
|
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 [
|
|
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
|
-
|
|
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
|
|
45
|
-
if (!
|
|
46
|
-
const pct = Math.round((
|
|
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={`${
|
|
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)]">{
|
|
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
|
-
<
|
|
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
|
|
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"><
|
|
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-
|
|
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,
|
|
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-
|
|
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 (
|