@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
|
@@ -1,361 +1,179 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* of the missing-report protocol (B4).
|
|
4
|
-
*
|
|
5
|
-
* MORPH.md already made the sub-agent's canonical report file the authoritative
|
|
6
|
-
* return value ("o ARQUIVO é o retorno, a última mensagem é cortesia"), but it
|
|
7
|
-
* never said what to do when the file is not there. The naive reading — "no
|
|
8
|
-
* report, so the agent failed" — is wrong and expensive: the agent may have
|
|
9
|
-
* done every bit of the work and lost the means to record it (observed: a
|
|
10
|
-
* worktree removed out from under a running sub-agent).
|
|
11
|
-
*
|
|
12
|
-
* So this command answers the question the orchestrator actually has, and
|
|
13
|
-
* answers it from evidence rather than from the agent's own word: **did the
|
|
14
|
-
* WORK land, independently of whether the REPORT did?**
|
|
15
|
-
*
|
|
16
|
-
* verdict action
|
|
17
|
-
* ───────────────────────────── ──────────────────
|
|
18
|
-
* ok proceed
|
|
19
|
-
* report-incomplete complete-degraded
|
|
20
|
-
* work-landed-report-missing accept-degraded
|
|
21
|
-
* no-work-no-report redispatch
|
|
22
|
-
* report-missing-evidence-unknown redispatch (conservative default)
|
|
23
|
-
*
|
|
24
|
-
* Exit 0 only for `ok` — anything needing an action exits non-zero so a caller
|
|
25
|
-
* can branch on it. `--json` always prints the machine-readable verdict.
|
|
26
|
-
*
|
|
27
|
-
* The output also carries `tree` (see tree-state.js): the working tree the
|
|
28
|
-
* orchestrator would otherwise ask git for, call by call, right after the same
|
|
29
|
-
* dispatch. It is information only and never changes the verdict.
|
|
30
|
-
*/
|
|
1
|
+
// Judged from observed evidence, never the agent's word: did the WORK land, independently of
|
|
2
|
+
// the REPORT? The `tree` block is information only and never changes the verdict.
|
|
31
3
|
|
|
32
4
|
import chalk from 'chalk';
|
|
33
5
|
import { existsSync, readFileSync, statSync } from 'fs';
|
|
34
6
|
import { join } from 'path';
|
|
35
|
-
import {
|
|
7
|
+
import { isFailedToolCall } from '../../../framework/hooks/shared/telemetry-logger.js';
|
|
8
|
+
import { sliceReportPath, inspectReport, REPORT_MARKERS } from '../../../framework/hooks/shared/slice-progress.js';
|
|
9
|
+
import { loadTelemetryAllTrees, dedupeEvents } from '../../lib/telemetry/telemetry-reader.js';
|
|
36
10
|
import { treeState, snapshotTree } from '../../lib/git/tree-state.js';
|
|
37
11
|
import { atomicWriteJson } from '../../core/state/state-manager.js';
|
|
38
12
|
import { minimatch } from 'minimatch';
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
13
|
+
import { reviewReports, reviewFileName } from '../../lib/review-reports.js';
|
|
14
|
+
import { verifierModelFor } from '../../lib/risk-detector.js';
|
|
41
15
|
import { changedPathsAgainst, detectDefaultBranch } from '../../lib/git/default-branch.js';
|
|
42
16
|
import { readMorphConfig } from '../../lib/tasks/test-runner.js';
|
|
43
|
-
import {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// (2) files touched — a list line carrying a path or a backticked identifier
|
|
56
|
-
{ id: 'files', re: /^\s*[-*]\s+.*[`/\\].*$/m },
|
|
57
|
-
// (3) each doneCriteria, met or not, with its evidence
|
|
58
|
-
{ id: 'criteria', re: /crit[ée]rio|doneCriteria|p[óo]s-condi[çc][ãa]o|atendid/i },
|
|
59
|
-
];
|
|
60
|
-
|
|
61
|
-
/** (1) "what was done" — prose beyond the structural markers. */
|
|
62
|
-
const MIN_PROSE_CHARS = 80;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* The partial-report marker of the dispatch contract (clause 5, ADR-004): a
|
|
66
|
-
* sub-agent nearing its `maxTurns` ceiling writes `PARCIAL` on the FIRST line.
|
|
67
|
-
* Tolerates heading/bold/quote typography (`# PARCIAL`, `**PARCIAL**`).
|
|
68
|
-
*/
|
|
69
|
-
const PARTIAL_MARKER = /^[\s#>*_]*PARCIAL\b/;
|
|
70
|
-
|
|
71
|
-
function reportPath(cwd, feature, taskId) {
|
|
72
|
-
return join(cwd, '.morph', 'features', feature, '3-implement', 'reports', `${taskId}.md`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* `.morph/logs/task-times.json` — `{ [feature]: { [taskId]: {status,start,end} } }`,
|
|
77
|
-
* escrito pelo hook `trace-autogen` nas transições de status da task. É a
|
|
78
|
-
* janela temporal de cada task; sem ela não há como recortar quais writes do
|
|
79
|
-
* `events.jsonl` pertencem a qual task.
|
|
80
|
-
*/
|
|
81
|
-
function readTaskTimes(cwd) {
|
|
82
|
-
try {
|
|
83
|
-
const p = join(cwd, '.morph', 'logs', 'task-times.json');
|
|
84
|
-
if (!existsSync(p)) return null;
|
|
85
|
-
return JSON.parse(readFileSync(p, 'utf8'));
|
|
86
|
-
} catch {
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function evaluatorPath(cwd, feature) {
|
|
92
|
-
return join(cwd, '.morph', 'features', feature, '4-review', 'evaluator-report.md');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Inspect one report file: does it exist, and which of the four mandatory
|
|
97
|
-
* items can be found in it.
|
|
98
|
-
*/
|
|
99
|
-
export function inspectReport(path) {
|
|
100
|
-
if (!existsSync(path)) return { exists: false, complete: false, missing: ['what', 'files', 'criteria'] };
|
|
101
|
-
let body = '';
|
|
102
|
-
try {
|
|
103
|
-
body = readFileSync(path, 'utf8');
|
|
104
|
-
} catch {
|
|
105
|
-
return { exists: true, complete: false, missing: ['what', 'files', 'criteria'], unreadable: true };
|
|
17
|
+
import { readPlanChecks } from '../../lib/plan/checks.js';
|
|
18
|
+
import { featureGeneratesMedia, collectReviewRequirement } from './gate-check.js';
|
|
19
|
+
|
|
20
|
+
export { inspectReport, REPORT_MARKERS };
|
|
21
|
+
|
|
22
|
+
/** Each review report: complete when it carries a readable verdict and is not partial. */
|
|
23
|
+
export function inspectReviewReports(cwd, feature) {
|
|
24
|
+
const out = {};
|
|
25
|
+
for (const [role, r] of Object.entries(reviewReports(cwd, feature))) {
|
|
26
|
+
out[role] = r.status === 'missing'
|
|
27
|
+
? { exists: false, complete: false, partial: false, verdict: null }
|
|
28
|
+
: { exists: true, complete: r.status === 'ok', partial: r.status === 'partial', verdict: r.verdict ?? null };
|
|
106
29
|
}
|
|
107
|
-
|
|
108
|
-
const missing = REPORT_MARKERS.filter((m) => !m.re.test(body)).map((m) => m.id);
|
|
109
|
-
// Prose = the body minus headings and list lines, so a bare file list does
|
|
110
|
-
// not pass as "what was done".
|
|
111
|
-
const prose = body
|
|
112
|
-
.split('\n')
|
|
113
|
-
.filter((l) => !/^\s*(#|[-*]\s)/.test(l))
|
|
114
|
-
.join(' ')
|
|
115
|
-
.replace(/\s+/g, ' ')
|
|
116
|
-
.trim();
|
|
117
|
-
if (prose.length < MIN_PROSE_CHARS) missing.unshift('what');
|
|
118
|
-
|
|
119
|
-
const firstLine = body.split('\n').find((l) => l.trim() !== '') ?? '';
|
|
120
|
-
const partial = PARTIAL_MARKER.test(firstLine);
|
|
121
|
-
if (partial) missing.push('partial');
|
|
122
|
-
|
|
123
|
-
return { exists: true, complete: missing.length === 0, missing, partial };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/** The evaluator report is complete when it carries a readable verdict and is not partial. */
|
|
127
|
-
export function inspectEvaluatorReport(path) {
|
|
128
|
-
if (!existsSync(path)) return { exists: false, complete: false };
|
|
129
|
-
let body = '';
|
|
130
|
-
try { body = readFileSync(path, 'utf8'); } catch { return { exists: true, complete: false, unreadable: true }; }
|
|
131
|
-
const firstLine = body.split('\n').find((l) => l.trim() !== '') ?? '';
|
|
132
|
-
const partial = PARTIAL_MARKER.test(firstLine);
|
|
133
|
-
const { verdict } = evaluatorVerdictFromText(body);
|
|
134
|
-
return { exists: true, complete: !partial && VERDICTS.includes(verdict), partial, verdict: verdict ?? null };
|
|
30
|
+
return out;
|
|
135
31
|
}
|
|
136
32
|
|
|
137
|
-
|
|
33
|
+
// The verifier routes to opus on prompt/model or media; security is dispatched only when required.
|
|
34
|
+
function reviewDispatch(cwd, feature, slices) {
|
|
138
35
|
let changedFiles = [];
|
|
139
36
|
try { changedFiles = changedPathsAgainst(detectDefaultBranch(cwd), cwd); } catch { changedFiles = []; }
|
|
140
37
|
const watch = readMorphConfig(cwd)?.project?.evals?.watch;
|
|
141
38
|
const globs = Array.isArray(watch) ? watch.filter((g) => typeof g === 'string' && g.trim()) : [];
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return existsSync(join(cwd, rel));
|
|
155
|
-
} catch {
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
}).length;
|
|
159
|
-
return { outputsExpected: expected.length, outputsPresent: present };
|
|
39
|
+
let workType;
|
|
40
|
+
try { workType = JSON.parse(readFileSync(join(cwd, '.morph', 'features', feature, 'feature.json'), 'utf8')).workType; } catch { workType = undefined; }
|
|
41
|
+
const { required, security } = collectReviewRequirement(cwd, { workType });
|
|
42
|
+
return {
|
|
43
|
+
required,
|
|
44
|
+
verifier: verifierModelFor({
|
|
45
|
+
promptModelTouched: changedFiles.some((f) => globs.some((g) => minimatch(f, g))),
|
|
46
|
+
generatesMedia: featureGeneratesMedia(slices),
|
|
47
|
+
}),
|
|
48
|
+
reviewer: { model: 'sonnet', reasons: [] },
|
|
49
|
+
security: { required: security.required, model: 'opus', reasons: security.reasons },
|
|
50
|
+
};
|
|
160
51
|
}
|
|
161
52
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
*
|
|
165
|
-
* São DOIS RELÓGIOS: `mtime` vem do filesystem, `ts` vem de
|
|
166
|
-
* `new Date().toISOString()` dentro do hook de telemetria. O write do próprio
|
|
167
|
-
* relatório e o evento que o registra caem a milissegundos de distância, com
|
|
168
|
-
* arredondamentos diferentes — sem folga, um relatório perfeitamente fresco
|
|
169
|
-
* seria acusado de velho. Dois segundos são muito mais que o skew real e muito
|
|
170
|
-
* menos que qualquer trabalho de verdade feito depois de relatar.
|
|
171
|
-
*/
|
|
53
|
+
// Two clocks: the report mtime comes from the filesystem, the event `ts` from the hook. Two
|
|
54
|
+
// seconds absorb their skew and are far less than any real work done after reporting.
|
|
172
55
|
export const STALE_TOLERANCE_MS = 2000;
|
|
173
56
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
* `Write`/`Edit` exigem caminho ABSOLUTO (`telemetry-log.js` só normaliza as
|
|
180
|
-
* barras, não relativiza); `task.outputs` é relativo à raiz — é assim que
|
|
181
|
-
* `outputEvidence` o resolve, com `join(cwd, rel)`. Sem este passo,
|
|
182
|
-
* `undeclared`/`unobserved` listariam TUDO em toda task, e um sinal que sempre
|
|
183
|
-
* grita é um sinal que se aprende a ignorar.
|
|
184
|
-
*
|
|
185
|
-
* Um target que não fica sob a raiz (outra árvore) volta como veio, em vez de
|
|
186
|
-
* virar `../../..` — fora da árvore ele é evidência legítima de escrita alheia,
|
|
187
|
-
* não um output que faltou declarar.
|
|
188
|
-
*/
|
|
189
|
-
function toRepoRelative(target, cwd) {
|
|
190
|
-
const norm = String(target || '').replace(/\\/g, '/');
|
|
191
|
-
const root = String(cwd || '').replace(/\\/g, '/').replace(/\/+$/, '');
|
|
57
|
+
const slash = (p) => String(p || '').replace(/\\/g, '/');
|
|
58
|
+
|
|
59
|
+
function toTreeRelative(target, treePath) {
|
|
60
|
+
const norm = slash(target);
|
|
61
|
+
const root = slash(treePath).replace(/\/+$/, '');
|
|
192
62
|
if (!root) return norm;
|
|
193
|
-
//
|
|
194
|
-
// ou `r:/repo` conforme quem chamou o comando.
|
|
63
|
+
// Case-insensitive prefix: on Windows the same root arrives as `R:/repo` or `r:/repo`.
|
|
195
64
|
return norm.toLowerCase().startsWith(`${root.toLowerCase()}/`) ? norm.slice(root.length + 1) : norm;
|
|
196
65
|
}
|
|
197
66
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
*/
|
|
215
|
-
function isBookkeepingWrite(target, reportRel) {
|
|
216
|
-
if (!target) return true;
|
|
217
|
-
if (target === reportRel) return true;
|
|
218
|
-
return target.includes('.morph/');
|
|
67
|
+
const reportSuffix = (feature, sliceId) => `.morph/features/${feature}/3-implement/reports/${sliceId}.md`.toLowerCase();
|
|
68
|
+
|
|
69
|
+
function loadWrites(cwd) {
|
|
70
|
+
let trees = [];
|
|
71
|
+
try { trees = loadTelemetryAllTrees(cwd); } catch { trees = []; }
|
|
72
|
+
const events = [];
|
|
73
|
+
for (const tree of trees) {
|
|
74
|
+
for (const e of tree.events) {
|
|
75
|
+
if (e?.tool !== 'Write' && e?.tool !== 'Edit') continue;
|
|
76
|
+
if (isFailedToolCall(e)) continue;
|
|
77
|
+
const ts = Date.parse(e?.ts ?? '');
|
|
78
|
+
if (!Number.isFinite(ts)) continue;
|
|
79
|
+
events.push({ ...e, rel: toTreeRelative(e.target, tree.path), tsMs: ts });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { available: trees.some((t) => t.events.length > 0), writes: dedupeEvents(events) };
|
|
219
83
|
}
|
|
220
84
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
*/
|
|
235
|
-
function observedEvidence(cwd, feature, task) {
|
|
236
|
-
const times = readTaskTimes(cwd)?.[feature]?.[task.id];
|
|
237
|
-
if (!times?.start) return { available: false };
|
|
238
|
-
|
|
239
|
-
const events = readTelemetryEvents(cwd);
|
|
240
|
-
if (!events.length) return { available: false };
|
|
241
|
-
|
|
242
|
-
const startMs = Date.parse(times.start);
|
|
243
|
-
// Janela aberta à direita quando a task não fechou: uma task ainda em
|
|
244
|
-
// andamento tem trabalho observável, e recusar auditá-la esconde exatamente
|
|
245
|
-
// o caso do relatório escrito cedo demais.
|
|
246
|
-
const endMs = times.end ? Date.parse(times.end) : Number.POSITIVE_INFINITY;
|
|
247
|
-
if (!Number.isFinite(startMs)) return { available: false };
|
|
248
|
-
|
|
249
|
-
const reportRel = `.morph/features/${feature}/3-implement/reports/${task.id}.md`;
|
|
250
|
-
const writes = [];
|
|
251
|
-
for (const e of events) {
|
|
252
|
-
if (e?.tool !== 'Write' && e?.tool !== 'Edit') continue;
|
|
253
|
-
if (isFailedToolCall(e)) continue;
|
|
254
|
-
const ts = Date.parse(e?.ts ?? '');
|
|
255
|
-
if (!Number.isFinite(ts) || ts < startMs || ts > endMs) continue;
|
|
256
|
-
const target = toRepoRelative(e.target, cwd);
|
|
257
|
-
if (isBookkeepingWrite(target, reportRel)) continue;
|
|
258
|
-
writes.push({ target, ts });
|
|
85
|
+
// The owning dispatch is the latest sub-agent write of the report; a later orchestrator edit
|
|
86
|
+
// does not hide it. Without a report, any write to one of the slice files says the work landed.
|
|
87
|
+
function observedEvidence(telemetry, feature, slice) {
|
|
88
|
+
if (!telemetry.available) return { available: false };
|
|
89
|
+
const suffix = reportSuffix(feature, slice.id);
|
|
90
|
+
const isReport = (w) => slash(w.target).toLowerCase().endsWith(suffix);
|
|
91
|
+
const declared = new Set((slice.files ?? []).map((f) => slash(f).replace(/^\.\//, '').toLowerCase()));
|
|
92
|
+
const isSliceFile = (w) => declared.has(w.rel.toLowerCase());
|
|
93
|
+
|
|
94
|
+
const reportWrites = telemetry.writes.filter((w) => isReport(w) && w.agentId);
|
|
95
|
+
const agentId = reportWrites.length ? reportWrites[reportWrites.length - 1].agentId : null;
|
|
96
|
+
if (!agentId) {
|
|
97
|
+
return { available: true, agentId: null, sliceWrites: telemetry.writes.filter(isSliceFile).length };
|
|
259
98
|
}
|
|
260
99
|
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
const
|
|
264
|
-
|
|
100
|
+
const work = telemetry.writes.filter((w) => w.agentId === agentId && !isReport(w) && !w.rel.includes('.morph/'));
|
|
101
|
+
const observed = [...new Set(work.map((w) => w.rel))];
|
|
102
|
+
const observedKeys = new Set(observed.map((f) => f.toLowerCase()));
|
|
265
103
|
return {
|
|
266
104
|
available: true,
|
|
105
|
+
agentId,
|
|
267
106
|
observed,
|
|
268
|
-
lastWriteAt:
|
|
269
|
-
undeclared: observed.filter((
|
|
270
|
-
unobserved: [
|
|
107
|
+
lastWriteAt: work.length ? new Date(Math.max(...work.map((w) => w.tsMs))).toISOString() : null,
|
|
108
|
+
undeclared: observed.filter((f) => !declared.has(f.toLowerCase())),
|
|
109
|
+
unobserved: (slice.files ?? []).filter((f) => !observedKeys.has(slash(f).replace(/^\.\//, '').toLowerCase())),
|
|
110
|
+
sliceWrites: work.filter(isSliceFile).length,
|
|
271
111
|
};
|
|
272
112
|
}
|
|
273
113
|
|
|
274
|
-
/** O relatório é mais velho que o último trabalho observado da própria task? */
|
|
275
114
|
function isReportStale(reportFsPath, observed) {
|
|
276
|
-
if (!observed?.
|
|
115
|
+
if (!observed?.agentId || !observed.lastWriteAt) return false;
|
|
277
116
|
try {
|
|
278
|
-
|
|
279
|
-
return writtenAt + STALE_TOLERANCE_MS < Date.parse(observed.lastWriteAt);
|
|
117
|
+
return statSync(reportFsPath).mtimeMs + STALE_TOLERANCE_MS < Date.parse(observed.lastWriteAt);
|
|
280
118
|
} catch {
|
|
281
119
|
return false;
|
|
282
120
|
}
|
|
283
121
|
}
|
|
284
122
|
|
|
285
|
-
|
|
286
|
-
function decide(report, evidence, stale) {
|
|
123
|
+
function decide(report, observed, stale) {
|
|
287
124
|
if (report.exists && report.complete) {
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
// sub-agent gravou o esqueleto dizendo "teste ainda não escrito — em
|
|
291
|
-
// andamento", seguiu trabalhando, escreveu os 9 testes e foi cortado. O
|
|
292
|
-
// arquivo ficou completo e MENTINDO, e saía `ok` aqui.
|
|
125
|
+
// Structural integrity answers "is the report complete?", never "is it
|
|
126
|
+
// still true?": an agent can write it early, keep working and be cut off.
|
|
293
127
|
return stale
|
|
294
128
|
? { verdict: 'report-stale', action: 'refresh-from-evidence' }
|
|
295
129
|
: { verdict: 'ok', action: 'proceed' };
|
|
296
130
|
}
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
// unfinished work as done.
|
|
131
|
+
// PARCIAL: the agent stopped short (turn ceiling) and resumes from its last
|
|
132
|
+
// `wip` commit — completing it DEGRADADO would stamp unfinished work as done.
|
|
300
133
|
if (report.exists && report.partial) return { verdict: 'report-incomplete', action: 'resume' };
|
|
301
134
|
if (report.exists) return { verdict: 'report-incomplete', action: 'complete-degraded' };
|
|
302
|
-
if (
|
|
303
|
-
|
|
304
|
-
// sub-agent; silently accepting a phantom completion costs correctness.
|
|
305
|
-
return { verdict: 'report-missing-evidence-unknown', action: 'redispatch' };
|
|
306
|
-
}
|
|
307
|
-
return evidence.outputsPresent > 0
|
|
135
|
+
if (!observed.available) return { verdict: 'report-missing-evidence-unknown', action: 'redispatch' };
|
|
136
|
+
return observed.sliceWrites > 0
|
|
308
137
|
? { verdict: 'work-landed-report-missing', action: 'accept-degraded' }
|
|
309
138
|
: { verdict: 'no-work-no-report', action: 'redispatch' };
|
|
310
139
|
}
|
|
311
140
|
|
|
312
|
-
function
|
|
313
|
-
const fsPath =
|
|
314
|
-
const report = inspectReport(fsPath);
|
|
315
|
-
const
|
|
316
|
-
const observed = observedEvidence(cwd, feature, task);
|
|
141
|
+
function checkSlice(cwd, feature, slice, telemetry) {
|
|
142
|
+
const fsPath = sliceReportPath(join(cwd, '.morph', 'features', feature), slice.id);
|
|
143
|
+
const report = inspectReport(fsPath, { covers: slice.covers ?? [] });
|
|
144
|
+
const observed = observedEvidence(telemetry, feature, slice);
|
|
317
145
|
const stale = report.exists && isReportStale(fsPath, observed);
|
|
318
|
-
return {
|
|
146
|
+
return { slice: slice.id, report, observed, ...decide(report, observed, stale) };
|
|
319
147
|
}
|
|
320
148
|
|
|
321
149
|
const ACTION_HINT = {
|
|
322
150
|
proceed: 'relatório íntegro — siga o fluxo normal.',
|
|
323
|
-
'refresh-from-evidence': 'o relatório está COMPLETO mas VELHO:
|
|
324
|
-
'complete-degraded': 'complete os itens faltantes a partir da mensagem de retorno do sub-agent, carimbe o relatório como DEGRADADO e registre a falha de protocolo
|
|
325
|
-
resume: 'relatório PARCIAL: o sub-agent parou antes do fim (limite de turnos) e a
|
|
326
|
-
'accept-degraded': 'o TRABALHO entrou — não re-despache. Grave você mesmo o relatório
|
|
327
|
-
redispatch: 're-despache a
|
|
151
|
+
'refresh-from-evidence': 'o relatório está COMPLETO mas VELHO: o agente da fatia seguiu escrevendo arquivos depois de gravá-lo, então ele descreve um estado já ultrapassado. NÃO re-despache e não confie no texto — confira os arquivos em `observed` (evidência real, não auto-declarada) e atualize o relatório a partir deles.',
|
|
152
|
+
'complete-degraded': 'complete os itens faltantes a partir da mensagem de retorno do sub-agent, carimbe o relatório como DEGRADADO e registre nele a falha de protocolo.',
|
|
153
|
+
resume: 'relatório PARCIAL: o sub-agent parou antes do fim (limite de turnos) e a fatia NÃO está pronta. Não carimbe DEGRADADO — retome o MESMO agente por `SendMessage` (cole no recado `tree.dirty` e `tree.commits` desta saída e o que falta) ou re-despache só o que falta, a partir do último commit `wip`.',
|
|
154
|
+
'accept-degraded': 'o TRABALHO entrou — não re-despache. Grave você mesmo o relatório da fatia a partir da mensagem de retorno, carimbado como DEGRADADO, com a falha de protocolo registrada nele.',
|
|
155
|
+
redispatch: 're-despache a fatia UMA vez com o mesmo prompt. Segunda ausência sem trabalho entregue = falha real: escale ao humano.',
|
|
328
156
|
};
|
|
329
157
|
|
|
330
158
|
function snapshotPath(cwd, feature) {
|
|
331
159
|
return join(cwd, '.morph', 'features', feature, '4-review', 'tree-before-eval.json');
|
|
332
160
|
}
|
|
333
161
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
* dispatched when there is one. `beforeEval` takes a new snapshot after the
|
|
337
|
-
* comparison, so the output still shows what changed since the previous one.
|
|
338
|
-
*/
|
|
339
|
-
function treeFor(cwd, feature, beforeEval, tasks = []) {
|
|
162
|
+
// `beforeEval` snapshots after comparing, so the output still shows what changed since the last one.
|
|
163
|
+
function treeFor(cwd, feature, beforeEval, slices = []) {
|
|
340
164
|
const path = snapshotPath(cwd, feature);
|
|
341
165
|
let snapshot = null;
|
|
342
166
|
try { if (existsSync(path)) snapshot = JSON.parse(readFileSync(path, 'utf8')); } catch { snapshot = null; }
|
|
343
167
|
const tree = treeState(cwd, { snapshot });
|
|
344
168
|
if (!beforeEval) return { tree };
|
|
345
|
-
const
|
|
169
|
+
const reviewModels = reviewDispatch(cwd, feature, slices);
|
|
346
170
|
const next = snapshotTree(cwd);
|
|
347
|
-
if (!next) return { tree, beforeEval: null,
|
|
171
|
+
if (!next) return { tree, beforeEval: null, reviewModels };
|
|
348
172
|
atomicWriteJson(path, next);
|
|
349
|
-
return { tree, beforeEval: { at: next.at, files: Object.keys(next.files).length },
|
|
173
|
+
return { tree, beforeEval: { at: next.at, files: Object.keys(next.files).length }, reviewModels };
|
|
350
174
|
}
|
|
351
175
|
|
|
352
|
-
|
|
353
|
-
* @param {string} feature
|
|
354
|
-
* @param {string|string[]|undefined} taskId - several ids audit a parallel
|
|
355
|
-
* batch whose tasks are not `done` yet, which the whole-feature mode skips
|
|
356
|
-
* @param {{ json?: boolean, cwd?: string }} [options]
|
|
357
|
-
*/
|
|
358
|
-
export function reportCheckCommand(feature, taskId, options = {}) {
|
|
176
|
+
export function reportCheckCommand(feature, sliceIds, options = {}) {
|
|
359
177
|
const cwd = options.cwd || process.cwd();
|
|
360
178
|
const emit = (obj, code) => {
|
|
361
179
|
if (options.json) console.log(JSON.stringify(obj, null, 2));
|
|
@@ -363,39 +181,24 @@ export function reportCheckCommand(feature, taskId, options = {}) {
|
|
|
363
181
|
process.exit(code);
|
|
364
182
|
};
|
|
365
183
|
|
|
366
|
-
const
|
|
367
|
-
if (!
|
|
368
|
-
emit({ error: `feature "${feature}"
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
let tasks = [];
|
|
372
|
-
try {
|
|
373
|
-
const parsed = JSON.parse(readFileSync(tasksPath, 'utf8'));
|
|
374
|
-
tasks = Array.isArray(parsed.tasks) ? parsed.tasks : [];
|
|
375
|
-
} catch {
|
|
376
|
-
emit({ error: `2-plan/tasks.json de "${feature}" está corrompido`, feature }, 1);
|
|
184
|
+
const { status, checks } = readPlanChecks(cwd, feature);
|
|
185
|
+
if (!checks) {
|
|
186
|
+
emit({ error: `feature "${feature}" sem 2-plan/checks.json legível (${status}) — rode morph-spec plan-check ${feature}`, feature }, 1);
|
|
377
187
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
if (!task) emit({ error: `task "${taskId}" não existe em tasks.json`, feature }, 1);
|
|
390
|
-
const result = checkTask(cwd, feature, task);
|
|
391
|
-
emit({ feature, ...result, hint: ACTION_HINT[result.action], ...treeFor(cwd, feature, options.beforeEval, tasks) }, result.verdict === 'ok' ? 0 : 1);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// Whole-feature audit: every DONE task owes a report; a pending one does not.
|
|
395
|
-
const results = tasks.filter((t) => t.status === 'done').map((t) => checkTask(cwd, feature, t));
|
|
396
|
-
const evaluator = inspectEvaluatorReport(evaluatorPath(cwd, feature));
|
|
188
|
+
const slices = checks.slices;
|
|
189
|
+
const ids = Array.isArray(sliceIds) ? sliceIds : sliceIds ? [sliceIds] : [];
|
|
190
|
+
const unknown = ids.filter((id) => !slices.some((s) => s.id === id));
|
|
191
|
+
if (unknown.length) emit({ error: `fatia(s) ${unknown.join(', ')} não existe(m) no plano de ${feature}`, feature }, 1);
|
|
192
|
+
|
|
193
|
+
const telemetry = loadWrites(cwd);
|
|
194
|
+
const chosen = ids.length ? ids.map((id) => slices.find((s) => s.id === id)) : slices;
|
|
195
|
+
const results = chosen.map((s) => {
|
|
196
|
+
const r = checkSlice(cwd, feature, s, telemetry);
|
|
197
|
+
return { ...r, hint: ACTION_HINT[r.action] };
|
|
198
|
+
});
|
|
397
199
|
const clean = results.every((r) => r.verdict === 'ok');
|
|
398
|
-
|
|
200
|
+
const reviews = ids.length ? undefined : inspectReviewReports(cwd, feature);
|
|
201
|
+
emit({ feature, results, ...(reviews ? { reviews } : {}), clean, ...treeFor(cwd, feature, options.beforeEval, slices) }, clean ? 0 : 1);
|
|
399
202
|
}
|
|
400
203
|
|
|
401
204
|
function printHuman(obj) {
|
|
@@ -403,37 +206,30 @@ function printHuman(obj) {
|
|
|
403
206
|
console.error(chalk.red(`\n❌ ${obj.error}\n`));
|
|
404
207
|
return;
|
|
405
208
|
}
|
|
406
|
-
|
|
209
|
+
console.log(chalk.bold(`\n📄 report-check ${obj.feature}`));
|
|
210
|
+
if (!obj.results.length) console.log(chalk.gray(' nenhuma fatia no plano — nada a auditar'));
|
|
211
|
+
for (const r of obj.results) {
|
|
407
212
|
const icon = r.verdict === 'ok' ? chalk.green('✓') : chalk.yellow('⚠');
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
: 'outputs não declarados';
|
|
411
|
-
console.log(` ${icon} ${String(r.task).padEnd(6)} ${r.verdict.padEnd(32)} ${ev}`);
|
|
213
|
+
const who = r.observed.agentId ? `agente ${String(r.observed.agentId).slice(0, 8)}` : (r.observed.available ? 'sem dispatch' : 'sem telemetria');
|
|
214
|
+
console.log(` ${icon} ${String(r.slice).padEnd(5)} ${r.verdict.padEnd(32)} ${who}`);
|
|
412
215
|
if (r.verdict !== 'ok') {
|
|
413
216
|
if (r.report.exists && r.report.missing.length) {
|
|
414
217
|
console.log(chalk.gray(` faltando no relatório: ${r.report.missing.join(', ')}`));
|
|
415
218
|
}
|
|
416
|
-
console.log(chalk.gray(` → ${
|
|
219
|
+
console.log(chalk.gray(` → ${r.hint}`));
|
|
417
220
|
}
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
if (obj.task) {
|
|
421
|
-
console.log(chalk.bold(`\n📄 report-check ${obj.feature} ${obj.task}`));
|
|
422
|
-
line(obj);
|
|
423
|
-
printTree(obj.tree);
|
|
424
|
-
console.log('');
|
|
425
|
-
return;
|
|
426
221
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
if (!obj.results.length) console.log(chalk.gray(' nenhuma task done — nada a auditar'));
|
|
430
|
-
for (const r of obj.results) line(r);
|
|
431
|
-
if (obj.evaluator) {
|
|
222
|
+
for (const [role, r] of Object.entries(obj.reviews ?? {})) {
|
|
223
|
+
if (!r.exists && role === 'security') continue;
|
|
432
224
|
console.log(
|
|
433
|
-
` ${
|
|
434
|
-
+
|
|
225
|
+
` ${r.complete ? chalk.green('✓') : chalk.yellow('⚠')} ${'Gate 3'.padEnd(6)} `
|
|
226
|
+
+ `${reviewFileName(role)} ${r.exists ? (r.complete ? `íntegro (${r.verdict})` : r.partial ? 'PARCIAL' : 'incompleto') : 'ausente'}`,
|
|
435
227
|
);
|
|
436
228
|
}
|
|
229
|
+
if (obj.reviewModels) {
|
|
230
|
+
const m = obj.reviewModels;
|
|
231
|
+
console.log(chalk.gray(` dispatch: verifier ${m.verifier.model}, reviewer ${m.reviewer.model}${m.security.required ? `, security ${m.security.model}` : ''}`));
|
|
232
|
+
}
|
|
437
233
|
printTree(obj.tree);
|
|
438
234
|
console.log('');
|
|
439
235
|
}
|