@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,117 +1,45 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { existsSync, readFileSync } from 'fs';
|
|
3
2
|
import { getFeature } from '../../core/state/state-manager.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { evaluatorReportState, evaluatorMediaSection, verdictSignal } from '../../lib/evaluator-report.js';
|
|
3
|
+
import { shouldPauseGate, getRiskSignals, classifyE2eSkip, planChecksPause, securityReviewFor } from '../../lib/risk-detector.js';
|
|
4
|
+
import { reviewReports, reviewSignal, requiredReviewRoles, verifierMediaSection } from '../../lib/review-reports.js';
|
|
7
5
|
import { collectScreenChecklist } from '../../lib/specs/screen-checklist.js';
|
|
8
6
|
import { persistGateDecision, VALID_GATES, readTrustFromConfig } from './gate-decision.js';
|
|
9
7
|
import { minimatch } from 'minimatch';
|
|
10
8
|
import { detectDefaultBranch, getHeadSha, getDiffStat, changedPathsAgainst } from '../../lib/git/default-branch.js';
|
|
11
9
|
import { treeMovedSince } from '../../lib/git/evidence-staleness.js';
|
|
12
10
|
import { readMorphConfig } from '../../lib/tasks/test-runner.js';
|
|
13
|
-
import {
|
|
11
|
+
import { readPlanChecks } from '../../lib/plan/checks.js';
|
|
14
12
|
import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* — everything else here is read straight off disk (workType, taskCount, sha,
|
|
19
|
-
* diffstat, verification), and letting a caller override those would let a
|
|
20
|
-
* careless/hostile `--signals` silently defeat the policy this command exists
|
|
21
|
-
* to make deterministic (e.g. overriding `workType` away from "hotfix" would
|
|
22
|
-
* suppress risk-detector's forced Gate-3 pause). Only the signals that are
|
|
23
|
-
* genuinely NOT derivable from disk today are accepted here:
|
|
24
|
-
* - standardsViolations / designSystemDivergence: still an LLM's
|
|
25
|
-
* natural-language read (see the R4 report — no deterministic detector
|
|
26
|
-
* exists in JS yet).
|
|
27
|
-
*/
|
|
14
|
+
// Only signals NOT derivable from disk may be overridden: overriding a disk fact (e.g. `workType`
|
|
15
|
+
// away from hotfix) would silently defeat the policy this command makes deterministic.
|
|
28
16
|
const OVERRIDABLE_SIGNAL_KEYS = ['standardsViolations', 'designSystemDivergence'];
|
|
29
17
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
* 2 congelou. Nao ha julgamento envolvido: ou a task pinou o standard, ou nao
|
|
35
|
-
* pinou. Um sinal derivado de opiniao seria sobrescrivivel na pratica ainda que
|
|
36
|
-
* nao estivesse em OVERRIDABLE_SIGNAL_KEYS.
|
|
37
|
-
*
|
|
38
|
-
* A forma dos valores e a de `tasks.json` (`standards/...`), nao a do disco
|
|
39
|
-
* (`framework/standards/...`): e o vocabulario que o plano usa.
|
|
40
|
-
*/
|
|
41
|
-
export const MEDIA_STANDARD_PATHS = [
|
|
42
|
-
'standards/ai-agents/modalities-image-gen.md',
|
|
43
|
-
'standards/ai-agents/media-video.md',
|
|
44
|
-
'standards/ai-agents/media-pipeline.md',
|
|
18
|
+
export const MEDIA_STANDARD_IDS = [
|
|
19
|
+
'ai-agents-modalities-image-gen',
|
|
20
|
+
'ai-agents-media-video',
|
|
21
|
+
'ai-agents-media-pipeline',
|
|
45
22
|
];
|
|
46
23
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
* sozinho — o irmao `productionCode` tem quatro casos, e este nao tinha nenhum.
|
|
54
|
-
* Um predicado que sempre devolve `false` desarma a clausula em silencio, e
|
|
55
|
-
* silencio e o defeito que esta epic catalogou sete vezes.
|
|
56
|
-
*
|
|
57
|
-
* **O prefixo `framework/` e normalizado, e essa e a razao de existir da
|
|
58
|
-
* funcao.** O vocabulario do plano e `standards/...` (o exemplo do
|
|
59
|
-
* `tasks.schema.json`, e a unica forma medida hoje nos `standards[]` desta
|
|
60
|
-
* arvore), mas o disco e `framework/standards/...` — e a mesma prosa das skills
|
|
61
|
-
* cita as duas formas. Um plano que escrevesse a forma do disco desarmaria o
|
|
62
|
-
* guardrail sem erro nenhum: `standards --find`, `validate` e o proprio Gate 3
|
|
63
|
-
* continuariam verdes. Aceitar as duas grafias custa uma linha; recusar uma
|
|
64
|
-
* delas em silencio custa o gate.
|
|
65
|
-
*
|
|
66
|
-
* Fail-open por AUSENCIA (nao por opiniao): lista vazia, `standards` ausente ou
|
|
67
|
-
* de tipo errado ⇒ `false` ⇒ nao pausa. Sem plano legivel nao ha como AFIRMAR
|
|
68
|
-
* que a feature gera midia, e uma regra nova nao trava feature legada.
|
|
69
|
-
*
|
|
70
|
-
* @param {Array<object>} tasks - `tasks.json` → `tasks[]` (qualquer coisa nao-array vira []).
|
|
71
|
-
* @returns {boolean} true se ao menos uma task pinou um standard de midia.
|
|
72
|
-
*/
|
|
73
|
-
export function featureGeneratesMedia(tasks) {
|
|
74
|
-
if (!Array.isArray(tasks)) return false;
|
|
75
|
-
|
|
76
|
-
return tasks.some((t) =>
|
|
77
|
-
Array.isArray(t?.standards) &&
|
|
78
|
-
t.standards.some((s) =>
|
|
79
|
-
typeof s === 'string' &&
|
|
80
|
-
MEDIA_STANDARD_PATHS.includes(s.replace(/^framework\//, ''))));
|
|
24
|
+
// O plano escreve o standard pelo id ou pelo caminho (com ou sem `framework/`/`standards/`).
|
|
25
|
+
export function standardIdOf(value) {
|
|
26
|
+
return String(value ?? '').trim().replace(/\\/g, '/')
|
|
27
|
+
.replace(/^(\.morph\/)?(framework\/)?(standards\/)?/, '')
|
|
28
|
+
.replace(/\.md$/, '')
|
|
29
|
+
.replace(/\//g, '-');
|
|
81
30
|
}
|
|
82
31
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
} catch {
|
|
90
|
-
return [];
|
|
91
|
-
}
|
|
32
|
+
// Decide se um guardrail arma: um predicado que sempre devolve `false` desarma em silêncio a
|
|
33
|
+
// pausa de mídia da review e a rota opus.
|
|
34
|
+
export function featureGeneratesMedia(slices) {
|
|
35
|
+
if (!Array.isArray(slices)) return false;
|
|
36
|
+
return slices.some((s) => Array.isArray(s?.standards)
|
|
37
|
+
&& s.standards.some((id) => MEDIA_STANDARD_IDS.includes(standardIdOf(id))));
|
|
92
38
|
}
|
|
93
39
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
*
|
|
98
|
-
* `declared: false` é "o projeto não declara caminho algum de prompt/modelo" —
|
|
99
|
-
* a regra é inerte por construção e a razão registrada diz isso. `declared:
|
|
100
|
-
* true` com `touched: []` é "declara, e nada foi tocado" — a regra rodou e não
|
|
101
|
-
* viu nada. Colapsar os dois num array vazio faria um projeto que nunca
|
|
102
|
-
* configurou `watch[]` parecer um projeto que foi verificado.
|
|
103
|
-
*
|
|
104
|
-
* O predicado NÃO é invenção deste framework: o repositório de referência já
|
|
105
|
-
* roda exatamente ele em produção, disparando publicação por `paths:` sobre os
|
|
106
|
-
* mesmos globs e decidindo o escopo com `git diff --name-only`.
|
|
107
|
-
*
|
|
108
|
-
* COLETA, NUNCA DECIDE. Nada aqui julga se o que foi tocado deveria pausar um
|
|
109
|
-
* gate — política escrita no coletor cria duas fontes para o mesmo fato.
|
|
110
|
-
*
|
|
111
|
-
* @param {string} cwd
|
|
112
|
-
* @param {string|null} defaultBranch
|
|
113
|
-
* @returns {{declared: boolean, watch: string[], touched: string[]}}
|
|
114
|
-
*/
|
|
40
|
+
// `declared: false` (nenhum caminho declarado: regra inerte) e `touched: []` (declarou, nada
|
|
41
|
+
// tocado) não podem colapsar num array vazio. COLETA, NUNCA DECIDE: política no coletor cria
|
|
42
|
+
// duas fontes para o mesmo fato.
|
|
115
43
|
function collectPromptModel(cwd, defaultBranch) {
|
|
116
44
|
const watch = readMorphConfig(cwd)?.project?.evals?.watch;
|
|
117
45
|
const globs = Array.isArray(watch) ? watch.filter((g) => typeof g === 'string' && g.trim()) : [];
|
|
@@ -126,103 +54,73 @@ function collectPromptModel(cwd, defaultBranch) {
|
|
|
126
54
|
};
|
|
127
55
|
}
|
|
128
56
|
|
|
129
|
-
/**
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
* @returns {object}
|
|
153
|
-
*/
|
|
57
|
+
/** A regra única de quais relatórios o ship exige, lida por gate-check, approve e report-check. */
|
|
58
|
+
export function collectReviewRequirement(cwd, feature) {
|
|
59
|
+
let changed = [];
|
|
60
|
+
try { changed = changedPathsAgainst(detectDefaultBranch(cwd), cwd); } catch { changed = []; }
|
|
61
|
+
const security = securityReviewFor(changed);
|
|
62
|
+
return { required: requiredReviewRoles({ workType: feature?.workType, securityRequired: security.required }), security };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function collectReviewSignal(cwd, featureName, feature, planChecks = readPlanChecks(cwd, featureName)) {
|
|
66
|
+
const { required } = collectReviewRequirement(cwd, feature);
|
|
67
|
+
const checkIds = Array.isArray(planChecks.checks?.checks) ? planChecks.checks.checks.map((c) => c.id) : null;
|
|
68
|
+
return reviewSignal(reviewReports(cwd, featureName), { required, checkIds });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Why an automatic signature may not close the plan gate: the rule that makes gate-check pause it. */
|
|
72
|
+
export function planAutoApprovalBlock(cwd, featureName) {
|
|
73
|
+
const reason = planChecksPause(readPlanChecks(cwd, featureName).status);
|
|
74
|
+
return reason ? reason.replace('{feature}', featureName) : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Collects only; risk-detector.js decides. `e2eStatus`/`e2eReason` exist because a skipped e2e
|
|
78
|
+
// node does not fail the rollup: the literal reason is the only fact separating a legitimate
|
|
79
|
+
// waiver from missing proof.
|
|
154
80
|
export function collectSignals(featureName, feature, cwd, gateName) {
|
|
155
|
-
const
|
|
81
|
+
const planChecks = readPlanChecks(cwd, featureName);
|
|
82
|
+
const slices = planChecks.checks?.slices ?? null;
|
|
156
83
|
const defaultBranch = detectDefaultBranch(cwd);
|
|
157
84
|
const sha = getHeadSha(cwd);
|
|
158
85
|
const verification = feature.verification || null;
|
|
159
86
|
|
|
160
87
|
const signals = {
|
|
161
|
-
//
|
|
162
|
-
// cannot spoof which gate risk-detector.js thinks it is deciding, same
|
|
163
|
-
// anti-spoof rationale as workType (hotfix's forced pause).
|
|
88
|
+
// Disk-derived, never overridable: a caller cannot spoof which gate is being decided.
|
|
164
89
|
gate: gateName,
|
|
165
90
|
workType: feature.workType || 'feature',
|
|
166
|
-
|
|
91
|
+
sliceCount: slices ? slices.length : null,
|
|
167
92
|
standardsViolations: [],
|
|
168
93
|
designSystemDivergence: false,
|
|
169
94
|
};
|
|
170
95
|
|
|
171
|
-
|
|
172
|
-
// dispatch se fatia no plano, não depois de um sub-agent bater no teto.
|
|
173
|
-
if (gateName === 'plan') signals.oversizedTasks = oversizedTasks({ tasks });
|
|
96
|
+
if (gateName === 'plan') signals.planChecks = planChecks.status;
|
|
174
97
|
|
|
175
|
-
// Só onde o checklist decide algo: no fecho do design e no ship.
|
|
176
|
-
// disco, fora de OVERRIDABLE_SIGNAL_KEYS.
|
|
98
|
+
// Só onde o checklist decide algo: no fecho do design e no ship.
|
|
177
99
|
if (gateName === 'uiux' || gateName === 'review') {
|
|
178
100
|
signals.screenChecklist = collectScreenChecklist(cwd, featureName, gateName);
|
|
179
101
|
}
|
|
180
102
|
|
|
181
103
|
if (gateName === 'review') {
|
|
182
|
-
signals.
|
|
104
|
+
signals.reviewReports = collectReviewSignal(cwd, featureName, feature, planChecks);
|
|
183
105
|
signals.mediaClaims = {
|
|
184
|
-
section:
|
|
185
|
-
featureGeneratesMedia: featureGeneratesMedia(
|
|
106
|
+
section: verifierMediaSection(cwd, featureName),
|
|
107
|
+
featureGeneratesMedia: featureGeneratesMedia(slices),
|
|
186
108
|
};
|
|
187
109
|
}
|
|
188
110
|
|
|
189
|
-
//
|
|
190
|
-
// stamp é auditoria. verificationStale é fail-open na direção "not stale":
|
|
191
|
-
// stamp com sha null (checkout sem git) ou stamp ausente lê como NOT stale.
|
|
111
|
+
// verificationStale é fail-open: stamp sem sha (checkout sem git) ou ausente lê como NOT stale.
|
|
192
112
|
signals.verification = verification;
|
|
193
113
|
if (verification?.status) signals.verificationStatus = verification.status;
|
|
194
114
|
signals.verificationStale = treeMovedSince(cwd, verification?.sha, sha);
|
|
195
115
|
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
// quando o e2e rodou e passou quanto quando ele nunca rodou. `null` aqui é
|
|
199
|
-
// DESCONHECIDO (stamp ausente, ou anterior ao nó e2e), nunca 'skip'.
|
|
200
|
-
// `e2eSkipClass` é DERIVADO pela fonte única (classifyE2eSkip, em
|
|
201
|
-
// risk-detector.js) e persistido só como auditoria — quem decide continua
|
|
202
|
-
// sendo shouldPauseGate, aqui não há política.
|
|
203
|
-
//
|
|
204
|
-
// Nenhuma das três entra em OVERRIDABLE_SIGNAL_KEYS: descrevem O QUE
|
|
205
|
-
// ACONTECEU no disco, como `gate`/`workType`, não uma opinião que o chamador
|
|
206
|
-
// possa afirmar. Um `--signals '{"e2eStatus":"pass"}'` compraria o Gate 3.
|
|
116
|
+
// Separado do rollup: um `skip` NÃO reprova `verification.status`. `null` é DESCONHECIDO, nunca
|
|
117
|
+
// 'skip'. Fora de OVERRIDABLE_SIGNAL_KEYS: um `--signals '{"e2eStatus":"pass"}'` compraria o ship.
|
|
207
118
|
signals.e2eStatus = verification?.nodes?.e2e ?? null;
|
|
208
119
|
signals.e2eReason = verification?.e2eReason ?? null;
|
|
209
120
|
signals.e2eSkipClass = signals.e2eStatus === 'skip' ? classifyE2eSkip(signals.e2eReason) : null;
|
|
210
|
-
// De QUEM é o carimbo. `verify {f} {task}` grava 'task:{id}'; só 'feature'
|
|
211
|
-
// responde pela app inteira, e é por isso que este sinal existe: a razão
|
|
212
|
-
// 'task group "docs" does not touch runtime' é dispensa legítima do que
|
|
213
|
-
// AQUELA task verificou, e sem o escopo ela dispensaria o Gate 3 da feature.
|
|
214
|
-
// Fato de disco como os outros três — jamais sobrescrevível por --signals.
|
|
215
|
-
signals.verificationScope = verification?.scope ?? null;
|
|
216
121
|
|
|
217
|
-
//
|
|
218
|
-
// PULOU (escopo de task) não avermelha `verification.status`, então o gate
|
|
219
|
-
// leria 'pass' tanto para "o eval rodou e passou" quanto para "o eval nunca
|
|
220
|
-
// rodou". `null` aqui é "este projeto não declara nó de eval" — DISTINTO de
|
|
221
|
-
// 'skip', que é "declara, e esta corrida não o executou".
|
|
122
|
+
// Mesma razão do e2e. `null` = o projeto não declara nó de eval, DISTINTO de 'skip'.
|
|
222
123
|
signals.evalsStatus = verification?.nodes?.evals ?? null;
|
|
223
|
-
// O que a feature TOCOU, contra os globs que o projeto declarou como
|
|
224
|
-
// prompt/modelo. Fato de disco: `declared` vem do config commitado, `touched`
|
|
225
|
-
// do git. Nenhuma política mora aqui — quem decide é shouldPauseGate.
|
|
226
124
|
signals.promptModel = collectPromptModel(cwd, defaultBranch);
|
|
227
125
|
|
|
228
126
|
signals.diffstat = getDiffStat(defaultBranch, cwd);
|
|
@@ -231,20 +129,8 @@ export function collectSignals(featureName, feature, cwd, gateName) {
|
|
|
231
129
|
return signals;
|
|
232
130
|
}
|
|
233
131
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
* [--json]` — the deterministic, disk-derived counterpart to `gate-decision`:
|
|
237
|
-
* collects the signals `shouldPauseGate()` (risk-detector.js) already knows
|
|
238
|
-
* how to score, calls it, and persists the result through the SAME write path
|
|
239
|
-
* as `gate-decision` ({@link persistGateDecision}) so the two commands can
|
|
240
|
-
* never produce a differently-shaped `gateDecisions.{gate}` entry.
|
|
241
|
-
*
|
|
242
|
-
* Golden rule: this command does NOT change gate policy. It is a faithful
|
|
243
|
-
* caller of risk-detector.js's existing, already-tested rules — nothing here
|
|
244
|
-
* adds a new pause condition. Where the skills' natural-language risk tables
|
|
245
|
-
* (morph-brainstorming/morph-plan/morph-review/morph-apply) diverge from
|
|
246
|
-
* risk-detector.js, risk-detector.js wins (see the R4 report for the full list).
|
|
247
|
-
*/
|
|
132
|
+
// Never changes gate policy: a faithful caller of risk-detector.js, which wins wherever the
|
|
133
|
+
// skills' risk tables diverge. Persists through the same path as `gate-decision`.
|
|
248
134
|
export async function gateCheckCommand(featureName, gateName, options = {}) {
|
|
249
135
|
try {
|
|
250
136
|
if (!VALID_GATES.includes(gateName)) {
|
|
@@ -259,10 +145,7 @@ export async function gateCheckCommand(featureName, gateName, options = {}) {
|
|
|
259
145
|
process.exit(1);
|
|
260
146
|
}
|
|
261
147
|
|
|
262
|
-
//
|
|
263
|
-
// AND writes the resulting decision through persistGateDecision — both
|
|
264
|
-
// halves are wrong from a tree that isn't this feature's own, so refuse
|
|
265
|
-
// rather than compute a decision from stale reads and then fail to save it.
|
|
148
|
+
// Both the reads and the persisted decision are wrong from a tree that doesn't own the feature.
|
|
266
149
|
const owner = assertOwnerTree(featureName, process.cwd());
|
|
267
150
|
if (owner.blocked) {
|
|
268
151
|
for (const { tone, text } of ownerTreeRefusalLines(featureName, owner.tree, owner.branch, `morph-spec gate-check ${featureName} ${gateName}`)) {
|
|
@@ -289,7 +172,7 @@ export async function gateCheckCommand(featureName, gateName, options = {}) {
|
|
|
289
172
|
if (rejected.length > 0) {
|
|
290
173
|
console.error(chalk.yellow(
|
|
291
174
|
`\n⚠️ --signals ignored for keys not derived from disk-facts you may safely override: ${rejected.join(', ')}\n` +
|
|
292
|
-
` Only ${OVERRIDABLE_SIGNAL_KEYS.join(', ')} may be overridden here — everything else is read from feature.json/
|
|
175
|
+
` Only ${OVERRIDABLE_SIGNAL_KEYS.join(', ')} may be overridden here — everything else is read from feature.json/checks.json/git.\n` +
|
|
293
176
|
` Use \`morph-spec gate-decision\` directly for a fully manual decision.\n`
|
|
294
177
|
));
|
|
295
178
|
}
|
|
@@ -303,9 +186,14 @@ export async function gateCheckCommand(featureName, gateName, options = {}) {
|
|
|
303
186
|
const signals = { ...collectSignals(featureName, feature, cwd, gateName), ...overrides };
|
|
304
187
|
|
|
305
188
|
const trust = options.trust || readTrustFromConfig();
|
|
306
|
-
const
|
|
189
|
+
const computed = shouldPauseGate({ trust }, signals);
|
|
190
|
+
const manual = feature.gateDecisions?.[gateName];
|
|
191
|
+
const manualPause = manual?.source === 'manual' && manual.pause === true;
|
|
192
|
+
const { pause, reason } = manualPause ? { pause: true, reason: manual.reason } : computed;
|
|
307
193
|
|
|
308
|
-
const entry = await persistGateDecision(featureName, gateName, {
|
|
194
|
+
const entry = await persistGateDecision(featureName, gateName, {
|
|
195
|
+
pause, reason, signals, trust, cwd, source: manualPause ? 'manual' : 'gate-check',
|
|
196
|
+
});
|
|
309
197
|
|
|
310
198
|
if (options.json) {
|
|
311
199
|
console.log(JSON.stringify({ feature: featureName, gate: gateName, ...entry }, null, 2));
|
|
@@ -317,6 +205,9 @@ export async function gateCheckCommand(featureName, gateName, options = {}) {
|
|
|
317
205
|
? chalk.yellow(`\n⏸️ Gate "${gateName}" — PAUSE (${featureName})`)
|
|
318
206
|
: chalk.green(`\n▶️ Gate "${gateName}" — auto-pass (${featureName})`));
|
|
319
207
|
console.log(chalk.gray(` reason: ${entry.reason} | trust: ${entry.trust} | decidedAt: ${entry.decidedAt}`));
|
|
208
|
+
if (manualPause) {
|
|
209
|
+
console.log(chalk.gray(` pausa manual mantida (sinais calculados: ${computed.reason}); só \`morph-spec gate-decision ${featureName} ${gateName} --pause false\` a libera`));
|
|
210
|
+
}
|
|
320
211
|
if (allSignals.length > 0) {
|
|
321
212
|
console.log(chalk.gray(` risk signals present: ${allSignals.join('; ')}`));
|
|
322
213
|
}
|
|
@@ -5,7 +5,7 @@ import { getFeature, updateFeature } from '../../core/state/state-manager.js';
|
|
|
5
5
|
import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
|
|
6
6
|
import { mockupsHash } from '../../lib/specs/screen-checklist.js';
|
|
7
7
|
|
|
8
|
-
export const VALID_GATES = ['
|
|
8
|
+
export const VALID_GATES = ['plan', 'uiux', 'review'];
|
|
9
9
|
|
|
10
10
|
function parsePauseValue(raw) {
|
|
11
11
|
const v = String(raw).toLowerCase();
|
|
@@ -15,11 +15,7 @@ function parsePauseValue(raw) {
|
|
|
15
15
|
process.exit(1);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
* trust from the committed config (BOM-tolerant), defaulting to "auto".
|
|
20
|
-
* Shared by `gate-decision` (recorded as metadata) and `gate-check`
|
|
21
|
-
* (drives shouldPauseGate's decision directly).
|
|
22
|
-
*/
|
|
18
|
+
/** `trust` from the committed config (BOM-tolerant), defaulting to "auto". */
|
|
23
19
|
export function readTrustFromConfig() {
|
|
24
20
|
try {
|
|
25
21
|
const path = join(process.cwd(), '.morph', 'config', 'config.json');
|
|
@@ -32,24 +28,10 @@ export function readTrustFromConfig() {
|
|
|
32
28
|
}
|
|
33
29
|
|
|
34
30
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* caller-supplied pause/reason/signals, e.g. an LLM's natural-language risk
|
|
38
|
-
* read, or a manual override) and `gate-check` (a `shouldPauseGate()`-derived
|
|
39
|
-
* pause/reason/signals) feed into, so the two commands can never drift into
|
|
40
|
-
* two different persisted shapes for the same field. Does NOT validate
|
|
41
|
-
* `gateName` — callers that need a friendly CLI error check `VALID_GATES`
|
|
42
|
-
* themselves before calling this.
|
|
43
|
-
*
|
|
44
|
-
* @param {string} featureName
|
|
45
|
-
* @param {string} gateName
|
|
46
|
-
* @param {{ pause: boolean, reason?: string, signals?: object, trust?: string, cwd?: string }} decision
|
|
47
|
-
* @returns {Promise<{ pause: boolean, reason: string, signals: object, trust: string, decidedAt: string, mockupsHash?: string|null }>}
|
|
48
|
-
* the exact entry written (decidedAt is server-side, generated here). A uiux
|
|
49
|
-
* decision also carries the hash of the mockup it saw: it answers that
|
|
50
|
-
* mockup's pause, not the next one's.
|
|
31
|
+
* The ONE write path of `gateDecisions.{gate}`, shared by `gate-decision` and `gate-check`.
|
|
32
|
+
* Does not validate `gateName`. A uiux decision records the `mockupsHash` it saw.
|
|
51
33
|
*/
|
|
52
|
-
export async function persistGateDecision(featureName, gateName, { pause, reason, signals = {}, trust, cwd = process.cwd() } = {}) {
|
|
34
|
+
export async function persistGateDecision(featureName, gateName, { pause, reason, signals = {}, trust, cwd = process.cwd(), source } = {}) {
|
|
53
35
|
const entry = {
|
|
54
36
|
pause,
|
|
55
37
|
reason: reason || (pause ? 'risco detectado' : 'nenhum sinal de risco'),
|
|
@@ -57,30 +39,13 @@ export async function persistGateDecision(featureName, gateName, { pause, reason
|
|
|
57
39
|
trust: trust || readTrustFromConfig(),
|
|
58
40
|
decidedAt: new Date().toISOString(),
|
|
59
41
|
...(gateName === 'uiux' ? { mockupsHash: mockupsHash(cwd, featureName) } : {}),
|
|
42
|
+
source,
|
|
60
43
|
};
|
|
61
44
|
|
|
62
45
|
await updateFeature(featureName, `gateDecisions.${gateName}`, entry);
|
|
63
46
|
return entry;
|
|
64
47
|
}
|
|
65
48
|
|
|
66
|
-
/**
|
|
67
|
-
* `morph-spec gate-decision <feature> <gate> --pause <bool> [--reason <txt>]
|
|
68
|
-
* [--signals <json>] [--trust <mode>]` — record the trust decision for a gate
|
|
69
|
-
* under `gateDecisions` in the authoritative feature.json.
|
|
70
|
-
*
|
|
71
|
-
* The timestamp (`decidedAt`) is
|
|
72
|
-
* stamped server-side and the write is atomic (state-manager). This replaces
|
|
73
|
-
* the manual Edit of feature.json from the skills — which collided with the
|
|
74
|
-
* state-sync hook rewriting the file between the agent's Read and Edit
|
|
75
|
-
* ("file modified since read"). The canonical shape is documented in
|
|
76
|
-
* framework/schemas/feature.schema.json and consumed by gate-guard.js.
|
|
77
|
-
*
|
|
78
|
-
* The manual escape hatch: `gate-check` (src/commands/state/gate-check.js) is
|
|
79
|
-
* the default, disk-derived path via `shouldPauseGate()`; this command stays
|
|
80
|
-
* for a caller that already knows the decision (an LLM's natural-language
|
|
81
|
-
* risk read a script can't see, or a deliberate override) — both write
|
|
82
|
-
* through {@link persistGateDecision}, so the persisted shape never diverges.
|
|
83
|
-
*/
|
|
84
49
|
export async function gateDecisionCommand(featureName, gateName, options = {}) {
|
|
85
50
|
try {
|
|
86
51
|
if (!VALID_GATES.includes(gateName)) {
|
|
@@ -118,7 +83,7 @@ export async function gateDecisionCommand(featureName, gateName, options = {}) {
|
|
|
118
83
|
process.exit(1);
|
|
119
84
|
}
|
|
120
85
|
if (!signals || typeof signals !== 'object' || Array.isArray(signals)) {
|
|
121
|
-
console.error(chalk.red('\n❌ Invalid --signals: expected a JSON object, e.g. \'{"
|
|
86
|
+
console.error(chalk.red('\n❌ Invalid --signals: expected a JSON object, e.g. \'{"standardsViolations":[]}\'\n'));
|
|
122
87
|
process.exit(1);
|
|
123
88
|
}
|
|
124
89
|
}
|
|
@@ -128,6 +93,7 @@ export async function gateDecisionCommand(featureName, gateName, options = {}) {
|
|
|
128
93
|
reason: options.reason,
|
|
129
94
|
signals,
|
|
130
95
|
trust: options.trust,
|
|
96
|
+
source: 'manual',
|
|
131
97
|
});
|
|
132
98
|
|
|
133
99
|
if (options.json) {
|
|
@@ -1,34 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `morph-spec handoff <feature>` — generate the compressed session-handoff
|
|
3
|
-
* document (`.morph/features/{f}/handoff.md`).
|
|
4
|
-
*
|
|
5
|
-
* The fleet workflow hands a feature between Claude Code instances (a new
|
|
6
|
-
* worktree session, a resumed session after a phase transition). The handoff
|
|
7
|
-
* is the sanctioned way to carry context across: derived, regenerable, and
|
|
8
|
-
* pointer-based (see handoff-generator.js). This command is also called
|
|
9
|
-
* in-process by `worktree setup`, `create --worktree`, `advance` and
|
|
10
|
-
* `approve` — the CLI surface exists for the LLM/user to refresh the
|
|
11
|
-
* document on demand.
|
|
12
|
-
*
|
|
13
|
-
* WORKTREE-AWARE: `create --worktree` writes a COMPLETE feature.json into the
|
|
14
|
-
* primary root and the branch-committed copy (`morph/{f}`) becomes the truth
|
|
15
|
-
* from then on. Building the handoff from `process.cwd()` unconditionally
|
|
16
|
-
* used to mean: run this from the root while the worktree is alive, and you
|
|
17
|
-
* either got "Feature not found" (root's copy was already sheded) or a
|
|
18
|
-
* document silently built from the stale birth skeleton, written INTO the
|
|
19
|
-
* root (a second, always-defasada copy of a document that's supposed to be
|
|
20
|
-
* regenerated, not duplicated). This resolves the owning tree first
|
|
21
|
-
* (`resolveFeatureTree`) and writes THERE, unless `--here` forces the cwd.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
1
|
import chalk from 'chalk';
|
|
25
2
|
import { buildHandoff, writeHandoff } from '../../lib/generators/handoff-generator.js';
|
|
26
3
|
import { resolveFeatureTree } from '../../lib/worktree/feature-truth.js';
|
|
27
4
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
* @param {{ json?: boolean, here?: boolean }} [options]
|
|
31
|
-
*/
|
|
5
|
+
// With a live worktree the root copy is a stale birth skeleton: build and write in the
|
|
6
|
+
// owning tree (`resolveFeatureTree`) unless `--here` forces the cwd.
|
|
32
7
|
export function handoffCommand(featureName, options = {}) {
|
|
33
8
|
const cwd = process.cwd();
|
|
34
9
|
|
|
@@ -44,9 +19,7 @@ export function handoffCommand(featureName, options = {}) {
|
|
|
44
19
|
}
|
|
45
20
|
|
|
46
21
|
if (owner.source === 'branch-only') {
|
|
47
|
-
// The
|
|
48
|
-
// into ANY tree right now. A handoff points at files on disk — pointing
|
|
49
|
-
// at files that exist nowhere is worse than recusing outright.
|
|
22
|
+
// The handoff points at files on disk; a truth materialized in no tree is refused.
|
|
50
23
|
const hint = `morph-spec worktree setup ${featureName} (ou: git checkout ${owner.branch})`;
|
|
51
24
|
if (options.json) {
|
|
52
25
|
console.log(JSON.stringify({ error: 'feature-not-in-tree', feature: featureName, branch: owner.branch, hint }));
|
|
@@ -87,6 +60,6 @@ export function handoffCommand(featureName, options = {}) {
|
|
|
87
60
|
if (tree !== cwd) {
|
|
88
61
|
console.log(chalk.yellow(` resolvido para ${tree} — a cópia da raiz é esqueleto de nascimento, não a verdade.`));
|
|
89
62
|
}
|
|
90
|
-
console.log(chalk.gray(` fase ${result.meta.phase} ·
|
|
63
|
+
console.log(chalk.gray(` fase ${result.meta.phase} · fatias ${result.meta.slicesDone}/${result.meta.slicesTotal} · ${result.meta.generatedAt}`));
|
|
91
64
|
console.log(chalk.gray(' A próxima sessão lê este documento antes de continuar (ponteiros, não cópias).\n'));
|
|
92
65
|
}
|