@polymorphism-tech/morph-spec 9.0.0 → 9.2.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 +8 -9
- package/README.md +38 -36
- package/bin/morph-spec.js +11 -63
- package/bin/validate.js +1 -39
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -1
- package/framework/MORPH.md +3 -3
- package/framework/agents.json +19 -11
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +22 -31
- package/framework/evals/review-checklist.md +12 -2
- package/framework/hooks/README.md +8 -44
- 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 +2 -16
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
- package/framework/hooks/claude-code/core/gate-guard.js +7 -67
- 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 +0 -4
- package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
- package/framework/hooks/claude-code/core/state-sync.js +12 -68
- package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
- package/framework/hooks/claude-code/core/validate-completion.js +11 -57
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
- package/framework/hooks/claude-code/core/worktree-context.js +10 -110
- 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.sh +11 -11
- 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 +6 -75
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +6 -51
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +6 -25
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +2 -15
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +1 -3
- package/framework/hooks/shared/gate-question.js +1 -7
- 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 +1 -42
- 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 +4 -23
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/state-reader.js +13 -183
- package/framework/hooks/shared/state-writer.js +8 -52
- 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/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/rules/nextjs-standards.md +1 -1
- 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 +4 -4
- package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
- package/framework/skills/core/morph-machine/SKILL.md +1 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +9 -7
- package/framework/skills/core/morph-review/SKILL.md +9 -6
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +29 -12
- package/framework/standards/STANDARDS.json +36 -14
- package/framework/standards/STANDARDS.meta.json +16 -2
- 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/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 +2 -2
- 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 +1 -1
- 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/testing.md +10 -4
- 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/frontend/nextjs/testing.md +10 -6
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +9 -9
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/docs/plan.md +3 -1
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- 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 +17 -104
- 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 +5 -57
- 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.js +48 -302
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +3 -32
- package/src/commands/project/init.js +4 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/standards.js +1 -62
- package/src/commands/project/status.js +0 -53
- package/src/commands/project/update.js +31 -239
- package/src/commands/project/worktree.js +108 -882
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +14 -85
- package/src/commands/state/approve.js +15 -97
- package/src/commands/state/archive.js +12 -64
- package/src/commands/state/auto-approval.js +2 -5
- package/src/commands/state/create.js +22 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +98 -475
- package/src/commands/state/gate-check.js +22 -132
- package/src/commands/state/gate-decision.js +3 -39
- package/src/commands/state/handoff.js +3 -30
- package/src/commands/state/list.js +14 -84
- package/src/commands/state/report-check.js +8 -58
- package/src/commands/state/retype.js +4 -26
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +11 -11
- package/src/commands/validation/plan-check.js +4 -2
- package/src/commands/validation/tests-audit.js +7 -0
- package/src/commands/validation/validate-feature.js +10 -97
- package/src/commands/validation/verify.js +30 -113
- package/src/core/paths/output-schema.js +2 -101
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +0 -27
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +22 -304
- 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 +0 -1
- package/src/core/work-types.js +1 -36
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
- package/src/dashboard/app/components/CommandPalette.jsx +0 -7
- 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 +1 -5
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
- package/src/dashboard/app/pages/Features.jsx +1 -8
- package/src/dashboard/app/pages/Guide.jsx +0 -1
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
- 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 +10 -69
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +0 -6
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +10 -66
- 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 +2 -6
- package/src/dashboard/vite-plugin-morph-data.js +5 -19
- package/src/dashboard/vite.config.js +24 -24
- 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 +4 -19
- package/src/lib/ci/github-ci.js +9 -37
- package/src/lib/ci/workflow-scaffold.js +14 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +4 -50
- package/src/lib/e2e/applicability.js +37 -0
- 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 +9 -53
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +4 -37
- 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 +1 -0
- 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 +3 -15
- 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 +1 -1
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +4 -27
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/adapters.js +6 -17
- package/src/lib/mutation/dotnet-introspection.js +20 -106
- package/src/lib/mutation/runner.js +23 -75
- package/src/lib/mutation/subset.js +1 -4
- package/src/lib/mutation/trx.js +0 -1
- package/src/lib/plan/artifact-vocabulary.js +5 -25
- package/src/lib/plan/checks.js +34 -33
- package/src/lib/plan/plan-parser.js +7 -8
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +11 -26
- package/src/lib/risk-detector.js +41 -173
- package/src/lib/specs/adr-index.js +6 -34
- package/src/lib/specs/frontmatter.js +2 -15
- package/src/lib/specs/gfm-table.js +1 -7
- package/src/lib/specs/revocations.js +1 -14
- package/src/lib/specs/screen-checklist.js +47 -124
- package/src/lib/stack/dotnet-build-ignores.js +2 -6
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +1 -8
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +1 -7
- package/src/lib/standards/sections.js +2 -13
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +21 -169
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -240
- package/src/lib/tasks/test-runner.js +43 -341
- package/src/lib/telemetry/metrics.js +7 -36
- package/src/lib/telemetry/telemetry-reader.js +13 -125
- package/src/lib/telemetry/verify-event.js +2 -30
- package/src/lib/tests-audit/audit.js +96 -0
- 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 +2 -24
- 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 +11 -101
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +5 -63
- package/src/lib/validators/validation-runner.js +21 -158
- package/src/lib/validators/verify-runner.js +85 -376
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -136
- 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 +13 -95
- 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 -147
- package/src/utils/agents-installer.js +29 -276
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -52
- package/src/utils/file-copier.js +11 -95
- package/src/utils/hooks-installer.js +21 -298
- package/src/utils/skills-installer.js +4 -101
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -9
- package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
- package/framework/hooks/git/pre-commit/agents.sh +0 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
- package/framework/hooks/git/pre-push/run-tests.sh +0 -44
- package/src/commands/utils/index.js +0 -3
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/old-format.js +0 -68
- package/src/utils/agent-id-mapper.js +0 -11
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Scaffold Command - Generate feature files from templates.
|
|
3
|
-
*
|
|
4
|
-
* Sub-commands:
|
|
5
|
-
* - scaffold dotnet <feature> <slice-name> — VSA feature slice (.NET)
|
|
6
|
-
* - scaffold nextjs <feature> — Feature page + components (Next.js)
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
1
|
import chalk from 'chalk';
|
|
10
2
|
import { relative } from 'path';
|
|
11
3
|
import { logger } from '../../utils/logger.js';
|
|
@@ -17,11 +9,6 @@ import {
|
|
|
17
9
|
|
|
18
10
|
const KEBAB_REGEX = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
19
11
|
|
|
20
|
-
/**
|
|
21
|
-
* Validate that a name is valid kebab-case.
|
|
22
|
-
* @param {string} name
|
|
23
|
-
* @param {string} label
|
|
24
|
-
*/
|
|
25
12
|
function validateKebabCase(name, label) {
|
|
26
13
|
if (!KEBAB_REGEX.test(name)) {
|
|
27
14
|
logger.error(`Invalid ${label}: "${name}"`);
|
|
@@ -30,11 +17,6 @@ function validateKebabCase(name, label) {
|
|
|
30
17
|
}
|
|
31
18
|
}
|
|
32
19
|
|
|
33
|
-
/**
|
|
34
|
-
* Log the result of a scaffold operation.
|
|
35
|
-
* @param {Array<{path: string, skipped: boolean}>} files
|
|
36
|
-
* @param {string} outputDir
|
|
37
|
-
*/
|
|
38
20
|
function logResult(files, outputDir) {
|
|
39
21
|
let created = 0;
|
|
40
22
|
let skipped = 0;
|
|
@@ -56,16 +38,12 @@ function logResult(files, outputDir) {
|
|
|
56
38
|
logger.info(parts.join(', '));
|
|
57
39
|
}
|
|
58
40
|
|
|
59
|
-
/**
|
|
60
|
-
* Scaffold .NET VSA feature slice.
|
|
61
|
-
*/
|
|
62
41
|
export async function scaffoldDotnetCommand(feature, sliceName, options) {
|
|
63
42
|
validateKebabCase(feature, 'feature name');
|
|
64
43
|
validateKebabCase(sliceName, 'slice name');
|
|
65
44
|
|
|
66
45
|
const outputDir = options.outputDir || process.cwd();
|
|
67
46
|
|
|
68
|
-
// Detect or use provided namespace
|
|
69
47
|
const namespace = options.namespace || detectNamespace(outputDir);
|
|
70
48
|
if (!namespace) {
|
|
71
49
|
logger.error('Could not detect namespace');
|
|
@@ -100,9 +78,6 @@ export async function scaffoldDotnetCommand(feature, sliceName, options) {
|
|
|
100
78
|
}
|
|
101
79
|
}
|
|
102
80
|
|
|
103
|
-
/**
|
|
104
|
-
* Scaffold Next.js feature page with components and hooks.
|
|
105
|
-
*/
|
|
106
81
|
export async function scaffoldNextjsCommand(feature, options) {
|
|
107
82
|
validateKebabCase(feature, 'feature name');
|
|
108
83
|
|
|
@@ -14,44 +14,17 @@ import { seedDotnetBuildIgnores } from '../../lib/stack/dotnet-build-ignores.js'
|
|
|
14
14
|
|
|
15
15
|
const FRAMEWORK_DIR = fileURLToPath(new URL('../../../framework/', import.meta.url));
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
* `morph-spec advance <feature>` — approve the current phase's gate AND scaffold
|
|
19
|
-
* the next phase's folder in one step. This is the sanctioned, one-command way
|
|
20
|
-
* to move a feature forward: it removes the need to hand-edit state.json (gate)
|
|
21
|
-
* AND the need to `mkdir` the next phase folder by hand. Once the gate is
|
|
22
|
-
* approved, the enforce-phase-writes hook permits writing the next phase's first
|
|
23
|
-
* canonical artifact (which then advances the derived phase).
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
// The gate approved when *completing* a given phase. `implement` has no gate
|
|
27
|
-
// (it ends when all slices are done), so advancing from implement just
|
|
28
|
-
// scaffolds the review folder.
|
|
17
|
+
// `implement` has no gate: advancing from it only scaffolds the review folder.
|
|
29
18
|
const PHASE_GATE = { plan: 'plan', uiux: 'uiux', review: 'review' };
|
|
30
19
|
|
|
31
|
-
// The next phase folder to scaffold when advancing FROM a given phase. The
|
|
32
|
-
// default forward path skips the optional design phase (plan → implement); pass
|
|
33
|
-
// --design to take the uiux branch (plan → 1-design).
|
|
34
20
|
const NEXT_PHASE_DIR = {
|
|
35
21
|
plan: '3-implement',
|
|
36
22
|
uiux: '3-implement',
|
|
37
23
|
implement: '4-review',
|
|
38
24
|
};
|
|
39
25
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
* `framework/phases.json → phases[phase].requiredOutputs`, resolved to real
|
|
43
|
-
* filenames through OUTPUT_SCHEMA.
|
|
44
|
-
*
|
|
45
|
-
* Data-driven on purpose: adding an artifact to a phase must not need a code
|
|
46
|
-
* change here, and the list must never drift from the phase definition.
|
|
47
|
-
* Fail-open — an unreadable phases.json/OUTPUT_SCHEMA yields an empty list, so
|
|
48
|
-
* a broken install degrades to today's behaviour instead of blocking every
|
|
49
|
-
* advance.
|
|
50
|
-
*
|
|
51
|
-
* @param {string} phase
|
|
52
|
-
* @param {string} featurePath
|
|
53
|
-
* @returns {string[]} relative paths of the MISSING required artifacts
|
|
54
|
-
*/
|
|
26
|
+
// Data-driven from phases.json so the list never drifts from the phase definition. Fail-open:
|
|
27
|
+
// an unreadable phases.json yields an empty list instead of blocking every advance.
|
|
55
28
|
function missingPhaseArtifacts(phase, featurePath) {
|
|
56
29
|
try {
|
|
57
30
|
const phasesPath = join(FRAMEWORK_DIR, 'phases.json');
|
|
@@ -59,8 +32,7 @@ function missingPhaseArtifacts(phase, featurePath) {
|
|
|
59
32
|
const required = phases[phase]?.requiredOutputs || [];
|
|
60
33
|
const missing = [];
|
|
61
34
|
for (const outputType of required) {
|
|
62
|
-
//
|
|
63
|
-
// unknown key is skipped rather than guessed at.
|
|
35
|
+
// An unknown key is skipped rather than guessed at.
|
|
64
36
|
const entry = OUTPUT_SCHEMA[outputType];
|
|
65
37
|
if (!entry?.filename || !entry?.phaseDir) continue;
|
|
66
38
|
const rel = `${entry.phaseDir}/${entry.filename}`;
|
|
@@ -79,20 +51,8 @@ function unfinishedSliceReports(featurePath) {
|
|
|
79
51
|
return progress.slices.filter((s) => s.status !== 'feito').map((s) => `3-implement/reports/${s.id}.md`);
|
|
80
52
|
}
|
|
81
53
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
* worktree that owns this feature. `feature.json` travels committed on
|
|
85
|
-
* `morph/{feature}`, so a feature developed in a worktree has its artifacts
|
|
86
|
-
* there and only a scaffold in the primary root — running `advance` from the
|
|
87
|
-
* root then demanded a file that already exists, one directory over.
|
|
88
|
-
*
|
|
89
|
-
* Message-only: this never reads state for a decision and never writes across
|
|
90
|
-
* trees. It replaces a misleading error with an accurate one.
|
|
91
|
-
*
|
|
92
|
-
* Fail-open — no git, no worktree, or any error yields null (today's message).
|
|
93
|
-
*
|
|
94
|
-
* @returns {Promise<{path: string, branch: string|null, present: string[]}|null>}
|
|
95
|
-
*/
|
|
54
|
+
// Message-only and fail-open: names the worktree that holds the artifacts instead of demanding
|
|
55
|
+
// a file that exists one directory over. Never writes across trees.
|
|
96
56
|
async function artifactsLiveInWorktree(featureName, missing) {
|
|
97
57
|
try {
|
|
98
58
|
const { findWorktreeForFeature } = await import('../project/worktree.js');
|
|
@@ -107,7 +67,6 @@ async function artifactsLiveInWorktree(featureName, missing) {
|
|
|
107
67
|
}
|
|
108
68
|
}
|
|
109
69
|
|
|
110
|
-
/** Resolve who approved, honestly (mirrors approve.js — no fake human signatures). */
|
|
111
70
|
function resolveApprovedBy(options) {
|
|
112
71
|
if (options.approver) return options.approver;
|
|
113
72
|
if (options.mode === 'auto') return 'auto';
|
|
@@ -133,9 +92,7 @@ export async function advanceCommand(featureName, options = {}) {
|
|
|
133
92
|
process.exit(1);
|
|
134
93
|
}
|
|
135
94
|
|
|
136
|
-
//
|
|
137
|
-
// never stamp a gate onto the root's birth-instant skeleton (branch-drift
|
|
138
|
-
// created by this command, not merely left behind by one).
|
|
95
|
+
// A live worktree elsewhere owns the state: never stamp a gate onto the root skeleton.
|
|
139
96
|
const owner = assertOwnerTree(featureName, process.cwd());
|
|
140
97
|
if (owner.blocked) {
|
|
141
98
|
for (const { tone, text } of ownerTreeRefusalLines(featureName, owner.tree, owner.branch, `morph-spec advance ${featureName}`)) {
|
|
@@ -169,17 +126,10 @@ export async function advanceCommand(featureName, options = {}) {
|
|
|
169
126
|
return;
|
|
170
127
|
}
|
|
171
128
|
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
// thing that keeps a feature with unfinished slice reports out of review.
|
|
175
|
-
//
|
|
176
|
-
// `--force` bypasses (same deliberate-recovery role it already plays for
|
|
177
|
-
// gate order) but still names what was skipped — a silent bypass would be
|
|
178
|
-
// the defect again.
|
|
129
|
+
// `implement` has no gate, so this is what keeps unfinished slice reports out of review.
|
|
130
|
+
// `--force` bypasses it but still names what was skipped.
|
|
179
131
|
const missingArtifacts = missingPhaseArtifacts(phase, featurePath);
|
|
180
132
|
if (missingArtifacts.length && !options.force) {
|
|
181
|
-
// Divergence, not absence: name the tree that actually holds the work
|
|
182
|
-
// instead of asking for a file that exists in it.
|
|
183
133
|
const elsewhere = await artifactsLiveInWorktree(featureName, missingArtifacts);
|
|
184
134
|
if (elsewhere) {
|
|
185
135
|
console.error(chalk.red(`\n❌ A fase "${phase}" de "${featureName}" não pode ser encerrada A PARTIR DESTA ÁRVORE.`));
|
|
@@ -199,16 +149,10 @@ export async function advanceCommand(featureName, options = {}) {
|
|
|
199
149
|
process.exit(1);
|
|
200
150
|
}
|
|
201
151
|
|
|
202
|
-
// 1) Approve the gate that completes the current phase.
|
|
203
152
|
const gate = PHASE_GATE[phase];
|
|
204
153
|
const approvedBy = resolveApprovedBy(options);
|
|
205
154
|
|
|
206
|
-
// Gate monotonicity
|
|
207
|
-
// so it must not stamp gate N while a predecessor is pending. In the
|
|
208
|
-
// sanctioned flow advance is always in-order, but an anomalous filesystem
|
|
209
|
-
// state — e.g. 1-design/design-system.md present without the plan gate —
|
|
210
|
-
// would otherwise let advance deepen the violation by stamping uiux.
|
|
211
|
-
// Escape via --force for deliberate recovery.
|
|
155
|
+
// Gate monotonicity: never stamp gate N while a predecessor is pending (`--force` to recover).
|
|
212
156
|
if (gate) {
|
|
213
157
|
const unmet = unmetPrereqs(gate, feature.approvalGates);
|
|
214
158
|
if (unmet.length && !options.force) {
|
|
@@ -218,19 +162,8 @@ export async function advanceCommand(featureName, options = {}) {
|
|
|
218
162
|
}
|
|
219
163
|
}
|
|
220
164
|
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
// refresh it — it destroys `approvedBy`, `timestamp`, `notes` and
|
|
224
|
-
// `selfAssessed` in one assignment. And `resolveApprovedBy` above answers
|
|
225
|
-
// "auto" in every headless context, so `approve f plan --approver
|
|
226
|
-
// lucas` followed by `advance f` used to rewrite a human's signature as
|
|
227
|
-
// "auto" — the one stamp the charter forbids on a human-approved gate.
|
|
228
|
-
// The state-sync hook already carries exactly this guard; `advance`, the
|
|
229
|
-
// other gate write-path, simply never got it.
|
|
230
|
-
//
|
|
231
|
-
// It skips ONLY the write. The scaffold and the handoff below still run,
|
|
232
|
-
// so re-running `advance` after a failed scaffold keeps working — the
|
|
233
|
-
// reason this is a guard and not an abort.
|
|
165
|
+
// `setApprovalGate` replaces the gate whole: rewriting an approved gate would turn a human
|
|
166
|
+
// signature into "auto". Skips ONLY the write, so the scaffold and handoff below still run.
|
|
234
167
|
const priorGate = gate ? feature.approvalGates?.[gate] : null;
|
|
235
168
|
const alreadyApproved = priorGate?.approved === true;
|
|
236
169
|
|
|
@@ -250,7 +183,6 @@ export async function advanceCommand(featureName, options = {}) {
|
|
|
250
183
|
});
|
|
251
184
|
}
|
|
252
185
|
|
|
253
|
-
// 2) Scaffold the next phase folder so the agent can write its first artifact.
|
|
254
186
|
let nextDir = NEXT_PHASE_DIR[phase];
|
|
255
187
|
if (phase === 'plan' && options.design) nextDir = '1-design';
|
|
256
188
|
if (nextDir) {
|
|
@@ -260,9 +192,7 @@ export async function advanceCommand(featureName, options = {}) {
|
|
|
260
192
|
if (nextDir === '1-design') reportDesignSeed(seedDesignArtifacts(featureName, process.cwd()));
|
|
261
193
|
if (phase === 'plan') reportDotnetIgnores(seedDotnetBuildIgnores(process.cwd(), { featureName }));
|
|
262
194
|
|
|
263
|
-
//
|
|
264
|
-
// feature over (to a new instance, or to itself after /compact) — refresh
|
|
265
|
-
// the derived handoff.md so the next reader starts from the new phase.
|
|
195
|
+
// A phase transition is when a session hands the feature over: refresh handoff.md.
|
|
266
196
|
try {
|
|
267
197
|
const { writeHandoff } = await import('../../lib/generators/handoff-generator.js');
|
|
268
198
|
writeHandoff(featureName, process.cwd());
|
|
@@ -276,8 +206,7 @@ export async function advanceCommand(featureName, options = {}) {
|
|
|
276
206
|
const by = options.force ? '--force' : `aprovado por ${approvedBy}`;
|
|
277
207
|
console.log(chalk.yellow(` ⚠️ ${by}: avançou com o ${checklistBlock}.`));
|
|
278
208
|
}
|
|
279
|
-
//
|
|
280
|
-
// was written is how the destroyed signature stayed invisible.
|
|
209
|
+
// Never claim "approved (auto)" when nothing was written.
|
|
281
210
|
if (gate && alreadyApproved) {
|
|
282
211
|
console.log(chalk.gray(` Gate "${gate}" was already approved (${priorGate.approvedBy || 'unknown'}) — approval preserved, nothing re-written.`));
|
|
283
212
|
} else if (gate) {
|
|
@@ -19,16 +19,10 @@ export { REVIEWED_SHA_TOKEN };
|
|
|
19
19
|
|
|
20
20
|
const SIGNED_OFF = { required: [], reports: {}, checks: { status: 'ok', total: 0, missing: [], notProven: [] } };
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* is compared; the worst answer wins.
|
|
25
|
-
*
|
|
26
|
-
* @param {ReturnType<typeof reviewReports>} reports
|
|
27
|
-
* @returns {{ status: 'ok'|'stale'|'unstamped'|'skip', file?: string, reviewed?: string, head?: string, ahead?: number }}
|
|
28
|
-
* `skip`: no verifier report or no git repo. Never a false refusal.
|
|
29
|
-
*/
|
|
22
|
+
// The worst answer across every report present wins. `skip` (no report at all, no git) never
|
|
23
|
+
// refuses falsely.
|
|
30
24
|
function reviewStaleness(reports) {
|
|
31
|
-
if (reports.
|
|
25
|
+
if (Object.values(reports).every((r) => r.status === 'missing')) return { status: 'skip' };
|
|
32
26
|
const cwd = process.cwd();
|
|
33
27
|
let head;
|
|
34
28
|
try {
|
|
@@ -64,9 +58,6 @@ function reviewStaleness(reports) {
|
|
|
64
58
|
return unstamped ?? { status: 'ok', head };
|
|
65
59
|
}
|
|
66
60
|
|
|
67
|
-
/**
|
|
68
|
-
* Unapprove action function - Revoke a previously approved gate
|
|
69
|
-
*/
|
|
70
61
|
export async function unapproveCommand(featureName, gateName, options = {}) {
|
|
71
62
|
try {
|
|
72
63
|
if (!VALID_GATES.includes(gateName)) {
|
|
@@ -95,8 +86,7 @@ export async function unapproveCommand(featureName, gateName, options = {}) {
|
|
|
95
86
|
return;
|
|
96
87
|
}
|
|
97
88
|
|
|
98
|
-
//
|
|
99
|
-
// the gate when there's no interactive shell behind the invocation.
|
|
89
|
+
// Don't pretend a human revoked the gate without an interactive shell behind the call.
|
|
100
90
|
let revokedBy;
|
|
101
91
|
if (options.revoker) {
|
|
102
92
|
revokedBy = options.revoker;
|
|
@@ -123,44 +113,25 @@ export async function unapproveCommand(featureName, gateName, options = {}) {
|
|
|
123
113
|
}
|
|
124
114
|
}
|
|
125
115
|
|
|
126
|
-
/**
|
|
127
|
-
* Approval Commands - Manage approval gates for MORPH workflow
|
|
128
|
-
*
|
|
129
|
-
* Commands:
|
|
130
|
-
* - approve: Mark a gate as approved
|
|
131
|
-
* - unapprove: Revoke a gate approval
|
|
132
|
-
* - approval-status: Show approval status for all gates
|
|
133
|
-
*/
|
|
134
|
-
|
|
135
116
|
const program = new Command();
|
|
136
117
|
|
|
137
|
-
/**
|
|
138
|
-
* Valid approval gate names
|
|
139
|
-
*/
|
|
140
118
|
const VALID_GATES = ['plan', 'uiux', 'review'];
|
|
141
119
|
|
|
142
|
-
/**
|
|
143
|
-
* Approve action function - Mark a gate as approved
|
|
144
|
-
*/
|
|
145
120
|
export async function approveCommand(featureName, gateName, options = {}) {
|
|
146
121
|
try {
|
|
147
|
-
// Validate gate name
|
|
148
122
|
if (!VALID_GATES.includes(gateName)) {
|
|
149
123
|
console.error(chalk.red(`\n❌ Invalid gate: ${gateName}`));
|
|
150
124
|
console.log(chalk.gray(`Valid gates: ${VALID_GATES.join(', ')}\n`));
|
|
151
125
|
process.exit(1);
|
|
152
126
|
}
|
|
153
127
|
|
|
154
|
-
// Get feature state
|
|
155
128
|
const feature = getFeature(featureName);
|
|
156
129
|
if (!feature) {
|
|
157
130
|
console.error(chalk.red(`\n❌ Feature not found: ${featureName}\n`));
|
|
158
131
|
process.exit(1);
|
|
159
132
|
}
|
|
160
133
|
|
|
161
|
-
//
|
|
162
|
-
// never stamp a gate onto the root's birth-instant skeleton (branch-drift
|
|
163
|
-
// created by this command, not merely left behind by one).
|
|
134
|
+
// A live worktree elsewhere owns the state: never stamp a gate onto the root skeleton.
|
|
164
135
|
const owner = assertOwnerTree(featureName, process.cwd());
|
|
165
136
|
if (owner.blocked) {
|
|
166
137
|
for (const { tone, text } of ownerTreeRefusalLines(featureName, owner.tree, owner.branch, `morph-spec approve ${featureName} ${gateName}`)) {
|
|
@@ -169,27 +140,18 @@ export async function approveCommand(featureName, gateName, options = {}) {
|
|
|
169
140
|
process.exit(1);
|
|
170
141
|
}
|
|
171
142
|
|
|
172
|
-
// Check if already approved
|
|
173
143
|
const currentStatus = getApprovalGate(featureName, gateName);
|
|
174
144
|
if (currentStatus?.approved) {
|
|
175
145
|
console.log(chalk.yellow(`\n⚠️ Gate "${gateName}" is already approved`));
|
|
176
146
|
console.log(chalk.gray(`Approved at: ${currentStatus.timestamp}`));
|
|
177
147
|
console.log(chalk.gray(`Approved by: ${currentStatus.approvedBy || 'Unknown'}`));
|
|
178
|
-
//
|
|
179
|
-
// `--force` does NOT cover it (that flag only covers gate ORDER). A
|
|
180
|
-
// caller who meant to re-approve therefore reads success and is left
|
|
181
|
-
// with nowhere to go. The way out already exists as `unapprove`; the
|
|
182
|
-
// message just never named it. Naming it is the whole fix (no new flag).
|
|
148
|
+
// `--force` does not cover this no-op (it only covers gate ORDER), so name the way out.
|
|
183
149
|
console.log(chalk.gray(`To approve it again, revoke it first: morph-spec unapprove ${featureName} ${gateName}\n`));
|
|
184
150
|
if (gateName === 'plan') reportDotnetIgnores(seedDotnetBuildIgnores(process.cwd(), { featureName }));
|
|
185
151
|
return;
|
|
186
152
|
}
|
|
187
153
|
|
|
188
|
-
// Gate monotonicity:
|
|
189
|
-
// approved (plan → review; uiux requires plan). Prevents the shape of a
|
|
190
|
-
// review approved with the plan still pending. Policy-stamped
|
|
191
|
-
// gates count as approved, so chore/hotfix satisfy prereqs naturally.
|
|
192
|
-
// Escape via --force for deliberate recovery.
|
|
154
|
+
// Gate monotonicity: predecessors first; policy stamps count (`--force` to recover).
|
|
193
155
|
const unmet = unmetPrereqs(gateName, feature.approvalGates);
|
|
194
156
|
if (unmet.length && !options.force) {
|
|
195
157
|
console.error(chalk.red(`\n❌ Cannot approve "${gateName}" — prerequisite gate(s) not approved: ${unmet.join(', ')}.`));
|
|
@@ -197,22 +159,8 @@ export async function approveCommand(featureName, gateName, options = {}) {
|
|
|
197
159
|
process.exit(1);
|
|
198
160
|
}
|
|
199
161
|
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
// approvedBy resolution (most explicit wins):
|
|
204
|
-
// 1. --approver flag (CLI caller signalled who approved)
|
|
205
|
-
// 2. --mode auto → "auto" (intentionally non-human signature)
|
|
206
|
-
// 3. interactive shell (TTY on stdin) → $USER / $USERNAME (a real
|
|
207
|
-
// human is at the terminal; their name is the honest answer)
|
|
208
|
-
// 4. non-interactive (no TTY) → "auto"
|
|
209
|
-
//
|
|
210
|
-
// The TTY check exists because morph-spec is increasingly invoked
|
|
211
|
-
// from headless contexts (claude -p subprocesses, CI, the
|
|
212
|
-
// morph-e2e-tester). Stamping the host's $USERNAME on a gate that
|
|
213
|
-
// no human actually approved is misleading — it lies to the audit
|
|
214
|
-
// trail. "auto" reads as what it is: a deterministic policy gate,
|
|
215
|
-
// not a human signing off.
|
|
162
|
+
// Resolved BEFORE the hotfix backstop, which gates on the signature. Without a TTY the answer
|
|
163
|
+
// is "auto": stamping $USERNAME on a gate no human approved lies to the audit trail.
|
|
216
164
|
let approvedBy;
|
|
217
165
|
if (options.approver) {
|
|
218
166
|
approvedBy = options.approver;
|
|
@@ -230,16 +178,8 @@ export async function approveCommand(featureName, gateName, options = {}) {
|
|
|
230
178
|
process.exit(1);
|
|
231
179
|
}
|
|
232
180
|
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
// inverted the default of a business rule; the report stayed in the repo
|
|
236
|
-
// claiming 976/976 tests (they were 982) and never mentioning T3. Nothing
|
|
237
|
-
// detected that the review artifact had fallen behind the code it reviews.
|
|
238
|
-
//
|
|
239
|
-
// The report stamps the SHA it reviewed; approval compares it with HEAD.
|
|
240
|
-
// A stamp that is BEHIND HEAD refuses. A MISSING stamp only warns — reports
|
|
241
|
-
// written before this existed must not brick a legacy feature — but the
|
|
242
|
-
// warning names the exact token to add, so it teaches instead of nagging.
|
|
181
|
+
// A review reviews a TREE: a `Reviewed-SHA` behind HEAD refuses. A missing stamp only warns,
|
|
182
|
+
// naming the token to add, so a legacy report does not brick the feature.
|
|
243
183
|
const reports = gateName === 'review' ? reviewReports(process.cwd(), featureName) : null;
|
|
244
184
|
if (gateName === 'review') {
|
|
245
185
|
const stale = reviewStaleness(reports);
|
|
@@ -290,9 +230,8 @@ export async function approveCommand(featureName, gateName, options = {}) {
|
|
|
290
230
|
selfAssessed = true;
|
|
291
231
|
}
|
|
292
232
|
|
|
293
|
-
// Sem gate-check prévio não há `gateDecisions.review` para o backstop
|
|
294
|
-
//
|
|
295
|
-
// a decisão pela MESMA régua do gate-check. --self-assessed já dispensou os relatórios.
|
|
233
|
+
// Sem gate-check prévio não há `gateDecisions.review` para o backstop morder: a auto-aprovação
|
|
234
|
+
// recalcula a decisão pela MESMA régua do gate-check. --self-assessed já dispensou os relatórios.
|
|
296
235
|
if (approvedBy === 'auto') {
|
|
297
236
|
const signals = collectSignals(featureName, feature, process.cwd(), 'review');
|
|
298
237
|
if (selfAssessed) signals.reviewReports = SIGNED_OFF;
|
|
@@ -321,12 +260,7 @@ export async function approveCommand(featureName, gateName, options = {}) {
|
|
|
321
260
|
...(selfAssessed ? { selfAssessed: true } : {}),
|
|
322
261
|
});
|
|
323
262
|
|
|
324
|
-
//
|
|
325
|
-
// feature hands over between sessions too — `approve` is the real path
|
|
326
|
-
// the plan gate took in the field (`approve <f> plan --mode auto`), not just
|
|
327
|
-
// `advance`, so a handoff.md left stale after approve was the common
|
|
328
|
-
// case, not the edge case. Same helper, same fail-open contract: a
|
|
329
|
-
// regeneration failure must never fail the approval itself.
|
|
263
|
+
// Approving is a session handoff too; a regeneration failure must never fail the approval.
|
|
330
264
|
try {
|
|
331
265
|
const { writeHandoff } = await import('../../lib/generators/handoff-generator.js');
|
|
332
266
|
writeHandoff(featureName, process.cwd());
|
|
@@ -347,28 +281,20 @@ export async function approveCommand(featureName, gateName, options = {}) {
|
|
|
347
281
|
}
|
|
348
282
|
}
|
|
349
283
|
|
|
350
|
-
/**
|
|
351
|
-
* Approve command - Mark a gate as approved
|
|
352
|
-
*/
|
|
353
284
|
program
|
|
354
285
|
.command('approve <feature> <gate>')
|
|
355
286
|
.description('Approve a phase gate to allow workflow progression')
|
|
356
287
|
.action(approveCommand);
|
|
357
288
|
|
|
358
|
-
/**
|
|
359
|
-
* Approval status action function - Show all gates and their status
|
|
360
|
-
*/
|
|
361
289
|
export async function approvalStatusCommand(featureName, options = {}) {
|
|
362
290
|
try {
|
|
363
|
-
// Get feature state
|
|
364
291
|
const feature = getFeature(featureName);
|
|
365
292
|
if (!feature) {
|
|
366
293
|
console.error(chalk.red(`\n❌ Feature not found: ${featureName}\n`));
|
|
367
294
|
process.exit(1);
|
|
368
295
|
}
|
|
369
296
|
|
|
370
|
-
//
|
|
371
|
-
// approved gates (same as `morph-spec status`), never print "unknown".
|
|
297
|
+
// Phase derived from the filesystem + approved gates, never "unknown".
|
|
372
298
|
const featurePath = join(process.cwd(), '.morph', 'features', featureName);
|
|
373
299
|
const phase = feature.phase || derivePhase(featurePath);
|
|
374
300
|
|
|
@@ -377,7 +303,6 @@ export async function approvalStatusCommand(featureName, options = {}) {
|
|
|
377
303
|
const uiuxGate = getApprovalGate(featureName, 'uiux');
|
|
378
304
|
const uiuxTaken = uiuxGate?.approved === true || existsSync(join(featurePath, '1-design'));
|
|
379
305
|
|
|
380
|
-
// JSON output
|
|
381
306
|
if (options.json) {
|
|
382
307
|
const status = {
|
|
383
308
|
feature: featureName,
|
|
@@ -398,11 +323,9 @@ export async function approvalStatusCommand(featureName, options = {}) {
|
|
|
398
323
|
console.log(chalk.bold(`\n📋 Approval Status for: ${featureName}`));
|
|
399
324
|
console.log('━'.repeat(60));
|
|
400
325
|
|
|
401
|
-
// Show current phase
|
|
402
326
|
console.log(chalk.cyan(`\nCurrent Phase: ${phase}`));
|
|
403
327
|
console.log(chalk.gray(`Feature Status: ${feature.status || 'unknown'}\n`));
|
|
404
328
|
|
|
405
|
-
// Show each gate
|
|
406
329
|
console.log(chalk.bold('Approval Gates:'));
|
|
407
330
|
|
|
408
331
|
for (const gateName of VALID_GATES) {
|
|
@@ -444,7 +367,6 @@ export async function approvalStatusCommand(featureName, options = {}) {
|
|
|
444
367
|
}
|
|
445
368
|
}
|
|
446
369
|
|
|
447
|
-
// Show approval history if available
|
|
448
370
|
const history = getApprovalHistory(featureName);
|
|
449
371
|
if (history && history.length > 0) {
|
|
450
372
|
console.log(chalk.bold('\nApproval History:'));
|
|
@@ -462,16 +384,12 @@ export async function approvalStatusCommand(featureName, options = {}) {
|
|
|
462
384
|
}
|
|
463
385
|
}
|
|
464
386
|
|
|
465
|
-
/**
|
|
466
|
-
* Approval status command - Show all gates and their status
|
|
467
|
-
*/
|
|
468
387
|
program
|
|
469
388
|
.command('approval-status <feature>')
|
|
470
389
|
.alias('status')
|
|
471
390
|
.description('Show approval status for all gates')
|
|
472
391
|
.action(approvalStatusCommand);
|
|
473
392
|
|
|
474
|
-
// Only parse if run directly (not imported as module)
|
|
475
393
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
476
394
|
if (process.argv.length > 2) {
|
|
477
395
|
program.parse(process.argv);
|