@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
package/src/lib/risk-detector.js
CHANGED
|
@@ -1,95 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
8
|
-
* Signals checked (in order):
|
|
9
|
-
* - designSystemDivergence: boolean — conflicting design sources detected
|
|
10
|
-
* - standardsViolations: string[] — violations of the plan's forbidden patterns
|
|
11
|
-
* - verificationStatus: string — último `morph-spec verify` ('fail' pausa, qualquer gate)
|
|
12
|
-
* - verificationScope + gate: string — presente e != 'feature' pausa SÓ no ship: o
|
|
13
|
-
* último `verify` foi um recorte de task, não da feature inteira (`null`/ausente
|
|
14
|
-
* não conta — narrow, nunca widen)
|
|
15
|
-
* - e2eStatus + e2eReason + gate — nó e2e do último verify. Um `skip`
|
|
16
|
-
* cuja razão NÃO seja dispensa legítima pausa SÓ no ship (gate 'review').
|
|
17
|
-
* Sem escopo (stamp sem o campo) cai do mesmo lado seguro que `unknown`
|
|
18
|
-
* - verificationStale + gate: bool/str — stamp de um commit cujo código já mudou
|
|
19
|
-
* (fora de .morph/) pausa SÓ no ship (gate 'review')
|
|
20
|
-
* - evaluatorVerdict + gate: object — { status, verdict, blockers, untyped } do
|
|
21
|
-
* evaluator-report.md. No ship, só `SHIP` sem bloqueador libera
|
|
22
|
-
* - mediaClaims + gate: object — { section, featureGeneratesMedia }
|
|
23
|
-
* - screenChecklist + gate: object — { mockups, legacy, screens, problems, exempt?,
|
|
24
|
-
* section?, uncovered? } derivado do 1-design/mockups.md. No uiux, checklist
|
|
25
|
-
* de tela incompleto pausa; no ship, item decidido sem a conferência do
|
|
26
|
-
* avaliador pausa. Mockup legado é isento nos dois, com a razão listada
|
|
27
|
-
* - oversizedTasks + gate: array — tasks acima de MAX_TASK_OUTPUTS outputs;
|
|
28
|
-
* pausa SÓ no plano (gate 'plan'): a task tem de caber num dispatch
|
|
29
|
-
* - taskCount: number — total tasks (pause if > 20)
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
const SCOPE_TASK_THRESHOLD = 20;
|
|
33
|
-
|
|
34
|
-
// Caminho alterado que pede o avaliador em opus: o resto da review roda em sonnet.
|
|
35
|
-
const OPUS_PATH_RE = /(^|[/._-])(auth|login|senha|password|token|secret|crypto|permiss|rbac|acl|lgpd|pii|privacy|payment|pagamento|billing|cobranca|invoice|asaas|stripe|migrations?)([/._-]|$)/i;
|
|
36
|
-
|
|
37
|
-
// Work types whose ship gate (review) always pauses for a human, regardless
|
|
38
|
-
// of trust mode — a type policy, not a per-feature risk signal. Mirrors
|
|
39
|
-
// `forcedPauseGates` in src/core/work-types.js (hotfix.review); kept here too
|
|
40
|
-
// because risk-detector is the deterministic layer consumed independent of
|
|
41
|
-
// the work-type config object.
|
|
1
|
+
import { MAX_SLICES } from './plan/checks.js';
|
|
2
|
+
import { BLOCKER_TYPES, reviewFileName } from './review-reports.js';
|
|
3
|
+
|
|
4
|
+
const SECURITY_PATH_RE = /(^|[/._-])(auth|login|senha|password|token|secret|crypto|permiss|rbac|acl|lgpd|pii|privacy|payment|pagamento|billing|cobranca|invoice|asaas|stripe|migrations?)([/._-]|$)/i;
|
|
5
|
+
|
|
6
|
+
// Espelha `forcedPauseGates` (hotfix.review) de src/core/work-types.js: os dois mudam juntos.
|
|
42
7
|
const FORCED_PAUSE_WORK_TYPES = ['hotfix'];
|
|
43
8
|
|
|
44
|
-
//
|
|
45
|
-
// nó e2e pula. Casadas por PADRÃO e não por igualdade porque duas delas
|
|
46
|
-
// carregam dado variável (o nome do grupo da task). Ordem irrelevante: os
|
|
47
|
-
// padrões são disjuntos.
|
|
9
|
+
// Razões literais que `e2e-check.js` emite ao pular; casadas por padrão porque algumas carregam dado variável.
|
|
48
10
|
const E2E_SKIP_PATTERNS = [
|
|
49
|
-
// "docker is not available in this environment — e2e could not run"
|
|
50
|
-
// (e2e-check.js). A app não foi exercida porque a MÁQUINA não podia.
|
|
51
11
|
[/docker is not available/i, 'environment'],
|
|
52
|
-
// `--skip-e2e` — o operador pediu para pular.
|
|
53
12
|
[/^requested$/i, 'requested'],
|
|
54
|
-
// "project does not containerize (no compose file)" — não há app para subir.
|
|
55
13
|
[/does not containerize/i, 'project'],
|
|
56
|
-
// 'task group "docs" does not touch runtime' — escopo da task, não da app.
|
|
57
|
-
[/does not touch runtime/i, 'scope'],
|
|
58
14
|
];
|
|
59
15
|
|
|
60
|
-
//
|
|
61
|
-
// diz que ele não precisava rodar. `unknown` está aqui de propósito — inclui a
|
|
62
|
-
// razão vazia/nula, e uma dispensa sem motivo é o mesmo no-op silencioso que
|
|
63
|
-
// esta regra existe para eliminar.
|
|
16
|
+
// `unknown` (razão vazia) pausa de propósito: dispensa sem motivo é o no-op silencioso que a regra elimina.
|
|
64
17
|
const E2E_SKIP_CLASSES_THAT_PAUSE = ['environment', 'requested', 'unknown'];
|
|
65
18
|
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
// pula com a razão do GRUPO daquela task ('task group "docs" does not touch
|
|
69
|
-
// runtime') — uma afirmação sobre UMA task, nunca sobre a app. Sem este teste,
|
|
70
|
-
// a última task de uma feature (tipicamente `group: docs`) compra o Gate 3 da
|
|
71
|
-
// feature que ela mesma acabou de escrever, numa máquina onde a app nunca
|
|
72
|
-
// subiu. Ausente/desconhecido cai do mesmo lado seguro que `unknown`: recusar
|
|
73
|
-
// é barato, aprovar sem prova não é.
|
|
74
|
-
const FEATURE_SCOPE = 'feature';
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Classifica a razão literal de um `skip` do nó e2e — de QUEM é a ausência de
|
|
78
|
-
* prova. Existe porque `verificationStatus` é o rollup do `verify` e um nó que
|
|
79
|
-
* pula NÃO o reprova: sem classificar a razão, "o e2e rodou e passou" e "o e2e
|
|
80
|
-
* nunca rodou" chegam ao gate como o mesmo `pass`.
|
|
81
|
-
*
|
|
82
|
-
* A assimetria é o ponto: `project`/`scope` descrevem o REPOSITÓRIO dizendo que
|
|
83
|
-
* não há runtime a exercer (dispensa legítima, registrada com motivo);
|
|
84
|
-
* `environment`/`requested` descrevem a MÁQUINA ou o operador, e nenhum dos
|
|
85
|
-
* dois é evidência sobre o código. `unknown` cai no lado seguro pelo mesmo
|
|
86
|
-
* motivo: recusar é barato, aprovar sem prova não é.
|
|
87
|
-
*
|
|
88
|
-
* @param {string|null|undefined} reason - `verification.e2eReason` (a razão
|
|
89
|
-
* literal que o `morph-spec verify` carimbou). Vazia/ausente ⇒ 'unknown' —
|
|
90
|
-
* DESCONHECIDO, jamais lido como dispensa.
|
|
91
|
-
* @returns {'environment'|'requested'|'project'|'scope'|'unknown'}
|
|
92
|
-
*/
|
|
19
|
+
// `project` diz que o repositório não tem runtime (dispensa legítima); `environment`/`requested` falam
|
|
20
|
+
// da máquina ou do operador, nunca do código. Razão vazia é `unknown`, jamais dispensa.
|
|
93
21
|
export function classifyE2eSkip(reason) {
|
|
94
22
|
const text = typeof reason === 'string' ? reason.trim() : '';
|
|
95
23
|
if (text === '') return 'unknown';
|
|
@@ -99,7 +27,6 @@ export function classifyE2eSkip(reason) {
|
|
|
99
27
|
return 'unknown';
|
|
100
28
|
}
|
|
101
29
|
|
|
102
|
-
/** Mensagem de pausa por classe — nomeia SEMPRE a razão literal do verify. */
|
|
103
30
|
function e2ePauseReason(klass, reason) {
|
|
104
31
|
const literal = typeof reason === 'string' && reason.trim() !== '' ? reason.trim() : null;
|
|
105
32
|
if (klass === 'environment') {
|
|
@@ -116,98 +43,115 @@ function e2ePauseReason(klass, reason) {
|
|
|
116
43
|
return `e2e pulado por razão não classificada: "${literal}" — sem classificação, não há dispensa`;
|
|
117
44
|
}
|
|
118
45
|
|
|
119
|
-
/**
|
|
120
|
-
* Mensagem de recusa por ESCOPO do stamp: a razão do skip É uma dispensa
|
|
121
|
-
* legítima (`project`/`scope`), mas quem a carimbou foi um verify que não
|
|
122
|
-
* responde pela feature. Nomeia o escopo real para que o leitor saiba qual
|
|
123
|
-
* comando rodar, em vez de rodar o mesmo de novo.
|
|
124
|
-
*/
|
|
125
|
-
function e2eScopeRefusal(klass, scope, reason) {
|
|
126
|
-
const literal = typeof reason === 'string' && reason.trim() !== '' ? reason.trim() : 'sem motivo registrado';
|
|
127
|
-
const where = typeof scope === 'string' && scope.trim() !== '' ? scope.trim() : 'ausente';
|
|
128
|
-
return `dispensa de e2e [${klass}] veio de um verify de escopo "${where}", não da feature:`
|
|
129
|
-
+ ` "${literal}" — um stamp de task responde por UMA task, não pela app inteira;`
|
|
130
|
-
+ ' rode `morph-spec verify {feature}` (sem task) antes do ship';
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/** A razão gravada no sinal quando o checklist de tela isenta um mockup legado. */
|
|
134
46
|
export const LEGACY_MOCKUP_EXEMPTION = 'checklist dispensado: mockup no formato antigo';
|
|
135
47
|
|
|
136
|
-
/** Os dois primeiros itens de uma lista, e quantos ficaram de fora. */
|
|
137
48
|
function firstOf(items) {
|
|
138
49
|
const shown = items.slice(0, 2).join('; ');
|
|
139
50
|
return items.length > 2 ? `${shown} (+${items.length - 2})` : shown;
|
|
140
51
|
}
|
|
141
52
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
* aprovação automática que passasse onde o gate-check pausa seria o atalho.
|
|
146
|
-
*
|
|
147
|
-
* Mockup ausente (`mockups: false`) não conta — nem toda feature de UI tem
|
|
148
|
-
* tela —, e o legado chega sem problemas: a auditoria o isenta.
|
|
149
|
-
*
|
|
150
|
-
* @param {object|undefined} screenChecklist - o sinal de `collectScreenChecklist`
|
|
151
|
-
* @returns {string|null}
|
|
152
|
-
*/
|
|
53
|
+
// Exportada porque approve/advance `auto` recusam pela mesma regra. Sem design de tela não conta,
|
|
54
|
+
// e o legado chega sem problemas: a auditoria o isenta. Checklist
|
|
55
|
+
// completo ainda deve o protótipo e os prints 1280/390 (`prototypes`).
|
|
153
56
|
export function uiuxChecklistPause(screenChecklist) {
|
|
154
57
|
if (screenChecklist?.mockups !== true) return null;
|
|
155
58
|
const problems = screenChecklist.problems ?? [];
|
|
156
|
-
if (problems.length
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return firstOf(
|
|
59
|
+
if (problems.length > 0) {
|
|
60
|
+
return `checklist de tela incompleto em 1-design/mockups.md: ${firstOf(problems)}`
|
|
61
|
+
+ ' — volume, paginação, ordenação, filtro, os três estados, mobile e abas por tela'
|
|
62
|
+
+ ' ("não se aplica" vale, se escrito)';
|
|
63
|
+
}
|
|
64
|
+
const prototypes = screenChecklist.prototypes ?? [];
|
|
65
|
+
if (prototypes.length === 0) return null;
|
|
66
|
+
return `protótipo incompleto: ${firstOf(prototypes)}`
|
|
67
|
+
+ ' — cada tela aponta o HTML renderizado e deixa os prints de 1280 e 390 ao lado dele';
|
|
164
68
|
}
|
|
165
69
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
70
|
+
const ROLE_PERSONA = { verifier: 'morph-verifier', reviewer: 'morph-reviewer', security: 'morph-security' };
|
|
71
|
+
|
|
72
|
+
export function reviewPause(signal) {
|
|
73
|
+
const required = signal?.required ?? ['verifier'];
|
|
74
|
+
const reports = signal?.reports ?? {};
|
|
75
|
+
for (const role of required) {
|
|
76
|
+
const r = reports[role];
|
|
77
|
+
const file = `4-review/${reviewFileName(role)}`;
|
|
78
|
+
if (!r || r.status === 'missing') {
|
|
79
|
+
return `Gate 3 sem \`${file}\` — despache o \`${ROLE_PERSONA[role]}\` (contexto fresco);`
|
|
80
|
+
+ ' auto-avaliação de quem implementou não decide o ship';
|
|
81
|
+
}
|
|
82
|
+
if (r.status === 'partial') {
|
|
83
|
+
return `\`${file}\` está PARCIAL — retome o mesmo agente por SendMessage; parcial não é veredito`;
|
|
84
|
+
}
|
|
85
|
+
if (r.status !== 'ok') {
|
|
86
|
+
return `\`${file}\` sem \`**Verdict:** SHIP|FIX\` — o relatório não seguiu o contrato`;
|
|
87
|
+
}
|
|
174
88
|
}
|
|
175
|
-
|
|
176
|
-
|
|
89
|
+
const present = Object.entries(reports).filter(([, r]) => r.status === 'ok');
|
|
90
|
+
const untyped = present.reduce((n, [, r]) => n + (r.untyped ?? 0), 0);
|
|
91
|
+
if (untyped > 0) {
|
|
92
|
+
return `${untyped} bloqueador(es) sem tipo de evidência (${BLOCKER_TYPES.join(' | ')})`
|
|
177
93
|
+ ' — sem evidência tipada não é bloqueador: reclassifique como nota ou complete a evidência';
|
|
178
94
|
}
|
|
179
|
-
|
|
180
|
-
|
|
95
|
+
for (const [role, r] of present) {
|
|
96
|
+
if ((r.blockers ?? 0) > 0) return `${role} abriu ${r.blockers} bloqueador(es): ${firstOf(r.blockerIds ?? [])}`;
|
|
97
|
+
}
|
|
98
|
+
for (const [role, r] of present) {
|
|
99
|
+
if (r.verdict !== 'SHIP') return `veredito do ${role} é ${r.verdict}`;
|
|
100
|
+
}
|
|
101
|
+
const checks = signal?.checks;
|
|
102
|
+
if (!checks || checks.status !== 'ok') {
|
|
103
|
+
return 'sem `2-plan/checks.json` legível — a cobertura dos cenários pelo verificador não é conferível';
|
|
104
|
+
}
|
|
105
|
+
const gaps = [
|
|
106
|
+
...checks.missing.map((id) => `${id} sem linha`),
|
|
107
|
+
...checks.notProven.map(({ id, status }) => `${id} ${status}`),
|
|
108
|
+
];
|
|
109
|
+
if (gaps.length) {
|
|
110
|
+
return `\`## Checks\` do verificador não prova ${firstOf(gaps)}`
|
|
111
|
+
+ ' — todo cenário precisa de uma linha PROVADO com evidência';
|
|
112
|
+
}
|
|
181
113
|
return null;
|
|
182
114
|
}
|
|
183
115
|
|
|
184
|
-
function
|
|
185
|
-
|
|
186
|
-
|
|
116
|
+
function reviewSummary(signal) {
|
|
117
|
+
const reports = signal?.reports ?? {};
|
|
118
|
+
const roles = Object.keys(reports);
|
|
119
|
+
if (roles.length === 0) return 'Gate 3 sem relatórios de review';
|
|
120
|
+
const verdicts = roles.map((role) => `${role} ${reports[role].verdict ?? reports[role].status}`).join(', ');
|
|
121
|
+
const blockers = roles.reduce((n, role) => n + (reports[role].blockers ?? 0), 0);
|
|
122
|
+
const checks = signal.checks?.status === 'ok'
|
|
123
|
+
? `; checks ${signal.checks.total - signal.checks.missing.length - signal.checks.notProven.length}/${signal.checks.total} PROVADO`
|
|
124
|
+
: '';
|
|
125
|
+
return `review: ${verdicts}; ${blockers} bloqueador(es)${checks}`;
|
|
187
126
|
}
|
|
188
127
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
* (auth, segredo, permissão, dado pessoal, dinheiro, migration), prompt/modelo
|
|
192
|
-
* ou geração de mídia; `sonnet` no resto.
|
|
193
|
-
*
|
|
194
|
-
* @param {{ changedFiles?: string[], promptModelTouched?: boolean, generatesMedia?: boolean }} input
|
|
195
|
-
* @returns {{ model: 'opus'|'sonnet', reasons: string[] }}
|
|
196
|
-
*/
|
|
197
|
-
export function evaluatorModelFor({ changedFiles = [], promptModelTouched = false, generatesMedia = false } = {}) {
|
|
128
|
+
// Caminho sensível não muda o verificador: despacha a persona `security` (`securityReviewFor`).
|
|
129
|
+
export function verifierModelFor({ promptModelTouched = false, generatesMedia = false } = {}) {
|
|
198
130
|
const reasons = [];
|
|
199
|
-
const risky = changedFiles.filter((f) => OPUS_PATH_RE.test(f.replaceAll('\\', '/')));
|
|
200
|
-
if (risky.length) reasons.push(`caminho sensível: ${firstOf(risky)}`);
|
|
201
131
|
if (promptModelTouched) reasons.push('prompt/modelo alterado');
|
|
202
132
|
if (generatesMedia) reasons.push('gera mídia');
|
|
203
133
|
return { model: reasons.length ? 'opus' : 'sonnet', reasons };
|
|
204
134
|
}
|
|
205
135
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
136
|
+
export function securityReviewFor(changedFiles = []) {
|
|
137
|
+
const risky = changedFiles.filter((f) => SECURITY_PATH_RE.test(f.replaceAll('\\', '/')));
|
|
138
|
+
return { required: risky.length > 0, reasons: risky.length ? [`caminho sensível: ${firstOf(risky)}`] : [] };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const PLAN_CHECKS_REASONS = {
|
|
142
|
+
'no-plan': 'sem 2-plan/plan.md',
|
|
143
|
+
'no-checks': 'sem 2-plan/checks.json',
|
|
144
|
+
unreadable: '2-plan/checks.json ilegível ou de outra versão',
|
|
145
|
+
stale: '2-plan/checks.json não corresponde ao plan.md atual',
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Regra única do gate-check (pausa) e do approve/advance automáticos (recusa).
|
|
149
|
+
export function planChecksPause(status) {
|
|
150
|
+
if (status === undefined || status === 'fresh') return null;
|
|
151
|
+
const what = PLAN_CHECKS_REASONS[status] ?? `checks.json em estado "${status}"`;
|
|
152
|
+
return `${what} — rode \`morph-spec plan-check {feature}\` até zero erro antes do gate do plano`;
|
|
153
|
+
}
|
|
154
|
+
|
|
211
155
|
export function shouldPauseGate(config, signals) {
|
|
212
156
|
const trust = config?.trust ?? 'auto';
|
|
213
157
|
|
|
@@ -233,10 +177,7 @@ export function shouldPauseGate(config, signals) {
|
|
|
233
177
|
};
|
|
234
178
|
}
|
|
235
179
|
|
|
236
|
-
//
|
|
237
|
-
// testes/validadores) está falhando — código antes do juiz vale também
|
|
238
|
-
// para a decisão de pausa. Só 'fail' pausa: ausência de stamp não é sinal
|
|
239
|
-
// (features antigas não têm o campo).
|
|
180
|
+
// Só 'fail' pausa aqui: features antigas não têm o stamp.
|
|
240
181
|
if (signals.verificationStatus === 'fail') {
|
|
241
182
|
return {
|
|
242
183
|
pause: true,
|
|
@@ -244,7 +185,7 @@ export function shouldPauseGate(config, signals) {
|
|
|
244
185
|
};
|
|
245
186
|
}
|
|
246
187
|
|
|
247
|
-
// No ship, ausência de stamp não é
|
|
188
|
+
// No ship, ausência de stamp não é verde.
|
|
248
189
|
if (signals.gate === 'review' && !signals.verificationStatus) {
|
|
249
190
|
return {
|
|
250
191
|
pause: true,
|
|
@@ -252,68 +193,16 @@ export function shouldPauseGate(config, signals) {
|
|
|
252
193
|
};
|
|
253
194
|
}
|
|
254
195
|
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
// SINGULAR, sobrescrito por qualquer verify de qualquer escopo, e vale
|
|
258
|
-
// INDEPENDENTE do que os nós individuais dizem: mesmo com e2e 'pass' dentro
|
|
259
|
-
// daquele recorte, build/tests/validators também rodaram só para a task, não
|
|
260
|
-
// para a feature. Antes desta regra, um `verify {f} {task}` totalmente verde
|
|
261
|
-
// comprava o Gate 3 da feature que ele nem tentou verificar por inteiro — já
|
|
262
|
-
// acontecia manualmente (`--filter`), e o recorte automático de PR B faz
|
|
263
|
-
// acontecer em TODA task. Por isso corre ANTES do bloco de e2e abaixo: ele
|
|
264
|
-
// pausa por si só, sem depender de e2eStatus/e2eReason.
|
|
265
|
-
//
|
|
266
|
-
// `typeof === 'string'` exclui `null`/ausente (features legadas sem stamp) —
|
|
267
|
-
// narrow, nunca widen. O caminho de volta é sempre o mesmo, barato e nomeado:
|
|
268
|
-
// `morph-spec verify {feature}` (suíte inteira) antes do ship.
|
|
269
|
-
if (signals.gate === 'review'
|
|
270
|
-
&& typeof signals.verificationScope === 'string'
|
|
271
|
-
&& signals.verificationScope !== FEATURE_SCOPE) {
|
|
272
|
-
return {
|
|
273
|
-
pause: true,
|
|
274
|
-
reason: `último morph-spec verify foi de escopo "${signals.verificationScope}" (recorte parcial de task)`
|
|
275
|
-
+ ' — rode `morph-spec verify {feature}` (suíte inteira) antes do ship',
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// No gate de SHIP apenas: o nó e2e do último verify PULOU. Um skip não
|
|
280
|
-
// reprova o rollup, então `verificationStatus` sai 'pass' e o gate ficava
|
|
281
|
-
// cego — auto-passando um Gate 3 sobre uma app que ninguém subiu. A razão
|
|
282
|
-
// literal decide: dispensa legítima (o projeto não containeriza; o grupo da
|
|
283
|
-
// task não toca runtime) passa e fica REGISTRADA nos signals; ausência de
|
|
284
|
-
// prova (docker indisponível, `--skip-e2e`, razão nenhuma) pausa. É a prosa
|
|
285
|
-
// do morph-review virando regra — "docker indisponível não é dispensa" — e
|
|
286
|
-
// é o que dispensa o `gate-decision --pause false` manual que ela mandava
|
|
287
|
-
// rodar. Só no review: pausar o Gate 1/2 numa máquina sem docker travaria
|
|
288
|
-
// trabalho legítimo de planejamento.
|
|
196
|
+
// Skip não reprova o rollup, então só a razão literal separa dispensa de ausência de prova.
|
|
197
|
+
// Só no ship: pausar plan/uiux numa máquina sem docker travaria planejamento legítimo.
|
|
289
198
|
if (signals.gate === 'review' && signals.e2eStatus === 'skip') {
|
|
290
199
|
const klass = classifyE2eSkip(signals.e2eReason);
|
|
291
200
|
if (E2E_SKIP_CLASSES_THAT_PAUSE.includes(klass)) {
|
|
292
201
|
return { pause: true, reason: e2ePauseReason(klass, signals.e2eReason) };
|
|
293
202
|
}
|
|
294
|
-
// Segunda pergunta, que a razão sozinha não responde: QUEM carimbou. Um
|
|
295
|
-
// escopo de TASK (`task:{id}`) já pausou acima, antes deste bloco — o que
|
|
296
|
-
// sobra a checar aqui é só a ausência de escopo (`null`/indefinido:
|
|
297
|
-
// feature legada, ou stamp anterior à introdução do campo). Cai do mesmo
|
|
298
|
-
// lado seguro que `unknown` pelo mesmo motivo de sempre: recusar é barato,
|
|
299
|
-
// aprovar sem prova de QUEM respondeu pela app não é. O caminho de volta é
|
|
300
|
-
// o mesmo (rodar `verify {feature}`), e é o que morph-review já manda
|
|
301
|
-
// fazer antes do Gate 3.
|
|
302
|
-
if (signals.verificationScope == null) {
|
|
303
|
-
return {
|
|
304
|
-
pause: true,
|
|
305
|
-
reason: e2eScopeRefusal(klass, signals.verificationScope, signals.e2eReason),
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
203
|
}
|
|
309
204
|
|
|
310
|
-
//
|
|
311
|
-
// sha diverge do HEAD atual significa que código mudou depois do último
|
|
312
|
-
// `morph-spec verify` — o resultado carimbado não cobre mais o que está
|
|
313
|
-
// prestes a ser aprovado. Pausa só aqui, nunca em plan/uiux/proposal: essa
|
|
314
|
-
// é uma regra de ship, não uma regra geral de gate. Stamp AUSENTE não entra
|
|
315
|
-
// aqui (verificationStale é fail-open para `false` em collectSignals
|
|
316
|
-
// quando não há stamp) — features legadas sem stamp continuam liberadas.
|
|
205
|
+
// Regra de ship, não de gate em geral; stamp ausente é fail-open (`false`) em collectSignals.
|
|
317
206
|
if (signals.gate === 'review' && signals.verificationStale === true) {
|
|
318
207
|
return {
|
|
319
208
|
pause: true,
|
|
@@ -322,23 +211,13 @@ export function shouldPauseGate(config, signals) {
|
|
|
322
211
|
}
|
|
323
212
|
|
|
324
213
|
if (signals.gate === 'review') {
|
|
325
|
-
const reason =
|
|
214
|
+
const reason = reviewPause(signals.reviewReports);
|
|
326
215
|
if (reason) return { pause: true, reason };
|
|
327
216
|
}
|
|
328
217
|
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
// unitário: os dois continuam verdes com o prompt trocado.
|
|
333
|
-
//
|
|
334
|
-
// DEPOIS da cláusula de procedência do score, de propósito: quando as duas
|
|
335
|
-
// valem, "Gate 3 sem score do avaliador independente" continua sendo a razão
|
|
336
|
-
// reportada — é o defeito mais grave dos dois.
|
|
337
|
-
//
|
|
338
|
-
// PAUSA, jamais bloqueia: `approve review` não ganha precondição nova. Uma
|
|
339
|
-
// segunda precondição dura ali tornaria inaprovável, inclusive por decisão
|
|
340
|
-
// humana, toda feature legada. Pausa é a severidade certa — o humano lê a
|
|
341
|
-
// razão e decide.
|
|
218
|
+
// Prompt e modelo não são lidos por build nem por teste. Depois dos relatórios de review de propósito:
|
|
219
|
+
// relatório ausente é o defeito mais grave. Pausa, nunca bloqueia: bloquear tornaria inaprovável
|
|
220
|
+
// toda feature legada.
|
|
342
221
|
if (signals.gate === 'review' && (signals.promptModel?.touched?.length ?? 0) > 0) {
|
|
343
222
|
const touched = signals.promptModel.touched;
|
|
344
223
|
const where = `${touched.slice(0, 3).join(', ')}${touched.length > 3 ? ` (+${touched.length - 3})` : ''}`;
|
|
@@ -360,9 +239,7 @@ export function shouldPauseGate(config, signals) {
|
|
|
360
239
|
+ ' — rode `morph-spec verify {feature}` (escopo de feature) antes do ship',
|
|
361
240
|
};
|
|
362
241
|
}
|
|
363
|
-
// 'fail'
|
|
364
|
-
// `verificationStatus` acima pausou antes daqui. 'pass' com stamp velho
|
|
365
|
-
// também já pausou, por `verificationStale`.
|
|
242
|
+
// 'fail' já pausou por `verificationStatus`; 'pass' com stamp velho, por `verificationStale`.
|
|
366
243
|
}
|
|
367
244
|
|
|
368
245
|
// Feature que gera mídia: custo, revisão humana, marcação e hot path só valem provados na seção.
|
|
@@ -371,32 +248,26 @@ export function shouldPauseGate(config, signals) {
|
|
|
371
248
|
&& signals.mediaClaims.section !== true) {
|
|
372
249
|
return {
|
|
373
250
|
pause: true,
|
|
374
|
-
reason: '
|
|
251
|
+
reason: '`4-review/verifier.md` sem a seção `## Mídia gerada` — as quatro afirmações de mídia'
|
|
375
252
|
+ ' (custo, revisão humana, marcação, hot path) não foram provadas'
|
|
376
|
-
+ ' (evals/review-checklist.md, "
|
|
253
|
+
+ ' (evals/review-checklist.md, "Verificador")',
|
|
377
254
|
};
|
|
378
255
|
}
|
|
379
256
|
|
|
380
|
-
//
|
|
381
|
-
// completo. É ele que faz paginação, ordenação, filtro e os três estados
|
|
382
|
-
// virarem `doneCriteria` — sem ele, uma lista de 2 000 linhas é aprovada
|
|
383
|
-
// listando tudo, e o implementador entrega exatamente o critério.
|
|
257
|
+
// Sem o checklist de tela completo, paginação, ordenação, filtro e os três estados não viram cenário.
|
|
384
258
|
if (signals.gate === 'uiux') {
|
|
385
259
|
const reason = uiuxChecklistPause(signals.screenChecklist);
|
|
386
260
|
if (reason) return { pause: true, reason };
|
|
387
261
|
}
|
|
388
262
|
|
|
389
|
-
//
|
|
390
|
-
// decidiu itens de tela e o avaliador não registrou a conferência deles
|
|
391
|
-
// contra a tela entregue. Cobra-se o item DEVIDO (`uncovered`): tela toda
|
|
392
|
-
// "não se aplica", mockup legado ou ausente não devem nada.
|
|
263
|
+
// Cobra-se só o item devido (`uncovered`): tela toda "não se aplica", design legado ou ausente não devem nada.
|
|
393
264
|
if (signals.gate === 'review' && (signals.screenChecklist?.uncovered?.length ?? 0) > 0) {
|
|
394
265
|
const { section, uncovered } = signals.screenChecklist;
|
|
395
266
|
if (section !== true) {
|
|
396
267
|
return {
|
|
397
268
|
pause: true,
|
|
398
|
-
reason:
|
|
399
|
-
+ ' e
|
|
269
|
+
reason: `\`4-review/verifier.md\` sem a seção \`## Checklist de tela\` — o design decidiu ${firstOf(uncovered)}`
|
|
270
|
+
+ ' e o verificador não registrou a conferência contra a tela entregue'
|
|
400
271
|
+ ' (evals/review-checklist.md, "Contrato de tela")',
|
|
401
272
|
};
|
|
402
273
|
}
|
|
@@ -407,28 +278,21 @@ export function shouldPauseGate(config, signals) {
|
|
|
407
278
|
};
|
|
408
279
|
}
|
|
409
280
|
|
|
410
|
-
if (signals.gate === 'plan'
|
|
411
|
-
const
|
|
412
|
-
return {
|
|
413
|
-
pause: true,
|
|
414
|
-
reason: `task grande demais para um dispatch: ${list} outputs — fatie por pós-condição`,
|
|
415
|
-
};
|
|
281
|
+
if (signals.gate === 'plan') {
|
|
282
|
+
const reason = planChecksPause(signals.planChecks);
|
|
283
|
+
if (reason) return { pause: true, reason };
|
|
416
284
|
}
|
|
417
285
|
|
|
418
|
-
if (typeof signals.
|
|
286
|
+
if (typeof signals.sliceCount === 'number' && signals.sliceCount > MAX_SLICES) {
|
|
419
287
|
return {
|
|
420
288
|
pause: true,
|
|
421
|
-
reason:
|
|
289
|
+
reason: `${signals.sliceCount} fatias, acima de ${MAX_SLICES} — o plano é grande demais para uma feature`,
|
|
422
290
|
};
|
|
423
291
|
}
|
|
424
292
|
|
|
425
293
|
return { pause: false, reason: '' };
|
|
426
294
|
}
|
|
427
295
|
|
|
428
|
-
/**
|
|
429
|
-
* Human-readable list of risk signals present (for logging/Gate summary).
|
|
430
|
-
* @returns {string[]}
|
|
431
|
-
*/
|
|
432
296
|
export function getRiskSignals(signals) {
|
|
433
297
|
const out = [];
|
|
434
298
|
if (FORCED_PAUSE_WORK_TYPES.includes(signals.workType)) out.push('hotfix (gate humano obrigatório)');
|
|
@@ -437,25 +301,19 @@ export function getRiskSignals(signals) {
|
|
|
437
301
|
out.push(`standards violations: ${signals.standardsViolations.join(', ')}`);
|
|
438
302
|
}
|
|
439
303
|
if (signals.verificationStatus === 'fail') out.push('verify vermelho');
|
|
440
|
-
// Listado mesmo quando
|
|
441
|
-
// indistinguível de uma omissão. A classe diz de quem é a ausência de prova.
|
|
304
|
+
// Listado mesmo quando não pausa: dispensa silenciosa é indistinguível de omissão.
|
|
442
305
|
if (signals.gate === 'review' && signals.e2eStatus === 'skip') {
|
|
443
306
|
const klass = classifyE2eSkip(signals.e2eReason);
|
|
444
307
|
const literal = typeof signals.e2eReason === 'string' && signals.e2eReason.trim() !== ''
|
|
445
308
|
? signals.e2eReason.trim()
|
|
446
309
|
: 'sem motivo registrado';
|
|
447
|
-
|
|
448
|
-
? signals.verificationScope.trim()
|
|
449
|
-
: 'escopo ausente';
|
|
450
|
-
out.push(`e2e pulado [${klass}] (stamp de ${where}): ${literal}`);
|
|
310
|
+
out.push(`e2e pulado [${klass}]: ${literal}`);
|
|
451
311
|
}
|
|
452
312
|
if (signals.gate === 'review' && signals.verificationStale === true) {
|
|
453
313
|
out.push('verification stamp desatualizado (stale) no ship');
|
|
454
314
|
}
|
|
455
|
-
if (signals.gate === 'review') out.push(
|
|
456
|
-
// Listado mesmo quando
|
|
457
|
-
// declara caminho de prompt/modelo é indistinguível, no relatório, de um que
|
|
458
|
-
// declara e não tocou em nada — e as duas coisas significam coisas diferentes.
|
|
315
|
+
if (signals.gate === 'review') out.push(reviewSummary(signals.reviewReports));
|
|
316
|
+
// Listado mesmo quando não pausa, pela mesma razão do e2e.
|
|
459
317
|
if (signals.gate === 'review' && signals.promptModel) {
|
|
460
318
|
const { declared, touched } = signals.promptModel;
|
|
461
319
|
if (!declared) {
|
|
@@ -464,23 +322,18 @@ export function getRiskSignals(signals) {
|
|
|
464
322
|
out.push(`prompt/modelo tocado (${touched.length}) com eval ${signals.evalsStatus ?? 'não declarado'}`);
|
|
465
323
|
}
|
|
466
324
|
}
|
|
467
|
-
if (signals.gate === 'plan' &&
|
|
468
|
-
out.push(`
|
|
325
|
+
if (signals.gate === 'plan' && signals.planChecks && signals.planChecks !== 'fresh') {
|
|
326
|
+
out.push(`checks.json ${signals.planChecks}`);
|
|
469
327
|
}
|
|
470
|
-
if (typeof signals.
|
|
471
|
-
out.push(`${signals.
|
|
328
|
+
if (typeof signals.sliceCount === 'number' && signals.sliceCount > MAX_SLICES) {
|
|
329
|
+
out.push(`${signals.sliceCount} fatias (acima de ${MAX_SLICES})`);
|
|
472
330
|
}
|
|
473
|
-
// Listado mesmo quando NÃO pausa, pela mesma razão do sinal irmão: a isenção
|
|
474
|
-
// de uma feature que não gera mídia tem de ser VISÍVEL, senão é
|
|
475
|
-
// indistinguível de uma omissão.
|
|
476
331
|
if (signals.gate === 'review' && signals.mediaClaims?.featureGeneratesMedia === true) {
|
|
477
332
|
out.push(signals.mediaClaims.section === true
|
|
478
333
|
? 'prova de mídia registrada'
|
|
479
|
-
: '
|
|
334
|
+
: 'verifier.md sem a seção `## Mídia gerada` (prova de mídia ausente)');
|
|
480
335
|
}
|
|
481
336
|
|
|
482
|
-
// Listado mesmo quando NÃO pausa: a isenção (sem mockup, legado, nada
|
|
483
|
-
// devido) e o checklist conferido precisam ser distinguíveis de uma omissão.
|
|
484
337
|
if ((signals.gate === 'uiux' || signals.gate === 'review') && signals.screenChecklist) {
|
|
485
338
|
const { mockups, legacy, exempt, screens = [], problems = [], section, uncovered = [] } = signals.screenChecklist;
|
|
486
339
|
if (!mockups) {
|
|
@@ -491,13 +344,18 @@ export function getRiskSignals(signals) {
|
|
|
491
344
|
out.push(problems.length > 0
|
|
492
345
|
? `checklist de tela incompleto (${problems.length} problema(s))`
|
|
493
346
|
: `checklist de tela completo (${screens.length} tela(s))`);
|
|
347
|
+
const prototypes = signals.screenChecklist.prototypes ?? [];
|
|
348
|
+
if (screens.length > 0) {
|
|
349
|
+
out.push(prototypes.length > 0
|
|
350
|
+
? `protótipo incompleto em ${prototypes.length} tela(s)`
|
|
351
|
+
: `protótipo e prints 1280/390 presentes (${screens.length} tela(s))`);
|
|
352
|
+
}
|
|
494
353
|
} else if (uncovered.length > 0) {
|
|
495
354
|
out.push(section !== true
|
|
496
|
-
? '
|
|
355
|
+
? 'verifier.md sem a seção `## Checklist de tela`'
|
|
497
356
|
: `checklist de tela sem conferência em ${uncovered.length} tela(s)`);
|
|
498
357
|
} else if (screens.length === 0) {
|
|
499
|
-
//
|
|
500
|
-
// não pode ler igual a uma tela toda "não se aplica".
|
|
358
|
+
// Sem tela nenhuma: nada a cobrar no ship, mas não pode ler igual a uma tela toda "não se aplica".
|
|
501
359
|
out.push(`checklist de tela: ${firstOf(problems)}`);
|
|
502
360
|
} else if (section === true) {
|
|
503
361
|
out.push(`checklist de tela conferido (${screens.length} tela(s))`);
|
|
@@ -510,7 +368,6 @@ export function getRiskSignals(signals) {
|
|
|
510
368
|
}
|
|
511
369
|
|
|
512
370
|
export const RISK_CONSTANTS = {
|
|
513
|
-
SCOPE_TASK_THRESHOLD,
|
|
514
371
|
FORCED_PAUSE_WORK_TYPES,
|
|
515
372
|
E2E_SKIP_CLASSES_THAT_PAUSE,
|
|
516
373
|
};
|