@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,31 +1,12 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { mkdirSync } from 'fs';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { getFeature, setApprovalGate, updateFeature } from '../../core/state/state-manager.js';
|
|
5
5
|
import { WORK_TYPES, isValidWorkType, POLICY_APPROVER_PREFIX } from '../../core/work-types.js';
|
|
6
|
+
import { renderPlanSkeleton } from '../../lib/plan/plan-template.js';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* asymmetric gate rules that preserve GENUINE human/auto approvals and only
|
|
10
|
-
* ever undo policy stamps:
|
|
11
|
-
*
|
|
12
|
-
* - Blocked outright once Gate 3 (review) is approved — the feature already
|
|
13
|
-
* shipped its pipeline, retyping it retroactively would be revisionist.
|
|
14
|
-
* - Downgrade (fewer skipped gates → more skipped gates, e.g. feature/bug
|
|
15
|
-
* → chore/hotfix) is blocked once Gate 2 (plan) carries a genuine
|
|
16
|
-
* (non-policy) approval — the plan was already accepted under the old
|
|
17
|
-
* type's scrutiny; retyping around it would erase that judgment call.
|
|
18
|
-
* - Upgrade (more skipped gates → fewer, e.g. chore/hotfix → feature/bug)
|
|
19
|
-
* is always allowed before review: it only ever ADDS scrutiny.
|
|
20
|
-
*
|
|
21
|
-
* Applying a retype resets exactly the gates that were stamped
|
|
22
|
-
* `policy:{oldType}` and the NEW type no longer skips (so the human is asked
|
|
23
|
-
* again), stamps `policy:{newType}` on any of the new type's skippedGates
|
|
24
|
-
* that aren't already approved (a prior genuine approval is left standing —
|
|
25
|
-
* that's the "preserves human approvals" half of the contract), scaffolds
|
|
26
|
-
* any phase folders the new type needs that don't exist yet, and appends an
|
|
27
|
-
* audit entry to `workTypeHistory`.
|
|
28
|
-
*/
|
|
8
|
+
// Retype only ever undoes `policy:` stamps: blocked once review is approved; a downgrade
|
|
9
|
+
// is blocked once the plan carries a genuine approval; an upgrade only adds scrutiny.
|
|
29
10
|
export async function retypeCommand(featureName, newType, options = {}) {
|
|
30
11
|
try {
|
|
31
12
|
if (!isValidWorkType(newType)) {
|
|
@@ -61,15 +42,14 @@ export async function retypeCommand(featureName, newType, options = {}) {
|
|
|
61
42
|
const genuinelyApproved = planGate?.approved === true
|
|
62
43
|
&& !String(planGate.approvedBy || '').startsWith(POLICY_APPROVER_PREFIX);
|
|
63
44
|
if (genuinelyApproved) {
|
|
64
|
-
console.error(chalk.red(`\n❌ Gate
|
|
45
|
+
console.error(chalk.red(`\n❌ Gate do plano já aprovado por "${planGate.approvedBy}"; downgrade bloqueado — o plano já foi aceito\n`));
|
|
65
46
|
process.exit(1);
|
|
66
47
|
}
|
|
67
48
|
}
|
|
68
49
|
|
|
69
50
|
const now = new Date().toISOString();
|
|
70
51
|
|
|
71
|
-
//
|
|
72
|
-
// longer skips — a genuine (non-policy) approval is never touched.
|
|
52
|
+
// A genuine (non-policy) approval is never reset.
|
|
73
53
|
const resetGates = [];
|
|
74
54
|
for (const [gate, data] of Object.entries(feature.approvalGates || {})) {
|
|
75
55
|
const isPolicyStamped = String(data?.approvedBy || '').startsWith(POLICY_APPROVER_PREFIX);
|
|
@@ -79,8 +59,7 @@ export async function retypeCommand(featureName, newType, options = {}) {
|
|
|
79
59
|
}
|
|
80
60
|
}
|
|
81
61
|
|
|
82
|
-
//
|
|
83
|
-
// approved (preserves a prior genuine approval untouched).
|
|
62
|
+
// A gate already approved keeps its genuine approval.
|
|
84
63
|
const stampedGates = [];
|
|
85
64
|
for (const gate of newWorkType.skippedGates) {
|
|
86
65
|
if (!feature.approvalGates?.[gate]?.approved) {
|
|
@@ -93,6 +72,11 @@ export async function retypeCommand(featureName, newType, options = {}) {
|
|
|
93
72
|
}
|
|
94
73
|
|
|
95
74
|
const featureDir = join(process.cwd(), '.morph', 'features', featureName);
|
|
75
|
+
const planPath = join(featureDir, '2-plan', 'plan.md');
|
|
76
|
+
if (stampedGates.includes('plan') && existsSync(planPath)
|
|
77
|
+
&& readFileSync(planPath, 'utf8') === renderPlanSkeleton(featureName)) {
|
|
78
|
+
unlinkSync(planPath);
|
|
79
|
+
}
|
|
96
80
|
for (const folder of newWorkType.scaffold) {
|
|
97
81
|
mkdirSync(join(featureDir, folder), { recursive: true });
|
|
98
82
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { execFileSync } from 'node:child_process';
|
|
3
|
-
import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { join, resolve } from 'node:path';
|
|
5
|
-
import {
|
|
6
|
-
import { dotnetTestMode } from '../../lib/
|
|
5
|
+
import { discoverDotnetBuildTargets } from '../../lib/tasks/test-runner.js';
|
|
6
|
+
import { dotnetTestMode } from '../../lib/mutation/dotnet-introspection.js';
|
|
7
7
|
import { dotnetBuildCommands } from '../../lib/mutation/commands.js';
|
|
8
8
|
import { resolveTestNodes } from '../../lib/tasks/test-nodes.js';
|
|
9
9
|
import { changedPathsAgainst, detectDefaultBranch, getHeadSha } from '../../lib/git/default-branch.js';
|
|
@@ -12,7 +12,8 @@ import { readHeavyConfig } from '../../../framework/hooks/shared/heavy-config.js
|
|
|
12
12
|
import { resolveGitCommonDir } from '../../../framework/hooks/shared/git-paths.js';
|
|
13
13
|
import { BACKSLASH_TOKEN, ManifestError } from '../../lib/mutation/manifest.js';
|
|
14
14
|
import { groupByNode, loadPlan } from '../../lib/mutation/plan.js';
|
|
15
|
-
import { dotnetSubset, jsSubset, testProjectDirsOf } from '../../lib/mutation/subset.js';
|
|
15
|
+
import { dotnetSubset, jsSubset, mutatedTypesOf, testFilesCiting, testProjectDirsOf } from '../../lib/mutation/subset.js';
|
|
16
|
+
import { isFinished } from '../../dashboard/scanner/feature-status.js';
|
|
16
17
|
import { dotnetAdapter, genericAdapter, spawnExec } from '../../lib/mutation/adapters.js';
|
|
17
18
|
import { runMutation } from '../../lib/mutation/runner.js';
|
|
18
19
|
import { renderSection, summarize } from '../../lib/mutation/report.js';
|
|
@@ -22,11 +23,8 @@ const EXIT = { ok: 0, failed: 1, usage: 2, busy: 3, unsafe: 4 };
|
|
|
22
23
|
const KEEP_RUNS = 2;
|
|
23
24
|
const DEFAULT_JOBS = 2;
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
* are taken only if free right now. The run uses as many workers as it got,
|
|
28
|
-
* so a busy machine degrades to sequential instead of queueing.
|
|
29
|
-
*/
|
|
26
|
+
// One heavy slot per worker: the first waits, the others are taken only if free now,
|
|
27
|
+
// so a busy machine degrades to sequential instead of queueing.
|
|
30
28
|
export function workerGate({ root, feature, jobs }) {
|
|
31
29
|
const config = readHeavyConfig(root);
|
|
32
30
|
const gates = [createHeavyGate({ projectPath: root, label: 'mutate', feature, config })];
|
|
@@ -97,23 +95,72 @@ export function restoreLatest(root, out = console) {
|
|
|
97
95
|
return restored;
|
|
98
96
|
}
|
|
99
97
|
|
|
98
|
+
function testDirsOf(node, root, { projectsOnly = false } = {}) {
|
|
99
|
+
const cwd = resolve(root, node.cwd || '.');
|
|
100
|
+
const targets = discoverDotnetBuildTargets(node, cwd);
|
|
101
|
+
return testProjectDirsOf(root, projectsOnly ? targets.filter((t) => !/\.slnx?$/i.test(t)) : targets, cwd);
|
|
102
|
+
}
|
|
103
|
+
|
|
100
104
|
function subsetFor({ node, group, root, options }) {
|
|
101
105
|
if (options.subset === false) return { usable: false, reason: '--no-subset' };
|
|
102
106
|
const mutatedFiles = group.regions.map((r) => r.file);
|
|
103
107
|
if (node.runner === 'dotnet') {
|
|
104
108
|
const base = detectDefaultBranch(root);
|
|
105
109
|
const changed = base ? changedPathsAgainst(base, root) : [];
|
|
106
|
-
return dotnetSubset({ root, testProjectDirs:
|
|
110
|
+
return dotnetSubset({ root, testProjectDirs: testDirsOf(node, root), mutatedFiles, changed });
|
|
107
111
|
}
|
|
108
112
|
return jsSubset({ runner: node.runner, command: node.command, root, mutatedFiles });
|
|
109
113
|
}
|
|
110
114
|
|
|
111
|
-
|
|
115
|
+
function e2eManualProtocol(feature, node) {
|
|
116
|
+
const f = feature ?? '<feature>';
|
|
117
|
+
const round = `eval "$(morph-spec env ${f})" && morph-spec e2e up ${f} && morph-spec heavy run -- ${node.command}`;
|
|
118
|
+
return 'mutate só recompila localmente, e a suíte que alcança esse código testa o container do compose: o mutante sairia sobrevivente em falso. ' +
|
|
119
|
+
`Prove à mão pelo nó "${node.id}", cada passo numa linha só e encadeado por \`&&\` (nunca \`;\`: um passo que falha não pode deixar a suíte rodar sobre o código original; ` +
|
|
120
|
+
'e o env da feature se perde entre chamadas de Bash): ' +
|
|
121
|
+
`(1) base verde antes do 1º mutante: \`${round}\`; ` +
|
|
122
|
+
`(2) por mutante: edite a linha && \`${round}\`; ` +
|
|
123
|
+
`(3) restaure, confirme com \`git diff --quiet -- <arquivo>\` e suba a stack de novo (\`eval "$(morph-spec env ${f})" && morph-spec e2e up ${f}\`). ` +
|
|
124
|
+
'Registre cada mutante em 4-review (expect, resultado, teste que o matou).';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// A .cs file belongs to every dotnet suite, by runner, not by project reference: a unit suite
|
|
128
|
+
// "owns" code only an e2e suite executes. With an e2e node declared, a unit suite with no test
|
|
129
|
+
// naming a type of the mutated file is not evidence, so the run is refused.
|
|
130
|
+
function uncitedUnderE2e({ root, groups, nodes }) {
|
|
131
|
+
const e2e = nodes.filter((n) => n.e2e);
|
|
132
|
+
if (e2e.length === 0) return [];
|
|
133
|
+
const e2eDirs = new Set(e2e.filter((n) => n.runner === 'dotnet').flatMap((n) => testDirsOf(n, root)));
|
|
134
|
+
const uncited = [];
|
|
135
|
+
for (const { node, regions } of groups.filter((g) => g.node.runner === 'dotnet')) {
|
|
136
|
+
const direct = new Set(testDirsOf(node, root, { projectsOnly: true }));
|
|
137
|
+
const dirs = testDirsOf(node, root).filter((d) => direct.has(d) || !e2eDirs.has(d));
|
|
138
|
+
for (const region of regions) {
|
|
139
|
+
const types = mutatedTypesOf(root, [region.file]);
|
|
140
|
+
if (testFilesCiting({ root, testProjectDirs: dirs, types, exclude: [region.file] }).length === 0) uncited.push({ region, node, types, e2e });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return uncited;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function soleActiveFeature(root) {
|
|
147
|
+
const dir = join(root, '.morph', 'features');
|
|
148
|
+
let names;
|
|
149
|
+
try { names = readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name); } catch { return null; }
|
|
150
|
+
const active = names.filter((name) => {
|
|
151
|
+
try { return !isFinished(JSON.parse(readFileSync(join(dir, name, 'feature.json'), 'utf8'))); } catch { return false; }
|
|
152
|
+
});
|
|
153
|
+
return active.length === 1 ? active[0] : null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export async function mutateCommand(featureArg, options = {}) {
|
|
112
157
|
const root = resolve(options.cwd ?? process.cwd());
|
|
113
158
|
if (options.restore) { restoreLatest(root); return null; }
|
|
114
159
|
|
|
115
|
-
const
|
|
116
|
-
if (!
|
|
160
|
+
const feature = featureArg ?? soleActiveFeature(root);
|
|
161
|
+
if (!feature && !options.plan) return fail('sem feature: passe <feature> (nenhuma, ou mais de uma, em andamento) ou o manifesto por --plan.', EXIT.usage);
|
|
162
|
+
const planPath = resolve(root, options.plan ?? join('.morph', 'features', feature, '4-review', 'mutants.json'));
|
|
163
|
+
if (!existsSync(planPath)) return fail(`manifesto não encontrado: ${planPath}\nEscreva-o com a ferramenta Write, se você a tem; só com Bash, heredoc de delimitador entre aspas simples, com a barra invertida do código como ${BACKSLASH_TOKEN} — formato em .morph/framework/CLI.md, seção "### mutate".`, EXIT.usage);
|
|
117
164
|
|
|
118
165
|
let regions;
|
|
119
166
|
try {
|
|
@@ -125,16 +172,26 @@ export async function mutateCommand(feature, options = {}) {
|
|
|
125
172
|
}
|
|
126
173
|
|
|
127
174
|
const { nodes } = resolveTestNodes(root);
|
|
128
|
-
const { groups, orphans } = groupByNode(regions, nodes, root);
|
|
175
|
+
const { groups, orphans, stackOnly } = groupByNode(regions, nodes, root);
|
|
129
176
|
if (orphans.length > 0) {
|
|
130
177
|
return fail(`nenhuma suíte declarada roda: ${orphans.map((o) => `${o.id} (${o.file})`).join(', ')}\nDeclare o nó em project.tests[] (.morph/config/config.json).`, EXIT.usage);
|
|
131
178
|
}
|
|
179
|
+
if (stackOnly.length > 0) {
|
|
180
|
+
const named = stackOnly.map(({ region, node }) => `${region.id} (${region.file}) → nó "${node.id}"`).join(', ');
|
|
181
|
+
return fail(`só uma suíte e2e: true cobre: ${named}\nProve a guarda por um teste de unidade, ou: ${e2eManualProtocol(feature, stackOnly[0].node)}`, EXIT.usage);
|
|
182
|
+
}
|
|
132
183
|
const mtp = groups.find((g) => g.node.runner === 'dotnet' && dotnetTestMode(resolve(root, g.node.cwd || '.')) === 'mtp');
|
|
133
184
|
if (mtp) return fail(`a suíte "${mtp.node.id}" roda no Microsoft.Testing.Platform (global.json): o TRX do VSTest, de onde o veredito e o motivo saem, não existe nela. Suporte a MTP ainda não há — rode a mutação com a suíte inteira à mão.`, EXIT.usage);
|
|
134
185
|
const uncompilable = groups.find((g) => g.node.runner === 'dotnet' && dotnetBuildCommands(g.node, root).length === 0);
|
|
135
|
-
if (uncompilable) return fail(`a suíte "${uncompilable.node.id}" não
|
|
186
|
+
if (uncompilable) return fail(`a suíte "${uncompilable.node.id}" não deixa descobrir o que compilar: o comando não nomeia .sln/.slnx/.csproj e o diretório alvo não tem uma única solution (ou um único projeto). Sem isso, todo mutante rodaria sobre o binário antigo e sairia "sobrevivente". Nomeie a solution no comando ou declare o campo build no nó (config.json, project.tests[]).`, EXIT.usage);
|
|
136
187
|
const preDecided = groups.find((g) => g.node.status);
|
|
137
188
|
if (preDecided) return fail(`a suíte "${preDecided.node.id}" já vem decidida como ${preDecided.node.status}: ${preDecided.node.reason ?? ''}`, EXIT.usage);
|
|
189
|
+
const uncited = uncitedUnderE2e({ root, groups, nodes });
|
|
190
|
+
if (uncited.length > 0) {
|
|
191
|
+
const named = uncited.map(({ region, node, types }) => `${region.id} (${region.file}; tipos: ${types.join(', ') || 'nenhum legível'}) → nenhum teste da suíte "${node.id}" os cita`).join('\n');
|
|
192
|
+
const e2eIds = uncited[0].e2e.map((n) => `"${n.id}"`).join(', ');
|
|
193
|
+
return fail(`${named}\nNó e2e: true declarado: ${e2eIds}. ${e2eManualProtocol(feature, uncited[0].e2e[0])}`, EXIT.usage);
|
|
194
|
+
}
|
|
138
195
|
|
|
139
196
|
if (!options.allowDirty) {
|
|
140
197
|
const dirty = gitOut(root, ['status', '--porcelain', '--', ...new Set(regions.map((r) => r.file))]).trim();
|
|
@@ -184,15 +241,18 @@ export async function mutateCommand(feature, options = {}) {
|
|
|
184
241
|
const order = new Map(regions.map((r, i) => [r.id, i]));
|
|
185
242
|
merged.mutants.sort((a, b) => order.get(a.id) - order.get(b.id));
|
|
186
243
|
const section = renderSection(merged, { head: git.head(), date: new Date().toISOString().slice(0, 10), subset: subsets[0] ?? null });
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
244
|
+
const outRel = options.out ?? (feature ? join('.morph', 'features', feature, '4-review', 'mutation-section.md') : null);
|
|
245
|
+
const outPath = outRel ? resolve(root, outRel) : null;
|
|
246
|
+
if (outPath) {
|
|
247
|
+
mkdirSync(join(outPath, '..'), { recursive: true });
|
|
248
|
+
writeFileSync(outPath, section);
|
|
249
|
+
}
|
|
190
250
|
|
|
191
251
|
const tally = summarize(merged);
|
|
192
252
|
if (options.json) console.log(JSON.stringify({ ...merged, tally, section: outPath }, null, 2));
|
|
193
253
|
else {
|
|
194
254
|
console.log(section);
|
|
195
|
-
console.log(chalk.gray(`seção gravada em ${outPath}`));
|
|
255
|
+
console.log(chalk.gray(outPath ? `seção gravada em ${outPath}` : 'seção não gravada: sem feature nem --out'));
|
|
196
256
|
console.log(`${chalk.green(`${tally.killed} mortos`)} · ${tally.survived ? chalk.yellow(`${tally.survived} sobreviveram`) : '0 sobreviveram'} · ${tally['build-error']} não compilaram · ${tally.inconclusive} inconclusivos`);
|
|
197
257
|
}
|
|
198
258
|
if (!merged.restore.ok || ['mismatch', 'unlocated'].includes(merged.binary.proof) || !merged.restore.treeClean) process.exitCode = EXIT.unsafe;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { existsSync, readFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { atomicWriteJson } from '../../core/state/state-manager.js';
|
|
5
|
+
import { isGateApproved } from '../../../framework/hooks/shared/state-reader.js';
|
|
6
|
+
import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
|
|
7
|
+
import { parsePlan } from '../../lib/plan/plan-parser.js';
|
|
8
|
+
import { buildChecks, knownPersonas, parallelGroups, planHash, validatePlan } from '../../lib/plan/checks.js';
|
|
9
|
+
import { knownSurfaces } from '../../lib/specs/screen-checklist.js';
|
|
10
|
+
import { revocationWarnings } from '../../lib/specs/adr-index.js';
|
|
11
|
+
import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
|
|
12
|
+
import { seedDotnetBuildIgnores } from '../../lib/stack/dotnet-build-ignores.js';
|
|
13
|
+
import { reportDotnetIgnores } from '../state/advance.js';
|
|
14
|
+
|
|
15
|
+
function readJson(path) {
|
|
16
|
+
try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return null; }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Parse and judge `2-plan/plan.md` of the project at `cwd`; with no error, write `2-plan/checks.json`. */
|
|
20
|
+
export function runPlanCheck(featureName, cwd = process.cwd()) {
|
|
21
|
+
const planPath = getAbsoluteOutputPath(cwd, featureName, 'plan');
|
|
22
|
+
const checksPath = getAbsoluteOutputPath(cwd, featureName, 'checks');
|
|
23
|
+
const base = { feature: featureName, plan: planPath, checksPath };
|
|
24
|
+
if (!existsSync(planPath)) return { ...base, ok: false, exit: 2, error: 'plan-not-found', errors: [], warnings: [], groups: [] };
|
|
25
|
+
|
|
26
|
+
const text = readFileSync(planPath, 'utf8');
|
|
27
|
+
const plan = parsePlan(text);
|
|
28
|
+
const { errors, warnings } = validatePlan(plan, { personas: knownPersonas(cwd), surfaces: knownSurfaces(cwd) });
|
|
29
|
+
warnings.push(...revocationWarnings(cwd, plan));
|
|
30
|
+
const groups = errors.length === 0 ? parallelGroups(plan.slices) : [];
|
|
31
|
+
const hash = planHash(text);
|
|
32
|
+
const result = { ...base, planHash: hash, errors, warnings, groups, written: false };
|
|
33
|
+
if (errors.length > 0) return { ...result, ok: false, exit: 1 };
|
|
34
|
+
|
|
35
|
+
const approved = isGateApproved(featureName, 'plan', cwd);
|
|
36
|
+
const previous = existsSync(checksPath) ? readJson(checksPath) : null;
|
|
37
|
+
if (approved && previous && previous.planHash !== hash) {
|
|
38
|
+
return { ...result, ok: false, exit: 1, error: 'frozen', previousHash: previous.planHash ?? null };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
atomicWriteJson(checksPath, buildChecks(featureName, text, plan));
|
|
42
|
+
return { ...result, ok: true, exit: 0, written: true };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function planCheckCommand(featureName, options = {}) {
|
|
46
|
+
const owner = assertOwnerTree(featureName, process.cwd());
|
|
47
|
+
if (owner.blocked) {
|
|
48
|
+
for (const { tone, text } of ownerTreeRefusalLines(featureName, owner.tree, owner.branch, `morph-spec plan-check ${featureName}`)) {
|
|
49
|
+
console.error(chalk[tone](text));
|
|
50
|
+
}
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const result = runPlanCheck(featureName);
|
|
55
|
+
const gitignoreAdded = result.written ? seedDotnetBuildIgnores(process.cwd(), { featureName }) : [];
|
|
56
|
+
if (options.json) {
|
|
57
|
+
if (gitignoreAdded.length) result.gitignoreAdded = gitignoreAdded;
|
|
58
|
+
console.log(JSON.stringify(result, null, 2));
|
|
59
|
+
process.exit(result.exit);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (result.error === 'plan-not-found') {
|
|
63
|
+
console.error(chalk.red(`plan-check: ${result.plan} não existe — o create escreve o esqueleto em 2-plan/plan.md (chore/hotfix: você o cria); gramática completa: morph-spec plan-check --help`));
|
|
64
|
+
process.exit(2);
|
|
65
|
+
}
|
|
66
|
+
for (const e of result.errors) console.log(chalk.red(` ✗ ${e}`));
|
|
67
|
+
for (const w of result.warnings) console.log(chalk.yellow(` ⚠ ${w}`));
|
|
68
|
+
|
|
69
|
+
if (result.error === 'frozen') {
|
|
70
|
+
console.log(chalk.red(`✗ plan-check: o gate plan de ${featureName} está aprovado e o plan.md mudou fora da errata — checks.json congelado.`));
|
|
71
|
+
console.log(chalk.gray(' Fato errado sobre o código: acrescente em ## Errata (não muda o planHash).'));
|
|
72
|
+
console.log(chalk.gray(` Mudança real de escopo: morph-spec unapprove ${featureName} plan, rode plan-check e re-apresente o gate.`));
|
|
73
|
+
} else if (!result.ok) {
|
|
74
|
+
console.log(chalk.red(`✗ plan-check: ${result.errors.length} erro(s) em ${result.plan} — checks.json não foi gravado`));
|
|
75
|
+
} else {
|
|
76
|
+
console.log(chalk.green(`✓ plan-check: ${join('2-plan', 'checks.json')} gravado (${result.planHash.slice(0, 12)})`));
|
|
77
|
+
result.groups.forEach((g, i) => console.log(chalk.gray(` onda ${i + 1}: ${g.join(', ')}${g.length > 1 ? ' (paralelo)' : ''}`)));
|
|
78
|
+
reportDotnetIgnores(gitignoreAdded);
|
|
79
|
+
}
|
|
80
|
+
process.exit(result.exit);
|
|
81
|
+
}
|