@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,83 +1,63 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { execFileSync } from 'child_process';
|
|
4
|
-
import { existsSync
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
6
|
import { getFeature, setApprovalGate, getApprovalGate, getApprovalHistory, derivePhase } from '../../core/state/state-manager.js';
|
|
7
7
|
import { unmetPrereqs } from '../../core/state/gate-order.js';
|
|
8
|
-
import {
|
|
8
|
+
import { reviewReports, reviewFileName, REVIEW_ROLES, VERDICT_TOKEN, REVIEWED_SHA_TOKEN } from '../../lib/review-reports.js';
|
|
9
9
|
import { shouldPauseGate } from '../../lib/risk-detector.js';
|
|
10
|
-
import { collectSignals } from './gate-check.js';
|
|
10
|
+
import { collectSignals, collectReviewRequirement } from './gate-check.js';
|
|
11
|
+
import { autoApprovalRefusal, printRefusal } from './auto-approval.js';
|
|
11
12
|
import { readTrustFromConfig } from './gate-decision.js';
|
|
12
13
|
import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
|
|
13
|
-
import { uiuxAutoApprovalBlock } from '../../lib/specs/screen-checklist.js';
|
|
14
14
|
import { treeMovedSince } from '../../lib/git/evidence-staleness.js';
|
|
15
|
+
import { seedDotnetBuildIgnores } from '../../lib/stack/dotnet-build-ignores.js';
|
|
16
|
+
import { reportDotnetIgnores } from './advance.js';
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* and the `morph-review` skill can never drift apart (the A5 lesson).
|
|
20
|
-
*/
|
|
21
|
-
export const REVIEWED_SHA_TOKEN = '**Reviewed-SHA:**';
|
|
22
|
-
|
|
23
|
-
const REVIEWED_SHA_RE = /\*\*Reviewed-SHA:\*\*\s*([0-9a-f]{7,40})/i;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Is the feature's `review-report.md` still describing the current tree?
|
|
27
|
-
*
|
|
28
|
-
* @param {string} featureName
|
|
29
|
-
* @returns {{ status: 'ok'|'stale'|'unstamped'|'skip', reviewed?: string, head?: string, ahead?: number }}
|
|
30
|
-
* `skip` covers every case where the question cannot be answered honestly:
|
|
31
|
-
* no report, no git repo, an unresolvable SHA. Never a false refusal.
|
|
32
|
-
*/
|
|
33
|
-
function reviewStaleness(featureName) {
|
|
34
|
-
const cwd = process.cwd();
|
|
35
|
-
const reportPath = join(cwd, '.morph', 'features', featureName, '4-review', 'review-report.md');
|
|
36
|
-
if (!existsSync(reportPath)) return { status: 'skip' };
|
|
18
|
+
export { REVIEWED_SHA_TOKEN };
|
|
19
|
+
|
|
20
|
+
const SIGNED_OFF = { required: [], reports: {}, checks: { status: 'ok', total: 0, missing: [], notProven: [] } };
|
|
37
21
|
|
|
22
|
+
// The worst answer across every report present wins. `skip` (no verifier report, no git) never
|
|
23
|
+
// refuses falsely.
|
|
24
|
+
function reviewStaleness(reports) {
|
|
25
|
+
if (reports.verifier.status === 'missing') return { status: 'skip' };
|
|
26
|
+
const cwd = process.cwd();
|
|
38
27
|
let head;
|
|
39
28
|
try {
|
|
40
29
|
head = execFileSync('git', ['rev-parse', 'HEAD'], { cwd, stdio: 'pipe' }).toString().trim();
|
|
41
|
-
} catch {
|
|
42
|
-
return { status: 'skip' }; // not a git repo (or no commits) — nothing to compare
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
let body = '';
|
|
46
|
-
try {
|
|
47
|
-
body = readFileSync(reportPath, 'utf8');
|
|
48
30
|
} catch {
|
|
49
31
|
return { status: 'skip' };
|
|
50
32
|
}
|
|
51
33
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
34
|
+
let unstamped = null;
|
|
35
|
+
for (const [role, report] of Object.entries(reports)) {
|
|
36
|
+
if (report.status === 'missing') continue;
|
|
37
|
+
const file = reviewFileName(role);
|
|
38
|
+
let reviewed = null;
|
|
39
|
+
try {
|
|
40
|
+
if (report.reviewedSha) {
|
|
41
|
+
reviewed = execFileSync('git', ['rev-parse', '--verify', `${report.reviewedSha}^{commit}`], { cwd, stdio: 'pipe' })
|
|
42
|
+
.toString().trim();
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
reviewed = null;
|
|
46
|
+
}
|
|
47
|
+
if (!reviewed) {
|
|
48
|
+
unstamped ??= { status: 'unstamped', file, head };
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (!treeMovedSince(cwd, reviewed, head)) continue;
|
|
52
|
+
let ahead = 0;
|
|
53
|
+
try {
|
|
54
|
+
ahead = Number(execFileSync('git', ['rev-list', '--count', `${reviewed}..${head}`], { cwd, stdio: 'pipe' }).toString().trim()) || 0;
|
|
55
|
+
} catch { ahead = 0; }
|
|
56
|
+
return { status: 'stale', file, reviewed, head, ahead };
|
|
63
57
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
let ahead = 0;
|
|
67
|
-
try {
|
|
68
|
-
ahead = execFileSync('git', ['rev-list', '--count', `${reviewed}..${head}`], { cwd, stdio: 'pipe' })
|
|
69
|
-
.toString().trim();
|
|
70
|
-
ahead = Number(ahead) || 0;
|
|
71
|
-
} catch { ahead = 0; }
|
|
72
|
-
|
|
73
|
-
// HEAD not descending from the reviewed commit (a rebase, a different branch)
|
|
74
|
-
// is still "not the tree that was reviewed".
|
|
75
|
-
return { status: 'stale', reviewed, head, ahead };
|
|
58
|
+
return unstamped ?? { status: 'ok', head };
|
|
76
59
|
}
|
|
77
60
|
|
|
78
|
-
/**
|
|
79
|
-
* Unapprove action function - Revoke a previously approved gate
|
|
80
|
-
*/
|
|
81
61
|
export async function unapproveCommand(featureName, gateName, options = {}) {
|
|
82
62
|
try {
|
|
83
63
|
if (!VALID_GATES.includes(gateName)) {
|
|
@@ -106,8 +86,7 @@ export async function unapproveCommand(featureName, gateName, options = {}) {
|
|
|
106
86
|
return;
|
|
107
87
|
}
|
|
108
88
|
|
|
109
|
-
//
|
|
110
|
-
// the gate when there's no interactive shell behind the invocation.
|
|
89
|
+
// Don't pretend a human revoked the gate without an interactive shell behind the call.
|
|
111
90
|
let revokedBy;
|
|
112
91
|
if (options.revoker) {
|
|
113
92
|
revokedBy = options.revoker;
|
|
@@ -134,44 +113,25 @@ export async function unapproveCommand(featureName, gateName, options = {}) {
|
|
|
134
113
|
}
|
|
135
114
|
}
|
|
136
115
|
|
|
137
|
-
/**
|
|
138
|
-
* Approval Commands - Manage approval gates for MORPH workflow
|
|
139
|
-
*
|
|
140
|
-
* Commands:
|
|
141
|
-
* - approve: Mark a gate as approved
|
|
142
|
-
* - unapprove: Revoke a gate approval
|
|
143
|
-
* - approval-status: Show approval status for all gates
|
|
144
|
-
*/
|
|
145
|
-
|
|
146
116
|
const program = new Command();
|
|
147
117
|
|
|
148
|
-
|
|
149
|
-
* Valid approval gate names
|
|
150
|
-
*/
|
|
151
|
-
const VALID_GATES = ['proposal', 'uiux', 'plan', 'review'];
|
|
118
|
+
const VALID_GATES = ['plan', 'uiux', 'review'];
|
|
152
119
|
|
|
153
|
-
/**
|
|
154
|
-
* Approve action function - Mark a gate as approved
|
|
155
|
-
*/
|
|
156
120
|
export async function approveCommand(featureName, gateName, options = {}) {
|
|
157
121
|
try {
|
|
158
|
-
// Validate gate name
|
|
159
122
|
if (!VALID_GATES.includes(gateName)) {
|
|
160
123
|
console.error(chalk.red(`\n❌ Invalid gate: ${gateName}`));
|
|
161
124
|
console.log(chalk.gray(`Valid gates: ${VALID_GATES.join(', ')}\n`));
|
|
162
125
|
process.exit(1);
|
|
163
126
|
}
|
|
164
127
|
|
|
165
|
-
// Get feature state
|
|
166
128
|
const feature = getFeature(featureName);
|
|
167
129
|
if (!feature) {
|
|
168
130
|
console.error(chalk.red(`\n❌ Feature not found: ${featureName}\n`));
|
|
169
131
|
process.exit(1);
|
|
170
132
|
}
|
|
171
133
|
|
|
172
|
-
//
|
|
173
|
-
// never stamp a gate onto the root's birth-instant skeleton (branch-drift
|
|
174
|
-
// created by this command, not merely left behind by one).
|
|
134
|
+
// A live worktree elsewhere owns the state: never stamp a gate onto the root skeleton.
|
|
175
135
|
const owner = assertOwnerTree(featureName, process.cwd());
|
|
176
136
|
if (owner.blocked) {
|
|
177
137
|
for (const { tone, text } of ownerTreeRefusalLines(featureName, owner.tree, owner.branch, `morph-spec approve ${featureName} ${gateName}`)) {
|
|
@@ -180,26 +140,18 @@ export async function approveCommand(featureName, gateName, options = {}) {
|
|
|
180
140
|
process.exit(1);
|
|
181
141
|
}
|
|
182
142
|
|
|
183
|
-
// Check if already approved
|
|
184
143
|
const currentStatus = getApprovalGate(featureName, gateName);
|
|
185
144
|
if (currentStatus?.approved) {
|
|
186
145
|
console.log(chalk.yellow(`\n⚠️ Gate "${gateName}" is already approved`));
|
|
187
146
|
console.log(chalk.gray(`Approved at: ${currentStatus.timestamp}`));
|
|
188
147
|
console.log(chalk.gray(`Approved by: ${currentStatus.approvedBy || 'Unknown'}`));
|
|
189
|
-
//
|
|
190
|
-
// `--force` does NOT cover it (that flag only covers gate ORDER). A
|
|
191
|
-
// caller who meant to re-approve therefore reads success and is left
|
|
192
|
-
// with nowhere to go. The way out already exists as `unapprove`; the
|
|
193
|
-
// message just never named it. Naming it is the whole fix (no new flag).
|
|
148
|
+
// `--force` does not cover this no-op (it only covers gate ORDER), so name the way out.
|
|
194
149
|
console.log(chalk.gray(`To approve it again, revoke it first: morph-spec unapprove ${featureName} ${gateName}\n`));
|
|
150
|
+
if (gateName === 'plan') reportDotnetIgnores(seedDotnetBuildIgnores(process.cwd(), { featureName }));
|
|
195
151
|
return;
|
|
196
152
|
}
|
|
197
153
|
|
|
198
|
-
// Gate monotonicity:
|
|
199
|
-
// approved (proposal → plan → review; uiux requires proposal). Prevents the
|
|
200
|
-
// lead-termometro shape — plan.approved with proposal pending. Policy-stamped
|
|
201
|
-
// gates count as approved, so chore/hotfix satisfy prereqs naturally.
|
|
202
|
-
// Escape via --force for deliberate recovery.
|
|
154
|
+
// Gate monotonicity: predecessors first; policy stamps count (`--force` to recover).
|
|
203
155
|
const unmet = unmetPrereqs(gateName, feature.approvalGates);
|
|
204
156
|
if (unmet.length && !options.force) {
|
|
205
157
|
console.error(chalk.red(`\n❌ Cannot approve "${gateName}" — prerequisite gate(s) not approved: ${unmet.join(', ')}.`));
|
|
@@ -207,22 +159,8 @@ export async function approveCommand(featureName, gateName, options = {}) {
|
|
|
207
159
|
process.exit(1);
|
|
208
160
|
}
|
|
209
161
|
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
// approvedBy resolution (most explicit wins):
|
|
214
|
-
// 1. --approver flag (CLI caller signalled who approved)
|
|
215
|
-
// 2. --mode auto → "auto" (intentionally non-human signature)
|
|
216
|
-
// 3. interactive shell (TTY on stdin) → $USER / $USERNAME (a real
|
|
217
|
-
// human is at the terminal; their name is the honest answer)
|
|
218
|
-
// 4. non-interactive (no TTY) → "auto"
|
|
219
|
-
//
|
|
220
|
-
// The TTY check exists because morph-spec is increasingly invoked
|
|
221
|
-
// from headless contexts (claude -p subprocesses, CI, the
|
|
222
|
-
// morph-e2e-tester). Stamping the host's $USERNAME on a gate that
|
|
223
|
-
// no human actually approved is misleading — it lies to the audit
|
|
224
|
-
// trail. "auto" reads as what it is: a deterministic policy gate,
|
|
225
|
-
// not a human signing off.
|
|
162
|
+
// Resolved BEFORE the hotfix backstop, which gates on the signature. Without a TTY the answer
|
|
163
|
+
// is "auto": stamping $USERNAME on a gate no human approved lies to the audit trail.
|
|
226
164
|
let approvedBy;
|
|
227
165
|
if (options.approver) {
|
|
228
166
|
approvedBy = options.approver;
|
|
@@ -234,119 +172,57 @@ export async function approveCommand(featureName, gateName, options = {}) {
|
|
|
234
172
|
approvedBy = 'auto';
|
|
235
173
|
}
|
|
236
174
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
// FORCED_PAUSE_WORK_TYPES), enforced server-side rather than trusting the
|
|
241
|
-
// calling skill. It gates on the RESOLVED approvedBy, NOT the raw --mode
|
|
242
|
-
// string: every headless context (claude -p, CI, the Bash tool) runs
|
|
243
|
-
// without a TTY, so an omitted --mode — or any --mode ≠ 'auto' — still
|
|
244
|
-
// resolves to "auto" and would otherwise rubber-stamp the gate. A human
|
|
245
|
-
// approval is --approver <name> or an interactive TTY ($USER); anything
|
|
246
|
-
// resolving to "auto" is refused here.
|
|
247
|
-
if (gateName === 'review' && feature.workType === 'hotfix' && approvedBy === 'auto') {
|
|
248
|
-
console.error(chalk.red('\n❌ Hotfix exige aprovação humana no Gate 3 — trust:auto não se aplica.'));
|
|
249
|
-
console.error(chalk.gray(' Aprove com --approver <nome>, num terminal interativo, ou pela resposta humana "aprovado" (state-sync) ao review-report via AskUserQuestion.\n'));
|
|
175
|
+
const refusal = autoApprovalRefusal({ cwd: process.cwd(), featureName, feature, gate: gateName, approvedBy });
|
|
176
|
+
if (refusal) {
|
|
177
|
+
printRefusal(chalk, refusal);
|
|
250
178
|
process.exit(1);
|
|
251
179
|
}
|
|
252
180
|
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
// claiming 976/976 tests (they were 982) and never mentioning T3. Nothing
|
|
257
|
-
// detected that the review artifact had fallen behind the code it reviews.
|
|
258
|
-
//
|
|
259
|
-
// The report stamps the SHA it reviewed; approval compares it with HEAD.
|
|
260
|
-
// A stamp that is BEHIND HEAD refuses. A MISSING stamp only warns — reports
|
|
261
|
-
// written before this existed must not brick a legacy feature — but the
|
|
262
|
-
// warning names the exact token to add, so it teaches instead of nagging.
|
|
181
|
+
// A review reviews a TREE: a `Reviewed-SHA` behind HEAD refuses. A missing stamp only warns,
|
|
182
|
+
// naming the token to add, so a legacy report does not brick the feature.
|
|
183
|
+
const reports = gateName === 'review' ? reviewReports(process.cwd(), featureName) : null;
|
|
263
184
|
if (gateName === 'review') {
|
|
264
|
-
const stale = reviewStaleness(
|
|
185
|
+
const stale = reviewStaleness(reports);
|
|
265
186
|
if (stale.status === 'stale') {
|
|
266
|
-
console.error(chalk.red(
|
|
187
|
+
console.error(chalk.red(`\n❌ O \`4-review/${stale.file}\` está desatualizado em relação ao código.`));
|
|
267
188
|
console.error(chalk.gray(` Revisado em: ${stale.reviewed.slice(0, 8)}`));
|
|
268
189
|
console.error(chalk.gray(` HEAD atual: ${stale.head.slice(0, 8)}${stale.ahead ? ` (${stale.ahead} commit(s) depois da revisão)` : ''}`));
|
|
269
190
|
console.error(chalk.gray(' Um review revisa uma ÁRVORE, e a árvore andou. Re-rode a `morph-review`'));
|
|
270
|
-
console.error(chalk.gray(
|
|
191
|
+
console.error(chalk.gray(` e atualize o \`${REVIEWED_SHA_TOKEN}\` de cada relatório.\n`));
|
|
271
192
|
process.exit(1);
|
|
272
193
|
}
|
|
273
194
|
if (stale.status === 'unstamped') {
|
|
274
|
-
console.log(chalk.yellow(
|
|
275
|
-
console.log(chalk.gray(` Adicione a linha
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Skipped-pause backstop: a gate whose recorded decision demanded a human
|
|
280
|
-
// pause (gateDecisions[gate].pause === true) cannot then be stamped by a
|
|
281
|
-
// non-human signature. The detection already existed — in gate-guard.js —
|
|
282
|
-
// but that hook is registered for `Write|Edit|AskUserQuestion`, and a Bash
|
|
283
|
-
// tool call fires no hook at all. Since MORPH.md requires gate approval to
|
|
284
|
-
// go through THIS command and forbids hand-editing the state, the only path
|
|
285
|
-
// the hook could ever see was the one the charter bans. The check belongs
|
|
286
|
-
// where the stamp is applied, not beside it.
|
|
287
|
-
//
|
|
288
|
-
// Gated on the RESOLVED `approvedBy`, matching the hotfix backstop above:
|
|
289
|
-
// an omitted --mode in a headless context also resolves to "auto" and must
|
|
290
|
-
// not slip through. `--force` does NOT bypass this either — it is the
|
|
291
|
-
// out-of-order recovery flag, a different concern. The way out is a human
|
|
292
|
-
// signature, or rewriting the decision that asked for the pause.
|
|
293
|
-
const decision = feature.gateDecisions?.[gateName];
|
|
294
|
-
if (decision?.pause === true && approvedBy === 'auto') {
|
|
295
|
-
console.error(chalk.red(`\n❌ O Gate "${gateName}" registrou pause:true — auto-aprovação não se aplica.`));
|
|
296
|
-
console.error(chalk.gray(` Sinal: ${decision.reason || 'risco detectado'}`));
|
|
297
|
-
console.error(chalk.gray(' A decisão gravada exigia pausa humana. Aprove com --approver <nome>, num'));
|
|
298
|
-
console.error(chalk.gray(' terminal interativo, ou pela resposta humana via AskUserQuestion (state-sync).'));
|
|
299
|
-
console.error(chalk.gray(' Se o sinal de risco já não vale, regrave a decisão:'));
|
|
300
|
-
console.error(chalk.gray(` morph-spec gate-decision ${featureName} ${gateName} --pause false --reason "<motivo>"\n`));
|
|
301
|
-
process.exit(1);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// The same backstop when no gate-check ran: the pause it WOULD record for
|
|
305
|
-
// the screen checklist, by the predicate advance.js reads too. `--force`
|
|
306
|
-
// does not bypass it, for the reason above.
|
|
307
|
-
if (gateName === 'uiux' && approvedBy === 'auto') {
|
|
308
|
-
const block = uiuxAutoApprovalBlock(process.cwd(), featureName, feature);
|
|
309
|
-
if (block) {
|
|
310
|
-
console.error(chalk.red(`\n❌ O Gate "uiux" não pode ser auto-aprovado — ${block}.`));
|
|
311
|
-
console.error(chalk.gray(' Complete o checklist (ver a skill morph-uiux) e rode de novo, aprove com'));
|
|
312
|
-
console.error(chalk.gray(' --approver <nome>, ou, se a pausa já foi decidida, grave a decisão:'));
|
|
313
|
-
console.error(chalk.gray(` morph-spec gate-decision ${featureName} uiux --pause false --reason "<motivo>"\n`));
|
|
314
|
-
process.exit(1);
|
|
195
|
+
console.log(chalk.yellow(`\n⚠️ O \`${stale.file}\` não carrega \`${REVIEWED_SHA_TOKEN}\` — impossível saber se ele revisa o código atual.`));
|
|
196
|
+
console.log(chalk.gray(` Adicione a linha \`${REVIEWED_SHA_TOKEN} ${stale.head}\` ao relatório para que o Gate 3 possa verificar.`));
|
|
315
197
|
}
|
|
316
198
|
}
|
|
317
199
|
|
|
318
200
|
let selfAssessed = false;
|
|
319
201
|
if (gateName === 'review') {
|
|
320
202
|
if (feature.workType === 'hotfix') {
|
|
321
|
-
// Hotfix
|
|
322
|
-
// (
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
// a degradation for a report that was never supposed to exist). The human
|
|
328
|
-
// signature is already enforced above (line ~215) — this only checks the
|
|
329
|
-
// artifact the skill actually produces.
|
|
330
|
-
const reviewReport = join(process.cwd(), '.morph', 'features', featureName, '4-review', 'review-report.md');
|
|
331
|
-
if (!existsSync(reviewReport)) {
|
|
332
|
-
console.error(chalk.red('\n❌ Gate 3 de hotfix exige `4-review/review-report.md` — não existe.'));
|
|
333
|
-
console.error(chalk.gray(' É o artefato que a `morph-hotfix` produz (diff do fix + evidência real de'));
|
|
334
|
-
console.error(chalk.gray(' teste do `morph-spec verify`), não o `evaluator-report.md` do avaliador'));
|
|
335
|
-
console.error(chalk.gray(' independente — hotfix nunca despacha o avaliador. Escreva o relatório antes'));
|
|
203
|
+
// Hotfix never dispatches: the orchestrator writes `verifier.md` itself
|
|
204
|
+
// (diff + real verify evidence); the human signature above decides.
|
|
205
|
+
if (reports.verifier.status === 'missing') {
|
|
206
|
+
console.error(chalk.red('\n❌ Gate 3 de hotfix exige `4-review/verifier.md` — não existe.'));
|
|
207
|
+
console.error(chalk.gray(' No hotfix o orquestrador o escreve (diff do fix + evidência real do'));
|
|
208
|
+
console.error(chalk.gray(' `morph-spec verify`) sem despachar ninguém. Escreva o relatório antes'));
|
|
336
209
|
console.error(chalk.gray(' de aprovar; a assinatura humana já é exigida pelo backstop acima.\n'));
|
|
337
210
|
process.exit(1);
|
|
338
211
|
}
|
|
339
212
|
} else {
|
|
340
|
-
const
|
|
341
|
-
|
|
213
|
+
const { required } = collectReviewRequirement(process.cwd(), feature);
|
|
214
|
+
const unusable = required.filter((role) => reports[role].status !== 'ok');
|
|
215
|
+
if (unusable.length) {
|
|
342
216
|
if (!options.selfAssessed) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
217
|
+
console.error(chalk.red(`\n❌ Gate 3 exige ${required.map((r) => `\`4-review/${reviewFileName(r)}\``).join(', ')}.`));
|
|
218
|
+
for (const role of unusable) {
|
|
219
|
+
const { status } = reports[role];
|
|
220
|
+
const what = status === 'missing' ? 'não existe'
|
|
221
|
+
: status === 'partial' ? 'está PARCIAL — retome o mesmo agente por SendMessage'
|
|
222
|
+
: `não tem \`${VERDICT_TOKEN} SHIP|FIX\``;
|
|
223
|
+
console.error(chalk.gray(` ${reviewFileName(role)} ${what}.`));
|
|
348
224
|
}
|
|
349
|
-
console.error(chalk.gray(
|
|
225
|
+
console.error(chalk.gray(` Os relatórios vêm de ${unusable.map((r) => `\`${REVIEW_ROLES[r].persona}\``).join(', ')} em contexto fresco, nunca de quem implementou.`));
|
|
350
226
|
console.error(chalk.gray(' Se o dispatch for impossível nesta sessão, aprove com --self-assessed —'));
|
|
351
227
|
console.error(chalk.gray(' a degradação fica registrada no feature.json e aparece no `list`/`fleet`.\n'));
|
|
352
228
|
process.exit(1);
|
|
@@ -354,12 +230,11 @@ export async function approveCommand(featureName, gateName, options = {}) {
|
|
|
354
230
|
selfAssessed = true;
|
|
355
231
|
}
|
|
356
232
|
|
|
357
|
-
// Sem gate-check prévio não há `gateDecisions.review` para o backstop
|
|
358
|
-
//
|
|
359
|
-
// a decisão pela MESMA régua do gate-check. --self-assessed já dispensou o veredito.
|
|
233
|
+
// Sem gate-check prévio não há `gateDecisions.review` para o backstop morder: a auto-aprovação
|
|
234
|
+
// recalcula a decisão pela MESMA régua do gate-check. --self-assessed já dispensou os relatórios.
|
|
360
235
|
if (approvedBy === 'auto') {
|
|
361
236
|
const signals = collectSignals(featureName, feature, process.cwd(), 'review');
|
|
362
|
-
if (selfAssessed) signals.
|
|
237
|
+
if (selfAssessed) signals.reviewReports = SIGNED_OFF;
|
|
363
238
|
const { pause, reason } = shouldPauseGate({ trust: readTrustFromConfig() }, signals);
|
|
364
239
|
if (pause) {
|
|
365
240
|
console.error(chalk.red(`\n❌ Gate 3 não pode ser auto-aprovado — ${reason}.`));
|
|
@@ -368,9 +243,12 @@ export async function approveCommand(featureName, gateName, options = {}) {
|
|
|
368
243
|
}
|
|
369
244
|
}
|
|
370
245
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
246
|
+
for (const role of required) {
|
|
247
|
+
const r = reports[role];
|
|
248
|
+
if (r.status === 'ok' && r.superseded.length > 0) {
|
|
249
|
+
console.log(chalk.yellow(`\n⚠️ ${reviewFileName(role)} tem ${r.superseded.length + 1} vereditos (append-only).`));
|
|
250
|
+
console.log(chalk.gray(` Vigente: ${r.verdict}, ${r.blockers.length} bloqueador(es). Superados: ${r.superseded.join(', ')}\n`));
|
|
251
|
+
}
|
|
374
252
|
}
|
|
375
253
|
}
|
|
376
254
|
}
|
|
@@ -382,23 +260,19 @@ export async function approveCommand(featureName, gateName, options = {}) {
|
|
|
382
260
|
...(selfAssessed ? { selfAssessed: true } : {}),
|
|
383
261
|
});
|
|
384
262
|
|
|
385
|
-
//
|
|
386
|
-
// feature hands over between sessions too — `approve` is the real path
|
|
387
|
-
// Gate 2 took in the field (`approve <f> plan --mode auto`), not just
|
|
388
|
-
// `advance`, so a handoff.md left stale after approve was the common
|
|
389
|
-
// case, not the edge case. Same helper, same fail-open contract: a
|
|
390
|
-
// regeneration failure must never fail the approval itself.
|
|
263
|
+
// Approving is a session handoff too; a regeneration failure must never fail the approval.
|
|
391
264
|
try {
|
|
392
265
|
const { writeHandoff } = await import('../../lib/generators/handoff-generator.js');
|
|
393
266
|
writeHandoff(featureName, process.cwd());
|
|
394
267
|
} catch { /* fail-open */ }
|
|
395
268
|
|
|
396
269
|
if (selfAssessed) {
|
|
397
|
-
console.log(chalk.yellow('\n⚠️ Gate 3 aprovado SEM
|
|
270
|
+
console.log(chalk.yellow('\n⚠️ Gate 3 aprovado SEM os relatórios de review independentes (--self-assessed).'));
|
|
398
271
|
console.log(chalk.gray(' Registrado em feature.json → approvalGates.review.selfAssessed.'));
|
|
399
272
|
}
|
|
400
273
|
|
|
401
274
|
console.log(chalk.green(`\n✅ Gate "${gateName}" approved for feature: ${featureName}`));
|
|
275
|
+
if (gateName === 'plan') reportDotnetIgnores(seedDotnetBuildIgnores(process.cwd(), { featureName }));
|
|
402
276
|
console.log(chalk.gray(`Workflow can now proceed past ${gateName} phase\n`));
|
|
403
277
|
|
|
404
278
|
} catch (error) {
|
|
@@ -407,28 +281,20 @@ export async function approveCommand(featureName, gateName, options = {}) {
|
|
|
407
281
|
}
|
|
408
282
|
}
|
|
409
283
|
|
|
410
|
-
/**
|
|
411
|
-
* Approve command - Mark a gate as approved
|
|
412
|
-
*/
|
|
413
284
|
program
|
|
414
285
|
.command('approve <feature> <gate>')
|
|
415
286
|
.description('Approve a phase gate to allow workflow progression')
|
|
416
287
|
.action(approveCommand);
|
|
417
288
|
|
|
418
|
-
/**
|
|
419
|
-
* Approval status action function - Show all gates and their status
|
|
420
|
-
*/
|
|
421
289
|
export async function approvalStatusCommand(featureName, options = {}) {
|
|
422
290
|
try {
|
|
423
|
-
// Get feature state
|
|
424
291
|
const feature = getFeature(featureName);
|
|
425
292
|
if (!feature) {
|
|
426
293
|
console.error(chalk.red(`\n❌ Feature not found: ${featureName}\n`));
|
|
427
294
|
process.exit(1);
|
|
428
295
|
}
|
|
429
296
|
|
|
430
|
-
//
|
|
431
|
-
// approved gates (same as `morph-spec status`), never print "unknown".
|
|
297
|
+
// Phase derived from the filesystem + approved gates, never "unknown".
|
|
432
298
|
const featurePath = join(process.cwd(), '.morph', 'features', featureName);
|
|
433
299
|
const phase = feature.phase || derivePhase(featurePath);
|
|
434
300
|
|
|
@@ -437,7 +303,6 @@ export async function approvalStatusCommand(featureName, options = {}) {
|
|
|
437
303
|
const uiuxGate = getApprovalGate(featureName, 'uiux');
|
|
438
304
|
const uiuxTaken = uiuxGate?.approved === true || existsSync(join(featurePath, '1-design'));
|
|
439
305
|
|
|
440
|
-
// JSON output
|
|
441
306
|
if (options.json) {
|
|
442
307
|
const status = {
|
|
443
308
|
feature: featureName,
|
|
@@ -458,11 +323,9 @@ export async function approvalStatusCommand(featureName, options = {}) {
|
|
|
458
323
|
console.log(chalk.bold(`\n📋 Approval Status for: ${featureName}`));
|
|
459
324
|
console.log('━'.repeat(60));
|
|
460
325
|
|
|
461
|
-
// Show current phase
|
|
462
326
|
console.log(chalk.cyan(`\nCurrent Phase: ${phase}`));
|
|
463
327
|
console.log(chalk.gray(`Feature Status: ${feature.status || 'unknown'}\n`));
|
|
464
328
|
|
|
465
|
-
// Show each gate
|
|
466
329
|
console.log(chalk.bold('Approval Gates:'));
|
|
467
330
|
|
|
468
331
|
for (const gateName of VALID_GATES) {
|
|
@@ -504,7 +367,6 @@ export async function approvalStatusCommand(featureName, options = {}) {
|
|
|
504
367
|
}
|
|
505
368
|
}
|
|
506
369
|
|
|
507
|
-
// Show approval history if available
|
|
508
370
|
const history = getApprovalHistory(featureName);
|
|
509
371
|
if (history && history.length > 0) {
|
|
510
372
|
console.log(chalk.bold('\nApproval History:'));
|
|
@@ -522,16 +384,12 @@ export async function approvalStatusCommand(featureName, options = {}) {
|
|
|
522
384
|
}
|
|
523
385
|
}
|
|
524
386
|
|
|
525
|
-
/**
|
|
526
|
-
* Approval status command - Show all gates and their status
|
|
527
|
-
*/
|
|
528
387
|
program
|
|
529
388
|
.command('approval-status <feature>')
|
|
530
389
|
.alias('status')
|
|
531
390
|
.description('Show approval status for all gates')
|
|
532
391
|
.action(approvalStatusCommand);
|
|
533
392
|
|
|
534
|
-
// Only parse if run directly (not imported as module)
|
|
535
393
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
536
394
|
if (process.argv.length > 2) {
|
|
537
395
|
program.parse(process.argv);
|