@polymorphism-tech/morph-spec 8.46.0 → 9.0.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 +53 -44
- package/README.md +3 -4
- package/bin/morph-spec.js +38 -55
- package/bin/validate.js +5 -134
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +2 -2
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +41 -101
- package/framework/MORPH.md +95 -252
- package/framework/agents.json +75 -18
- package/framework/commands/create-umbraco-lp.md +2 -0
- package/framework/commands/migrate-lp-to-umbraco.md +2 -0
- package/framework/commands/morph-apply.md +5 -28
- package/framework/commands/morph-archive.md +2 -2
- package/framework/commands/morph-hotfix.md +8 -8
- package/framework/commands/morph-infra.md +17 -203
- package/framework/commands/morph-media.md +3 -0
- package/framework/commands/morph-preflight.md +7 -31
- package/framework/commands/morph-proposal.md +2 -50
- package/framework/commands/morph-status.md +3 -3
- package/framework/commands/morph-troubleshoot.md +2 -14
- package/framework/docs/CLI.md +585 -286
- package/framework/evals/review-checklist.md +82 -41
- package/framework/hooks/README.md +10 -11
- package/framework/hooks/claude-code/core/comment-guard.js +1 -1
- package/framework/hooks/claude-code/core/denylist.json +33 -3
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +16 -20
- package/framework/hooks/claude-code/core/gate-guard.js +2 -11
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +38 -0
- package/framework/hooks/claude-code/core/protect-spec-files.js +30 -242
- package/framework/hooks/claude-code/core/state-sync.js +17 -19
- package/framework/hooks/claude-code/core/telemetry-log.js +3 -3
- package/framework/hooks/claude-code/core/validate-completion.js +33 -97
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +31 -0
- package/framework/hooks/claude-code/core/worktree-context.js +5 -4
- package/framework/hooks/claude-code/statusline.py +55 -41
- package/framework/hooks/package.json +3 -0
- package/framework/hooks/shared/activity-logger.js +1 -1
- package/framework/hooks/shared/build-check.js +10 -6
- package/framework/hooks/shared/command-tokens.js +169 -0
- package/framework/hooks/shared/comment-rules.js +1 -1
- package/framework/hooks/shared/comment-scanner.js +10 -23
- package/framework/hooks/shared/comment-stripper.js +1 -1
- package/framework/hooks/shared/e2e-check.js +67 -31
- package/framework/hooks/shared/errata.js +72 -0
- package/framework/hooks/shared/feature-locator.js +1 -1
- package/framework/hooks/shared/frontend-locations.js +6 -0
- package/framework/hooks/shared/gate-question.js +18 -0
- package/framework/hooks/shared/phase-utils.js +15 -32
- package/framework/hooks/shared/slice-progress.js +118 -0
- package/framework/hooks/shared/spawn-command.js +23 -0
- package/framework/hooks/shared/state-reader.js +61 -87
- package/framework/hooks/shared/state-writer.js +3 -3
- package/framework/mcp/registry.json +3 -4
- package/framework/phases.json +23 -39
- package/framework/rules/ai-agents-standards.md +18 -16
- package/framework/rules/asaas-standards.md +3 -1
- package/framework/rules/csharp-standards.md +10 -8
- package/framework/rules/data-standards.md +5 -5
- package/framework/rules/dotnet-templates.md +1 -1
- package/framework/rules/frontend-standards.md +4 -2
- package/framework/rules/gsap-standards.md +10 -5
- package/framework/rules/hangfire-standards.md +3 -1
- package/framework/rules/infrastructure-standards.md +4 -2
- package/framework/rules/neon-auth-standards.md +3 -1
- package/framework/rules/neon-standards.md +6 -4
- package/framework/rules/nextjs-standards.md +13 -11
- package/framework/rules/nextjs-templates.md +1 -1
- package/framework/rules/resend-standards.md +3 -1
- package/framework/schemas/config.schema.json +14 -9
- package/framework/schemas/feature.schema.json +15 -52
- package/framework/skills/README.md +4 -5
- package/framework/skills/core/create-umbraco-lp/SKILL.md +3 -3
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +3 -3
- package/framework/skills/core/morph-apply/SKILL.md +150 -223
- package/framework/skills/core/morph-hotfix/SKILL.md +34 -43
- package/framework/skills/core/morph-machine/SKILL.md +4 -4
- package/framework/skills/core/morph-media/SKILL.md +3 -3
- package/framework/skills/core/morph-plan/SKILL.md +138 -300
- package/framework/skills/core/morph-review/SKILL.md +112 -137
- package/framework/skills/core/morph-standards/SKILL.md +29 -41
- package/framework/skills/core/morph-uiux/SKILL.md +78 -153
- package/framework/standards/STANDARDS.json +90 -1219
- package/framework/standards/STANDARDS.meta.json +1305 -0
- package/framework/standards/ai-agents/evals-with-cache.md +2 -3
- package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
- package/framework/standards/ai-agents/modalities-speech-to-text.md +36 -26
- package/framework/standards/ai-agents/prompt-sources.md +6 -4
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +67 -39
- package/framework/standards/ai-agents/setup.md +2 -2
- package/framework/standards/ai-agents/structured-output.md +1 -1
- package/framework/standards/ai-agents/sweet-spot.md +1 -1
- package/framework/standards/architecture/code-comments.md +8 -8
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +18 -9
- package/framework/standards/backend/api/validation.md +135 -47
- package/framework/standards/backend/database/postgresql/database.md +11 -7
- package/framework/standards/backend/database/vector-search-rag.md +207 -324
- package/framework/standards/backend/dotnet/fluent-validation-vsa.md +14 -8
- package/framework/standards/backend/dotnet/result-pattern.md +40 -6
- package/framework/standards/backend/dotnet/scalar-ui.md +13 -7
- package/framework/standards/backend/dotnet/scrutor.md +1 -1
- package/framework/standards/backend/dotnet/testing.md +1 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +7 -7
- package/framework/standards/backend/integrations/asaas/asaas-api.md +309 -165
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +134 -154
- package/framework/standards/data/nosql/cache/redis.md +120 -44
- package/framework/standards/data/nosql/cosmos-db.md +114 -71
- package/framework/standards/frontend/design-system/animations.md +1 -1
- package/framework/standards/frontend/design-system/naming.md +1 -1
- package/framework/standards/frontend/nextjs/motion-patterns.md +45 -29
- package/framework/standards/frontend/nextjs/naming-conventions.md +1 -1
- package/framework/standards/infrastructure/docker/coolify-deploy.md +8 -6
- package/framework/standards/infrastructure/neon/neon-pgvector.md +120 -75
- package/framework/standards/infrastructure/neon/neon-setup.md +53 -31
- package/framework/standards/integration/mcp/mcp-tools.md +7 -7
- package/framework/templates/README.md +2 -3
- package/framework/templates/REGISTRY.json +6 -170
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +2 -2
- package/framework/templates/code/dotnet/extensions/database-extensions.cs +1 -1
- package/framework/templates/code/dotnet/extensions/result-extensions.cs +27 -0
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +2 -2
- package/framework/templates/docs/plan.md +72 -0
- package/framework/templates/dotnet/ai-kit/README.md +1 -9
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +1 -1
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
- package/framework/templates/infrastructure/docker/coolify.template.json +3 -3
- package/framework/templates/infrastructure/github/README.md +2 -1
- package/framework/templates/infrastructure/github/workflows/deploy-coolify.yml.hbs +2 -1
- 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/e2e/e2e.js +2 -2
- package/src/commands/observability/cost.js +18 -60
- package/src/commands/observability/telemetry.js +3 -5
- package/src/commands/project/doctor-procs.js +1 -1
- package/src/commands/project/doctor.js +9 -9
- package/src/commands/project/fleet.js +4 -4
- package/src/commands/project/heavy.js +3 -3
- package/src/commands/project/init.js +6 -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 +79 -25
- package/src/commands/project/status.js +29 -69
- package/src/commands/project/update.js +33 -19
- package/src/commands/project/worktree.js +1 -1
- package/src/commands/state/advance.js +62 -46
- package/src/commands/state/approve.js +95 -138
- package/src/commands/state/archive.js +20 -84
- package/src/commands/state/auto-approval.js +68 -0
- package/src/commands/state/create.js +28 -1
- package/src/commands/state/delete.js +1 -1
- package/src/commands/state/finish.js +11 -23
- package/src/commands/state/gate-check.js +86 -95
- package/src/commands/state/gate-decision.js +6 -4
- package/src/commands/state/handoff.js +1 -1
- package/src/commands/state/list.js +9 -9
- package/src/commands/state/report-check.js +157 -311
- package/src/commands/state/retype.js +9 -3
- package/src/commands/validation/mutate.js +77 -14
- package/src/commands/validation/plan-check.js +81 -0
- package/src/commands/validation/validate-feature.js +60 -135
- package/src/commands/validation/verify.js +41 -82
- package/src/core/paths/output-schema.js +20 -67
- package/src/core/state/gate-order.js +5 -6
- package/src/core/state/lifecycle-audit.js +2 -2
- package/src/core/state/state-manager.js +17 -98
- package/src/core/templates/template-registry.js +2 -2
- package/src/core/templates/template-seed.js +22 -0
- package/src/core/work-types.js +10 -10
- package/src/dashboard/app/App.jsx +0 -2
- package/src/dashboard/app/components/ActivityFeed.jsx +2 -3
- package/src/dashboard/app/components/CommandPalette.jsx +7 -8
- package/src/dashboard/app/components/EntityLink.jsx +1 -2
- package/src/dashboard/app/components/Markdown.jsx +1 -1
- package/src/dashboard/app/components/Sidebar.jsx +1 -2
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -1
- package/src/dashboard/app/lib/phases.js +2 -4
- package/src/dashboard/app/pages/Activity.jsx +1 -1
- package/src/dashboard/app/pages/FeatureDetail.jsx +139 -129
- package/src/dashboard/app/pages/Features.jsx +9 -9
- package/src/dashboard/app/pages/Guide.jsx +2 -2
- package/src/dashboard/app/pages/PhasePipeline.jsx +3 -4
- package/src/dashboard/app/pages/RulesMap.jsx +3 -3
- package/src/dashboard/app/pages/SkillsLibrary.jsx +3 -3
- package/src/dashboard/app/pages/Standards.jsx +1 -1
- package/src/dashboard/scanner/activity-scanner.js +6 -16
- package/src/dashboard/scanner/agents-scanner.js +1 -1
- package/src/dashboard/scanner/docs-scanner.js +5 -23
- package/src/dashboard/scanner/features-scanner.js +95 -60
- package/src/dashboard/scanner/index.js +0 -2
- package/src/dashboard/scanner/project-api.js +1 -1
- package/src/dashboard/scanner/rules-scanner.js +15 -10
- package/src/dashboard/scanner/skills-scanner.js +2 -3
- package/src/dashboard/scanner/standards-scanner.js +2 -1
- package/src/lib/ci/ci-verify.js +17 -1
- package/src/lib/ci/github-ci.js +1 -1
- package/src/lib/detectors/index.js +3 -8
- package/src/lib/generators/handoff-generator.js +34 -65
- package/src/lib/generators/settings-generator.js +11 -89
- package/src/lib/git/evidence-staleness.js +1 -1
- package/src/lib/git/tree-fingerprint.js +19 -7
- package/src/lib/git/tree-state.js +2 -2
- package/src/lib/heavy/cleanup.js +1 -5
- package/src/lib/installers/memory-installer.js +6 -8
- package/src/lib/mutation/commands.js +3 -2
- package/src/lib/{tasks/dotnet-test-introspection.js → mutation/dotnet-introspection.js} +17 -282
- package/src/lib/mutation/plan.js +14 -6
- package/src/lib/mutation/runner.js +1 -1
- package/src/lib/mutation/subset.js +46 -9
- package/src/lib/plan/artifact-vocabulary.js +60 -0
- package/src/lib/plan/checks.js +234 -0
- package/src/lib/plan/plan-parser.js +209 -0
- package/src/lib/plan/plan-template.js +18 -0
- package/src/lib/review-reports.js +222 -0
- package/src/lib/risk-detector.js +134 -135
- package/src/lib/specs/adr-index.js +50 -69
- package/src/lib/specs/design-seed.js +18 -0
- package/src/lib/specs/frontmatter.js +44 -0
- package/src/lib/specs/gfm-table.js +17 -0
- package/src/lib/specs/old-format.js +68 -0
- package/src/lib/specs/revocations.js +37 -95
- package/src/lib/specs/screen-checklist.js +18 -17
- package/src/lib/stack/dotnet-build-ignores.js +47 -0
- package/src/lib/stack/stack-profile.js +1 -1
- package/src/lib/stack/stack-scan.js +267 -0
- package/src/lib/standards/registry.js +43 -0
- package/src/lib/standards/sections.js +58 -0
- package/src/lib/tasks/eval-node.js +4 -12
- package/src/lib/tasks/test-nodes.js +1 -74
- package/src/lib/tasks/test-runner.js +75 -117
- package/src/lib/telemetry/metrics.js +8 -69
- package/src/lib/telemetry/telemetry-reader.js +5 -118
- package/src/lib/telemetry/verify-event.js +5 -7
- package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
- package/src/lib/validators/contracts/contract-compliance-validator.js +2 -2
- package/src/lib/validators/reuse/reuse-validator.js +24 -48
- package/src/lib/validators/reuse/symbol-scanner.js +2 -4
- package/src/lib/validators/traceability-check.js +85 -129
- package/src/lib/validators/validation-runner.js +8 -88
- package/src/lib/validators/verify-runner.js +134 -301
- package/src/lib/worktree/docker-audit.js +1 -2
- package/src/lib/worktree/fleet-scanner.js +25 -38
- package/src/scripts/setup-infra.js +42 -22
- package/src/utils/agents-installer.js +92 -13
- package/src/utils/claude-settings-manager.js +0 -35
- package/src/utils/file-copier.js +34 -6
- package/src/utils/hooks-installer.js +16 -9
- package/src/utils/skills-installer.js +1 -2
- package/framework/commands/commit.md +0 -166
- 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/rubrics/backend.json +0 -41
- package/framework/rubrics/default.json +0 -36
- package/framework/rubrics/frontend.json +0 -36
- 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/project/validate.js +0 -97
- 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/dashboard/app/pages/Rubrics.jsx +0 -97
- package/src/dashboard/scanner/rubrics-scanner.js +0 -12
- package/src/lib/agents/output-collector.js +0 -76
- package/src/lib/evaluator-report.js +0 -91
- 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/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/lib/validators/architecture/architecture-validator.js +0 -270
- package/src/lib/validators/architecture/index.js +0 -7
- package/src/lib/validators/css/css-validator.js +0 -340
- package/src/lib/validators/css/index.js +0 -7
- package/src/lib/validators/design-system/design-system-validator.js +0 -237
- package/src/lib/validators/design-system/index.js +0 -7
- package/src/lib/validators/score-aggregator.js +0 -244
- package/src/lib/validators/ui/index.js +0 -7
- package/src/lib/validators/ui/ui-contrast-validator.js +0 -422
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* MORPH-SPEC Feature Status Command
|
|
3
3
|
*
|
|
4
4
|
* Displays a comprehensive dashboard showing feature progress, phase status,
|
|
5
|
-
* outputs,
|
|
5
|
+
* outputs, slices, approvals, and next steps.
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
8
|
* morph-spec status <feature-name>
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import chalk from 'chalk';
|
|
16
16
|
import { loadState, getFeature, derivePhase, deriveOutputs } from '../../core/state/state-manager.js';
|
|
17
|
+
import { sliceProgress } from '../../../framework/hooks/shared/slice-progress.js';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Phase symbols for visual representation
|
|
@@ -29,13 +30,19 @@ const PHASE_SYMBOLS = {
|
|
|
29
30
|
* Phase definitions with order
|
|
30
31
|
*/
|
|
31
32
|
const PHASE_ORDER = [
|
|
32
|
-
{ key: '
|
|
33
|
-
{ key: 'uiux', name: 'FASE
|
|
34
|
-
{ key: '
|
|
35
|
-
{ key: '
|
|
36
|
-
{ key: 'review', name: 'FASE 4: REVIEW', order: 4 }
|
|
33
|
+
{ key: 'plan', name: 'FASE 1: PLAN', order: 0 },
|
|
34
|
+
{ key: 'uiux', name: 'FASE 2: UI/UX', order: 1, optional: true },
|
|
35
|
+
{ key: 'implement', name: 'FASE 3: IMPLEMENT', order: 2 },
|
|
36
|
+
{ key: 'review', name: 'FASE 4: REVIEW', order: 3 }
|
|
37
37
|
];
|
|
38
38
|
|
|
39
|
+
const PHASE_OUTPUTS = {
|
|
40
|
+
plan: ['plan', 'checks'],
|
|
41
|
+
uiux: ['uiDesignSystem', 'uiMockups', 'uiComponents', 'uiFlows'],
|
|
42
|
+
implement: [],
|
|
43
|
+
review: ['verifierReport', 'reviewerReport']
|
|
44
|
+
};
|
|
45
|
+
|
|
39
46
|
/**
|
|
40
47
|
* Calculate phase status (completed, current, pending, skipped)
|
|
41
48
|
*/
|
|
@@ -61,15 +68,7 @@ function getPhaseStatus(phaseKey, currentPhase, feature) {
|
|
|
61
68
|
* Check if phase has any outputs created
|
|
62
69
|
*/
|
|
63
70
|
function checkPhaseOutputs(phaseKey, feature) {
|
|
64
|
-
const
|
|
65
|
-
proposal: ['proposal', 'stackScan'],
|
|
66
|
-
uiux: ['uiDesignSystem', 'uiMockups', 'uiComponents', 'uiFlows'],
|
|
67
|
-
plan: ['spec', 'tasks', 'decisions'],
|
|
68
|
-
implement: ['recap'],
|
|
69
|
-
review: ['reviewReport']
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const outputs = phaseOutputMap[phaseKey] || [];
|
|
71
|
+
const outputs = PHASE_OUTPUTS[phaseKey] || [];
|
|
73
72
|
return outputs.some(output => feature.outputs[output]?.created);
|
|
74
73
|
}
|
|
75
74
|
|
|
@@ -99,15 +98,7 @@ function formatPhase(phaseKey, currentPhase, feature, options = {}) {
|
|
|
99
98
|
|
|
100
99
|
// Add output files if verbose
|
|
101
100
|
if (options.verbose && status !== 'pending' && status !== 'skipped') {
|
|
102
|
-
const
|
|
103
|
-
proposal: ['proposal', 'stackScan'],
|
|
104
|
-
uiux: ['uiDesignSystem', 'uiMockups', 'uiComponents', 'uiFlows'],
|
|
105
|
-
plan: ['spec', 'tasks', 'decisions'],
|
|
106
|
-
implement: ['recap'],
|
|
107
|
-
review: ['reviewReport']
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const outputs = phaseOutputMap[phaseKey] || [];
|
|
101
|
+
const outputs = PHASE_OUTPUTS[phaseKey] || [];
|
|
111
102
|
const created = outputs.filter(o => feature.outputs[o]?.created);
|
|
112
103
|
if (created.length > 0) {
|
|
113
104
|
display += chalk.gray(`\n Files: ${created.length}/${outputs.length}`);
|
|
@@ -121,7 +112,7 @@ function formatPhase(phaseKey, currentPhase, feature, options = {}) {
|
|
|
121
112
|
* Calculate completion percentage
|
|
122
113
|
*/
|
|
123
114
|
function calculateCompletionPercentage(feature) {
|
|
124
|
-
const totalPhases = PHASE_ORDER.filter(p => !p.optional).length;
|
|
115
|
+
const totalPhases = PHASE_ORDER.filter(p => !p.optional).length;
|
|
125
116
|
const completedPhases = PHASE_ORDER.filter(p => {
|
|
126
117
|
if (p.optional) return false;
|
|
127
118
|
const status = getPhaseStatus(p.key, feature.phase, feature);
|
|
@@ -139,9 +130,8 @@ function getNextSteps(feature) {
|
|
|
139
130
|
|
|
140
131
|
// Check for pending approvals relevant to current phase
|
|
141
132
|
const phaseApprovalMap = {
|
|
142
|
-
proposal: 'proposal',
|
|
143
|
-
uiux: 'uiux',
|
|
144
133
|
plan: 'plan',
|
|
134
|
+
uiux: 'uiux',
|
|
145
135
|
implement: 'plan',
|
|
146
136
|
review: 'review'
|
|
147
137
|
};
|
|
@@ -158,14 +148,7 @@ function getNextSteps(feature) {
|
|
|
158
148
|
}
|
|
159
149
|
|
|
160
150
|
// Check for missing outputs in current phase
|
|
161
|
-
const
|
|
162
|
-
proposal: ['proposal'],
|
|
163
|
-
plan: ['spec', 'tasks'],
|
|
164
|
-
implement: ['recap'],
|
|
165
|
-
review: ['reviewReport']
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const requiredOutputs = phaseOutputMap[feature.phase] || [];
|
|
151
|
+
const requiredOutputs = feature.phase === 'uiux' ? [] : PHASE_OUTPUTS[feature.phase] || [];
|
|
169
152
|
const missingOutputs = requiredOutputs.filter(o => !feature.outputs[o]?.created);
|
|
170
153
|
|
|
171
154
|
if (missingOutputs.length > 0) {
|
|
@@ -199,25 +182,6 @@ function getNextSteps(feature) {
|
|
|
199
182
|
return steps;
|
|
200
183
|
}
|
|
201
184
|
|
|
202
|
-
/**
|
|
203
|
-
* Estimate remaining time based on tasks
|
|
204
|
-
*/
|
|
205
|
-
function estimateRemainingTime(feature) {
|
|
206
|
-
if (!feature.tasks || feature.tasks.total === 0) {
|
|
207
|
-
return 'Not estimated';
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const remaining = feature.tasks.total - feature.tasks.completed;
|
|
211
|
-
const avgHoursPerTask = 2; // Configurable estimate
|
|
212
|
-
const hours = remaining * avgHoursPerTask;
|
|
213
|
-
|
|
214
|
-
if (hours < 1) return '< 1 hour';
|
|
215
|
-
if (hours === 1) return '1 hour';
|
|
216
|
-
if (hours < 8) return `${hours} hours`;
|
|
217
|
-
const days = Math.ceil(hours / 8);
|
|
218
|
-
return `~${days} day${days > 1 ? 's' : ''} (${hours}h)`;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
185
|
/**
|
|
222
186
|
* Main status command
|
|
223
187
|
*/
|
|
@@ -251,6 +215,7 @@ export async function statusCommand(featureName, options = {}) {
|
|
|
251
215
|
if (!feature.outputs) {
|
|
252
216
|
feature.outputs = deriveOutputs(featureName, process.cwd());
|
|
253
217
|
}
|
|
218
|
+
const slices = sliceProgress(featurePath);
|
|
254
219
|
|
|
255
220
|
// JSON output mode
|
|
256
221
|
if (options.json) {
|
|
@@ -270,10 +235,9 @@ export async function statusCommand(featureName, options = {}) {
|
|
|
270
235
|
created: output.created,
|
|
271
236
|
path: output.path
|
|
272
237
|
})),
|
|
273
|
-
|
|
238
|
+
slices,
|
|
274
239
|
approvals: feature.approvalGates || {},
|
|
275
|
-
nextSteps: getNextSteps(feature)
|
|
276
|
-
estimatedTimeRemaining: estimateRemainingTime(feature)
|
|
240
|
+
nextSteps: getNextSteps(feature)
|
|
277
241
|
};
|
|
278
242
|
|
|
279
243
|
console.log(JSON.stringify(status, null, 2));
|
|
@@ -315,17 +279,13 @@ export async function statusCommand(featureName, options = {}) {
|
|
|
315
279
|
}
|
|
316
280
|
console.log('');
|
|
317
281
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
console.log(`
|
|
325
|
-
console.log(chalk.green(` ✓ Completed: ${completed} (${percentage}%)`));
|
|
326
|
-
if (inProgress > 0) console.log(chalk.cyan(` ▶ In Progress: ${inProgress}`));
|
|
327
|
-
if (pending > 0) console.log(chalk.gray(` ⬜ Pending: ${pending}`));
|
|
328
|
-
console.log(` Estimated time remaining: ${estimateRemainingTime(feature)}`);
|
|
282
|
+
if (slices && slices.total > 0) {
|
|
283
|
+
console.log(chalk.white.bold('Fatias:'));
|
|
284
|
+
const percentage = Math.round((slices.done / slices.total) * 100);
|
|
285
|
+
console.log(` Total: ${slices.total}`);
|
|
286
|
+
console.log(chalk.green(` ✓ Feitas: ${slices.done} (${percentage}%)`));
|
|
287
|
+
if (slices.partial > 0) console.log(chalk.cyan(` ▶ Parciais: ${slices.partial}`));
|
|
288
|
+
if (slices.pending > 0) console.log(chalk.gray(` ⬜ Pendentes: ${slices.pending}`));
|
|
329
289
|
console.log('');
|
|
330
290
|
}
|
|
331
291
|
|
|
@@ -361,7 +321,7 @@ export async function statusCommand(featureName, options = {}) {
|
|
|
361
321
|
});
|
|
362
322
|
console.log('');
|
|
363
323
|
} else {
|
|
364
|
-
console.log(chalk.green('✓
|
|
324
|
+
console.log(chalk.green('✓ Nada pendente.\n'));
|
|
365
325
|
}
|
|
366
326
|
|
|
367
327
|
// Footer
|
|
@@ -30,11 +30,12 @@ import { installSkills } from '../../utils/skills-installer.js';
|
|
|
30
30
|
import { installAgents, installDomainAgents } from '../../utils/agents-installer.js';
|
|
31
31
|
import { injectMorphImport } from '../../utils/claude-md-injector.js';
|
|
32
32
|
import { getProjectTags } from '../../lib/stack-filter.js';
|
|
33
|
-
import { updateClaudeMdAgentsBlock, installRulesForStack } from '../../scripts/setup-infra.js';
|
|
33
|
+
import { updateClaudeMdAgentsBlock, installRulesForStack, installCommandsForStack } from '../../scripts/setup-infra.js';
|
|
34
34
|
import { reconcileLegacyTemplatesCopy } from '../../lib/installers/templates-reconcile.js';
|
|
35
35
|
import { createEffects } from '../../lib/installers/effects.js';
|
|
36
36
|
import { applyEditorSettings } from '../../lib/worktree/vscode-settings.js';
|
|
37
37
|
import { registerProject } from '../../lib/projects/registry.js';
|
|
38
|
+
import { findOldFormatFeatures, oldFormatGuardLines } from '../../lib/specs/old-format.js';
|
|
38
39
|
import {
|
|
39
40
|
writeGraphifyignore, describeOutdatedIgnore, graphifyignoreTracked,
|
|
40
41
|
} from '../../lib/graph/graphifyignore.js';
|
|
@@ -84,6 +85,7 @@ export function frameworkDirsToClean(morphPath, targetPath) {
|
|
|
84
85
|
join(morphPath, 'framework', 'standards'),
|
|
85
86
|
join(morphPath, 'framework', 'hooks'),
|
|
86
87
|
join(morphPath, 'framework', 'schemas'),
|
|
88
|
+
// Não é mais instalado: fica na lista para apagar o de instalações antigas.
|
|
87
89
|
join(morphPath, 'framework', 'rubrics'),
|
|
88
90
|
join(morphPath, 'framework', 'evals'),
|
|
89
91
|
join(morphPath, 'config'),
|
|
@@ -530,6 +532,21 @@ export async function updateCommand(options = {}) {
|
|
|
530
532
|
+ `Quem decide é o primeiro.`);
|
|
531
533
|
}
|
|
532
534
|
|
|
535
|
+
// `--force` não dispensa: esta CLI não sabe conduzir essas features.
|
|
536
|
+
const oldFormat = needsInitialization ? [] : findOldFormatFeatures(targetPath);
|
|
537
|
+
if (oldFormat.length) {
|
|
538
|
+
const lines = oldFormatGuardLines(oldFormat, targetPath);
|
|
539
|
+
if (dryRun) {
|
|
540
|
+
notices.push(...lines);
|
|
541
|
+
} else {
|
|
542
|
+
logger.error(lines[0]);
|
|
543
|
+
for (const l of lines.slice(1)) logger.dim(l);
|
|
544
|
+
logger.blank();
|
|
545
|
+
process.exitCode = 1;
|
|
546
|
+
return { refused: true, reason: 'old-format-features', features: oldFormat, effects: fx.list() };
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
533
550
|
// ── A partir daqui, tudo passa pelo coletor ──────────────────────────────
|
|
534
551
|
if (needsInitialization) {
|
|
535
552
|
logger.warn('MORPH not fully initialized. Creating basic structure...');
|
|
@@ -594,10 +611,8 @@ export async function updateCommand(options = {}) {
|
|
|
594
611
|
fx.copyFile(agentsSrc, agentsDest);
|
|
595
612
|
}
|
|
596
613
|
|
|
597
|
-
//
|
|
598
|
-
|
|
599
|
-
// the installed charter/rubrics/eval assets stale forever.
|
|
600
|
-
updateSpinner.text = 'Updating charter, schemas, rubrics and evals...';
|
|
614
|
+
// Paridade com setup-infra 7c/8b/8c/8d: sem isto o charter e os evals instalados ficam velhos.
|
|
615
|
+
updateSpinner.text = 'Updating charter, schemas and evals...';
|
|
601
616
|
const canonicalFrameworkDir = join(__dirname, '..', '..', '..', 'framework');
|
|
602
617
|
for (const file of ['MORPH.md', 'phases.json']) {
|
|
603
618
|
const src = join(canonicalFrameworkDir, file);
|
|
@@ -605,7 +620,7 @@ export async function updateCommand(options = {}) {
|
|
|
605
620
|
fx.copyFile(src, join(morphPath, 'framework', file));
|
|
606
621
|
}
|
|
607
622
|
}
|
|
608
|
-
for (const dir of ['schemas', '
|
|
623
|
+
for (const dir of ['schemas', 'evals']) {
|
|
609
624
|
const src = join(canonicalFrameworkDir, dir);
|
|
610
625
|
if (await pathExists(src)) {
|
|
611
626
|
fx.copyDir(src, join(morphPath, 'framework', dir));
|
|
@@ -644,28 +659,27 @@ export async function updateCommand(options = {}) {
|
|
|
644
659
|
const frameworkDir = join(__dirname, '..', '..', '..', 'framework');
|
|
645
660
|
const claudeDest = join(targetPath, '.claude');
|
|
646
661
|
|
|
647
|
-
let commandsCopied = false;
|
|
648
|
-
|
|
649
|
-
// Copy commands directory (slash commands): framework/commands/ → .claude/commands/
|
|
650
|
-
const commandsSrc = join(frameworkDir, 'commands');
|
|
651
|
-
const commandsDest = join(claudeDest, 'commands');
|
|
652
|
-
if (await pathExists(commandsSrc)) {
|
|
653
|
-
fx.copyDir(commandsSrc, commandsDest);
|
|
654
|
-
commandsCopied = true;
|
|
655
|
-
} else {
|
|
656
|
-
logger.warn(' ⚠ framework/commands/ source missing — commands not updated');
|
|
657
|
-
}
|
|
658
|
-
|
|
659
662
|
// Compute project tags for stack-aware installation of skills, agents, and rules.
|
|
660
663
|
// MUST come from the pre-clean backup: cleanFrameworkDirs() deleted .morph/config/
|
|
661
664
|
// above and restoreUserConfig() only runs at the end — re-reading the file here
|
|
662
665
|
// yields [] and silently downgrades the whole install to universal-only assets
|
|
663
|
-
// (the ".claude/agents/ only has morph-
|
|
666
|
+
// (the ".claude/agents/ only has morph-verifier.md after update" bug).
|
|
664
667
|
let projectTags = [];
|
|
665
668
|
try {
|
|
666
669
|
if (configBackup) projectTags = getProjectTags(configBackup);
|
|
667
670
|
} catch { /* ignore — malformed config */ }
|
|
668
671
|
|
|
672
|
+
let commandsCopied = false;
|
|
673
|
+
const commandsSrc = join(frameworkDir, 'commands');
|
|
674
|
+
if (await pathExists(commandsSrc)) {
|
|
675
|
+
const commandsDest = join(claudeDest, 'commands');
|
|
676
|
+
fx.ensureDir(commandsDest);
|
|
677
|
+
installCommandsForStack(commandsSrc, commandsDest, projectTags, fx);
|
|
678
|
+
commandsCopied = true;
|
|
679
|
+
} else {
|
|
680
|
+
logger.warn(' ⚠ framework/commands/ source missing — commands not updated');
|
|
681
|
+
}
|
|
682
|
+
|
|
669
683
|
// Sync morph skills to .claude/skills/ for native Claude Code discovery
|
|
670
684
|
updateSpinner.text = 'Syncing morph skills to .claude/skills/...';
|
|
671
685
|
const skillsResult = await installSkills(targetPath, { projectTags, fx });
|
|
@@ -1867,7 +1867,7 @@ export async function worktreeListCommand(options = {}) {
|
|
|
1867
1867
|
if (!m) continue; // skip the primary root entry and any non-morph worktree
|
|
1868
1868
|
const feature = m[1];
|
|
1869
1869
|
|
|
1870
|
-
let phase = '
|
|
1870
|
+
let phase = 'plan';
|
|
1871
1871
|
let status = 'unknown';
|
|
1872
1872
|
let pendingGates = [];
|
|
1873
1873
|
try {
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync } from 'fs';
|
|
3
|
-
import { join } from 'path';
|
|
3
|
+
import { join, relative } from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { getFeature, setApprovalGate, derivePhase } from '../../core/state/state-manager.js';
|
|
6
6
|
import { unmetPrereqs } from '../../core/state/gate-order.js';
|
|
7
7
|
import { OUTPUT_SCHEMA } from '../../core/paths/output-schema.js';
|
|
8
8
|
import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
|
|
9
9
|
import { uiuxAutoApprovalBlock } from '../../lib/specs/screen-checklist.js';
|
|
10
|
+
import { seedDesignArtifacts } from '../../lib/specs/design-seed.js';
|
|
11
|
+
import { sliceProgress } from '../../../framework/hooks/shared/slice-progress.js';
|
|
12
|
+
import { autoApprovalRefusal, printRefusal } from './auto-approval.js';
|
|
13
|
+
import { seedDotnetBuildIgnores } from '../../lib/stack/dotnet-build-ignores.js';
|
|
10
14
|
|
|
11
15
|
const FRAMEWORK_DIR = fileURLToPath(new URL('../../../framework/', import.meta.url));
|
|
12
16
|
|
|
@@ -20,17 +24,16 @@ const FRAMEWORK_DIR = fileURLToPath(new URL('../../../framework/', import.meta.u
|
|
|
20
24
|
*/
|
|
21
25
|
|
|
22
26
|
// The gate approved when *completing* a given phase. `implement` has no gate
|
|
23
|
-
// (it ends when all
|
|
24
|
-
// the review folder.
|
|
25
|
-
const PHASE_GATE = {
|
|
27
|
+
// (it ends when all slices are done), so advancing from implement just
|
|
28
|
+
// scaffolds the review folder.
|
|
29
|
+
const PHASE_GATE = { plan: 'plan', uiux: 'uiux', review: 'review' };
|
|
26
30
|
|
|
27
31
|
// The next phase folder to scaffold when advancing FROM a given phase. The
|
|
28
|
-
// default forward path skips the optional design phase (
|
|
29
|
-
// --design to take the uiux branch (
|
|
32
|
+
// default forward path skips the optional design phase (plan → implement); pass
|
|
33
|
+
// --design to take the uiux branch (plan → 1-design).
|
|
30
34
|
const NEXT_PHASE_DIR = {
|
|
31
|
-
proposal: '2-plan',
|
|
32
|
-
uiux: '2-plan',
|
|
33
35
|
plan: '3-implement',
|
|
36
|
+
uiux: '3-implement',
|
|
34
37
|
implement: '4-review',
|
|
35
38
|
};
|
|
36
39
|
|
|
@@ -63,12 +66,19 @@ function missingPhaseArtifacts(phase, featurePath) {
|
|
|
63
66
|
const rel = `${entry.phaseDir}/${entry.filename}`;
|
|
64
67
|
if (!existsSync(join(featurePath, entry.phaseDir, entry.filename))) missing.push(rel);
|
|
65
68
|
}
|
|
69
|
+
if (phase === 'implement') missing.push(...unfinishedSliceReports(featurePath));
|
|
66
70
|
return missing;
|
|
67
71
|
} catch {
|
|
68
72
|
return [];
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
|
|
76
|
+
function unfinishedSliceReports(featurePath) {
|
|
77
|
+
const progress = sliceProgress(featurePath);
|
|
78
|
+
if (!progress) return ['2-plan/checks.json'];
|
|
79
|
+
return progress.slices.filter((s) => s.status !== 'feito').map((s) => `3-implement/reports/${s.id}.md`);
|
|
80
|
+
}
|
|
81
|
+
|
|
72
82
|
/**
|
|
73
83
|
* When a required artifact is absent HERE, check whether it exists in the
|
|
74
84
|
* worktree that owns this feature. `feature.json` travels committed on
|
|
@@ -105,6 +115,16 @@ function resolveApprovedBy(options) {
|
|
|
105
115
|
return 'auto';
|
|
106
116
|
}
|
|
107
117
|
|
|
118
|
+
export function reportDotnetIgnores(added) {
|
|
119
|
+
if (added.length) console.log(chalk.gray(` .gitignore: ${added.join(' ')} adicionados (projeto .NET) — commite antes do primeiro build.`));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function reportDesignSeed({ mockups, designSystem }) {
|
|
123
|
+
const rel = (p) => relative(process.cwd(), p).replace(/\\/g, '/');
|
|
124
|
+
if (mockups.written) console.log(chalk.gray(` Escrito ${rel(mockups.path)} a partir do template (checklist de tela por \`## Tela:\`).`));
|
|
125
|
+
if (designSystem.written) console.log(chalk.gray(` Escrito ${rel(designSystem.path)} a partir do template (o projeto não tinha design system).`));
|
|
126
|
+
}
|
|
127
|
+
|
|
108
128
|
export async function advanceCommand(featureName, options = {}) {
|
|
109
129
|
try {
|
|
110
130
|
const feature = getFeature(featureName);
|
|
@@ -127,6 +147,21 @@ export async function advanceCommand(featureName, options = {}) {
|
|
|
127
147
|
const featurePath = join(process.cwd(), '.morph', 'features', featureName);
|
|
128
148
|
const phase = derivePhase(featurePath);
|
|
129
149
|
|
|
150
|
+
// The plan gate is often closed by the human's answer to the gate question
|
|
151
|
+
// (state-sync), which already moves the derived phase to implement: taking
|
|
152
|
+
// the design branch then is opening 1-design/, not closing a phase.
|
|
153
|
+
if (options.design && phase === 'implement' && feature.approvalGates?.plan?.approved === true) {
|
|
154
|
+
mkdirSync(join(featurePath, '1-design'), { recursive: true });
|
|
155
|
+
reportDesignSeed(seedDesignArtifacts(featureName, process.cwd()));
|
|
156
|
+
reportDotnetIgnores(seedDotnetBuildIgnores(process.cwd(), { featureName }));
|
|
157
|
+
try {
|
|
158
|
+
const { writeHandoff } = await import('../../lib/generators/handoff-generator.js');
|
|
159
|
+
writeHandoff(featureName, process.cwd());
|
|
160
|
+
} catch { /* fail-open */ }
|
|
161
|
+
console.log(chalk.green(`\n✅ "${featureName}": gate plan já aprovado — ramo de design aberto (1-design/).\n`));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
130
165
|
if (phase === 'review') {
|
|
131
166
|
console.log(chalk.yellow(`\n⚠️ Feature "${featureName}" is already at the final phase (review).`));
|
|
132
167
|
console.log(chalk.gray(`Approve Gate 3 with: morph-spec approve ${featureName} review`));
|
|
@@ -134,11 +169,9 @@ export async function advanceCommand(featureName, options = {}) {
|
|
|
134
169
|
return;
|
|
135
170
|
}
|
|
136
171
|
|
|
137
|
-
// Phase-exit artifact check
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
// the Gate 3 review had nothing to review. `implement` has no gate, which
|
|
141
|
-
// is precisely why nothing stopped it.
|
|
172
|
+
// Phase-exit artifact check: a phase transition must not step over what the
|
|
173
|
+
// phase it is CLOSING owes. `implement` has no gate, so this is the only
|
|
174
|
+
// thing that keeps a feature with unfinished slice reports out of review.
|
|
142
175
|
//
|
|
143
176
|
// `--force` bypasses (same deliberate-recovery role it already plays for
|
|
144
177
|
// gate order) but still names what was skipped — a silent bypass would be
|
|
@@ -160,7 +193,7 @@ export async function advanceCommand(featureName, options = {}) {
|
|
|
160
193
|
process.exit(1);
|
|
161
194
|
}
|
|
162
195
|
|
|
163
|
-
console.error(chalk.red(`\n❌ A fase "${phase}" não pode ser encerrada — artefato(s) obrigatório(s) ausente(s):`));
|
|
196
|
+
console.error(chalk.red(`\n❌ A fase "${phase}" não pode ser encerrada — artefato(s) obrigatório(s) ausente(s) ou PARCIAL:`));
|
|
164
197
|
for (const rel of missingArtifacts) console.error(chalk.gray(` • ${rel}`));
|
|
165
198
|
console.error(chalk.gray('\n Produza o artefato e rode de novo, ou passe --force para avançar assim mesmo.\n'));
|
|
166
199
|
process.exit(1);
|
|
@@ -170,25 +203,11 @@ export async function advanceCommand(featureName, options = {}) {
|
|
|
170
203
|
const gate = PHASE_GATE[phase];
|
|
171
204
|
const approvedBy = resolveApprovedBy(options);
|
|
172
205
|
|
|
173
|
-
// The screen checklist is part of what the uiux phase owes. The predicate
|
|
174
|
-
// is approve.js's too: an "auto" signature never closes a gate the
|
|
175
|
-
// gate-check would pause, and a recorded decision or a human signature
|
|
176
|
-
// (--approver) is the answer to that pause; --force is not, as in approve.
|
|
177
|
-
const checklistBlock = phase === 'uiux' ? uiuxAutoApprovalBlock(process.cwd(), featureName, feature) : null;
|
|
178
|
-
if (checklistBlock && approvedBy === 'auto') {
|
|
179
|
-
console.error(chalk.red(`\n❌ A fase "uiux" não pode ser encerrada com assinatura automática — ${checklistBlock}.`));
|
|
180
|
-
console.error(chalk.gray('\n Complete o checklist (ver a skill morph-uiux) e rode de novo. Quem decidiu aprovar assim'));
|
|
181
|
-
console.error(chalk.gray(` mesmo grava a decisão (morph-spec gate-decision ${featureName} uiux --pause false --reason "...")`));
|
|
182
|
-
console.error(chalk.gray(' ou assina com --approver <nome>. O --force não dispensa o checklist.\n'));
|
|
183
|
-
process.exit(1);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
206
|
// Gate monotonicity (mirrors approve.js): advance is a gate write-path too,
|
|
187
207
|
// so it must not stamp gate N while a predecessor is pending. In the
|
|
188
|
-
// sanctioned flow advance is always in-order
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
// shape — would otherwise let advance deepen the violation by stamping G2.
|
|
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.
|
|
192
211
|
// Escape via --force for deliberate recovery.
|
|
193
212
|
if (gate) {
|
|
194
213
|
const unmet = unmetPrereqs(gate, feature.approvalGates);
|
|
@@ -199,24 +218,11 @@ export async function advanceCommand(featureName, options = {}) {
|
|
|
199
218
|
}
|
|
200
219
|
}
|
|
201
220
|
|
|
202
|
-
// Hotfix backstop (defensive, mirrors approve.js): the phase==='review'
|
|
203
|
-
// early-return above already stops advance from ever reaching a
|
|
204
|
-
// gate==='review' approval, but guard the approval itself too so this
|
|
205
|
-
// stays true even if the early-return's shape changes later. Gate on the
|
|
206
|
-
// RESOLVED approvedBy — headless contexts have no TTY and resolve to
|
|
207
|
-
// "auto" regardless of --mode, so checking the raw --mode string alone
|
|
208
|
-
// would let them slip through (same bug the approve.js backstop had).
|
|
209
|
-
if (gate === 'review' && feature.workType === 'hotfix' && approvedBy === 'auto') {
|
|
210
|
-
console.error(chalk.red('\n❌ Hotfix exige aprovação humana no Gate 3 — trust:auto não se aplica.'));
|
|
211
|
-
console.error(chalk.gray(' Aprove com --approver <nome>, num terminal interativo, ou pela resposta humana "aprovado" (state-sync) ao review-report via AskUserQuestion.\n'));
|
|
212
|
-
process.exit(1);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
221
|
// Gate idempotency (F15): `setApprovalGate` REPLACES the gate object whole
|
|
216
222
|
// (state-manager.js), so re-writing an already-approved gate does not
|
|
217
223
|
// refresh it — it destroys `approvedBy`, `timestamp`, `notes` and
|
|
218
224
|
// `selfAssessed` in one assignment. And `resolveApprovedBy` above answers
|
|
219
|
-
// "auto" in every headless context, so `approve f
|
|
225
|
+
// "auto" in every headless context, so `approve f plan --approver
|
|
220
226
|
// lucas` followed by `advance f` used to rewrite a human's signature as
|
|
221
227
|
// "auto" — the one stamp the charter forbids on a human-approved gate.
|
|
222
228
|
// The state-sync hook already carries exactly this guard; `advance`, the
|
|
@@ -228,6 +234,14 @@ export async function advanceCommand(featureName, options = {}) {
|
|
|
228
234
|
const priorGate = gate ? feature.approvalGates?.[gate] : null;
|
|
229
235
|
const alreadyApproved = priorGate?.approved === true;
|
|
230
236
|
|
|
237
|
+
const refusal = alreadyApproved ? null
|
|
238
|
+
: autoApprovalRefusal({ cwd: process.cwd(), featureName, feature, gate, approvedBy });
|
|
239
|
+
if (refusal) {
|
|
240
|
+
printRefusal(chalk, refusal);
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
const checklistBlock = phase === 'uiux' && !alreadyApproved ? uiuxAutoApprovalBlock(process.cwd(), featureName, feature) : null;
|
|
244
|
+
|
|
231
245
|
if (gate && !alreadyApproved) {
|
|
232
246
|
await setApprovalGate(featureName, gate, true, {
|
|
233
247
|
approvedBy,
|
|
@@ -238,11 +252,13 @@ export async function advanceCommand(featureName, options = {}) {
|
|
|
238
252
|
|
|
239
253
|
// 2) Scaffold the next phase folder so the agent can write its first artifact.
|
|
240
254
|
let nextDir = NEXT_PHASE_DIR[phase];
|
|
241
|
-
if (phase === '
|
|
255
|
+
if (phase === 'plan' && options.design) nextDir = '1-design';
|
|
242
256
|
if (nextDir) {
|
|
243
257
|
const nextPath = join(featurePath, nextDir);
|
|
244
258
|
if (!existsSync(nextPath)) mkdirSync(nextPath, { recursive: true });
|
|
245
259
|
}
|
|
260
|
+
if (nextDir === '1-design') reportDesignSeed(seedDesignArtifacts(featureName, process.cwd()));
|
|
261
|
+
if (phase === 'plan') reportDotnetIgnores(seedDotnetBuildIgnores(process.cwd(), { featureName }));
|
|
246
262
|
|
|
247
263
|
// Fleet handoff: a phase transition is exactly when a session hands the
|
|
248
264
|
// feature over (to a new instance, or to itself after /compact) — refresh
|