@polymorphism-tech/morph-spec 4.8.19 → 4.10.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 +21 -0
- package/README.md +2 -2
- package/bin/morph-spec.js +44 -55
- package/bin/task-manager.js +133 -20
- package/bin/validate.js +67 -33
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +201 -203
- package/docs/QUICKSTART.md +2 -2
- package/framework/CLAUDE.md +99 -77
- package/framework/agents.json +734 -182
- package/framework/commands/commit.md +166 -0
- package/framework/commands/morph-apply.md +13 -2
- package/framework/commands/morph-archive.md +8 -2
- package/framework/commands/morph-infra.md +6 -0
- package/framework/commands/morph-preflight.md +6 -0
- package/framework/commands/morph-proposal.md +56 -7
- package/framework/commands/morph-status.md +6 -0
- package/framework/commands/morph-troubleshoot.md +6 -0
- package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
- package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +155 -32
- package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +78 -0
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +8 -17
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +16 -3
- package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +4 -3
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +4 -3
- package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +60 -0
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +124 -2
- package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
- package/framework/hooks/claude-code/statusline.py +76 -30
- package/framework/hooks/claude-code/stop/validate-completion.js +2 -15
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +23 -5
- package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
- package/framework/hooks/shared/activity-logger.js +0 -24
- package/framework/hooks/shared/compact-restore.js +100 -0
- package/framework/hooks/shared/dispatch-helpers.js +116 -0
- package/framework/hooks/shared/phase-utils.js +12 -5
- package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
- package/framework/hooks/shared/stale-task-reset.js +57 -0
- package/framework/hooks/shared/state-reader.js +29 -5
- package/framework/hooks/shared/worktree-helpers.js +53 -0
- package/framework/phases.json +69 -14
- package/framework/rules/morph-workflow.md +88 -86
- package/framework/skills/level-0-meta/mcp-registry.json +86 -51
- package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/SKILL.md +14 -17
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +163 -163
- package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/SKILL.md +9 -9
- package/framework/skills/level-0-meta/morph-init/SKILL.md +77 -12
- package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/SKILL.md +62 -15
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
- package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +1 -1
- package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +3 -4
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +7 -7
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/scripts/check-phase-outputs.mjs +2 -2
- package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +238 -0
- package/framework/skills/level-1-workflows/{phase-codebase-analysis → morph-phase-codebase-analysis}/SKILL.md +3 -3
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +507 -0
- package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/SKILL.md +168 -27
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +50 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +45 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +47 -0
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +254 -0
- package/framework/skills/level-1-workflows/{phase-setup → morph-phase-setup}/SKILL.md +50 -3
- package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/SKILL.md +48 -11
- package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
- package/framework/skills/level-1-workflows/{phase-uiux → morph-phase-uiux}/SKILL.md +46 -11
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +97 -0
- package/framework/standards/STANDARDS.json +640 -88
- package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
- package/framework/standards/integration/mcp/mcp-tools.md +25 -7
- package/framework/templates/REGISTRY.json +1825 -1909
- package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
- package/framework/templates/docs/onboarding.md +3 -7
- package/package.json +2 -7
- package/src/commands/agents/dispatch-agents.js +104 -6
- package/src/commands/mcp/mcp-setup.js +39 -2
- package/src/commands/phase/phase-reset.js +74 -0
- package/src/commands/project/doctor.js +34 -51
- package/src/commands/project/init.js +1 -1
- package/src/commands/project/status.js +2 -2
- package/src/commands/project/update.js +381 -365
- package/src/commands/project/worktree.js +154 -0
- package/src/commands/scope/escalate.js +215 -0
- package/src/commands/state/advance-phase.js +132 -68
- package/src/commands/state/approve.js +2 -2
- package/src/commands/state/index.js +7 -8
- package/src/commands/state/phase-runner.js +1 -1
- package/src/commands/state/state.js +61 -6
- package/src/commands/task/expand.js +100 -0
- package/src/commands/tasks/task.js +78 -99
- package/src/commands/templates/template-render.js +93 -173
- package/src/commands/trust/trust.js +26 -21
- package/src/core/paths/output-schema.js +19 -3
- package/src/core/state/phase-state-machine.js +7 -4
- package/src/core/state/state-manager.js +32 -57
- package/src/core/workflows/workflow-detector.js +9 -87
- package/src/lib/detectors/claude-config-detector.js +93 -347
- package/src/lib/detectors/design-system-detector.js +189 -189
- package/src/lib/detectors/index.js +155 -57
- package/src/lib/generators/context-generator.js +2 -2
- package/src/lib/installers/mcp-installer.js +37 -5
- package/src/lib/phase-chain/phase-validator.js +336 -0
- package/src/lib/scope/impact-analyzer.js +106 -0
- package/src/lib/stack/stack-profile.js +88 -0
- package/src/lib/tasks/task-classifier.js +16 -0
- package/src/lib/tasks/task-parser.js +1 -1
- package/src/lib/tasks/test-runner.js +77 -0
- package/src/lib/trust/trust-manager.js +32 -144
- package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
- package/src/lib/validators/spec-validator.js +58 -4
- package/src/lib/validators/validation-runner.js +23 -11
- package/src/scripts/setup-infra.js +255 -224
- package/src/utils/agents-installer.js +34 -14
- package/src/utils/banner.js +1 -1
- package/src/utils/claude-settings-manager.js +1 -1
- package/src/utils/file-copier.js +1 -1
- package/src/utils/hooks-installer.js +272 -8
- package/framework/hooks/dev/check-sync-health.js +0 -117
- package/framework/hooks/dev/guard-version-numbers.js +0 -57
- package/framework/hooks/dev/sync-standards-registry.js +0 -60
- package/framework/hooks/dev/sync-template-registry.js +0 -60
- package/framework/hooks/dev/validate-skill-format.js +0 -70
- package/framework/hooks/dev/validate-standard-format.js +0 -73
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +0 -190
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -366
- package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
- package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
- package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
- package/framework/workflows/configs/design-impl.json +0 -49
- package/framework/workflows/configs/express.json +0 -45
- package/framework/workflows/configs/fast-track.json +0 -42
- package/framework/workflows/configs/full-morph.json +0 -79
- package/framework/workflows/configs/fusion.json +0 -39
- package/framework/workflows/configs/long-running.json +0 -33
- package/framework/workflows/configs/spec-only.json +0 -43
- package/framework/workflows/configs/ui-refresh.json +0 -49
- package/framework/workflows/configs/zero-touch.json +0 -82
- package/src/commands/project/index.js +0 -8
- package/src/commands/project/monitor.js +0 -295
- package/src/commands/project/tutorial.js +0 -115
- package/src/commands/state/validate-phase.js +0 -238
- package/src/commands/templates/generate-contracts.js +0 -445
- package/src/core/index.js +0 -10
- package/src/core/orchestrator.js +0 -171
- package/src/core/registry/command-registry.js +0 -28
- package/src/core/registry/index.js +0 -8
- package/src/core/registry/validator-registry.js +0 -204
- package/src/core/state/index.js +0 -8
- package/src/core/templates/index.js +0 -9
- package/src/core/templates/template-data-sources.js +0 -325
- package/src/core/templates/template-validator.js +0 -296
- package/src/core/workflows/index.js +0 -7
- package/src/generator/config-generator.js +0 -206
- package/src/generator/templates/config.json.template +0 -40
- package/src/generator/templates/project.md.template +0 -67
- package/src/lib/agents/micro-agent-factory.js +0 -161
- package/src/lib/analysis/complexity-analyzer.js +0 -441
- package/src/lib/analysis/index.js +0 -7
- package/src/lib/analytics/analytics-engine.js +0 -345
- package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
- package/src/lib/checkpoints/index.js +0 -7
- package/src/lib/context/context-bundler.js +0 -241
- package/src/lib/context/context-optimizer.js +0 -212
- package/src/lib/context/context-tracker.js +0 -273
- package/src/lib/context/core-four-tracker.js +0 -201
- package/src/lib/context/mcp-optimizer.js +0 -200
- package/src/lib/detectors/config-detector.js +0 -223
- package/src/lib/detectors/standards-generator.js +0 -335
- package/src/lib/detectors/structure-detector.js +0 -275
- package/src/lib/execution/fusion-executor.js +0 -304
- package/src/lib/execution/parallel-executor.js +0 -270
- package/src/lib/hooks/stop-hook-executor.js +0 -286
- package/src/lib/hops/hop-composer.js +0 -221
- package/src/lib/monitor/agent-resolver.js +0 -144
- package/src/lib/monitor/renderer.js +0 -230
- package/src/lib/orchestration/index.js +0 -7
- package/src/lib/orchestration/team-orchestrator.js +0 -404
- package/src/lib/phase-chain/eligibility-checker.js +0 -243
- package/src/lib/threads/thread-coordinator.js +0 -238
- package/src/lib/threads/thread-manager.js +0 -317
- package/src/lib/tracking/artifact-trail.js +0 -202
- package/src/sanitizer/context-sanitizer.js +0 -221
- package/src/sanitizer/patterns.js +0 -163
- package/src/scanner/project-scanner.js +0 -242
- package/src/ui/diff-display.js +0 -91
- package/src/ui/interactive-wizard.js +0 -96
- package/src/ui/user-review.js +0 -211
- package/src/ui/wizard-questions.js +0 -188
- package/src/utils/color-utils.js +0 -70
- package/src/utils/process-handler.js +0 -97
- package/src/writer/file-writer.js +0 -86
- /package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/references/proposal-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-guidelines.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/scripts/scan-csharp.mjs +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/references/review-example-nextjs.md +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/scripts/scan-nextjs.mjs +0 -0
- /package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/scripts/scan-accessibility.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-dev-server.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-stack.mjs +0 -0
- /package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/scripts/set_title.sh +0 -0
- /package/framework/skills/level-1-workflows/{phase-clarify → morph-phase-clarify}/references/clarifications-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/architecture-analysis-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-authoring-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/recap-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/vsa-implementation-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/task-planning-patterns.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/tasks-example.md +0 -0
|
@@ -11,22 +11,36 @@
|
|
|
11
11
|
|
|
12
12
|
import chalk from 'chalk';
|
|
13
13
|
import { loadState, saveState, getFeature, getApprovalGate, derivePhase } from '../../core/state/state-manager.js';
|
|
14
|
-
import { PHASES, validatePhase } from '
|
|
14
|
+
import { PHASES, validatePhase } from '../../lib/phase-chain/phase-validator.js';
|
|
15
15
|
import { detectDesignSystem, hasUIAgentsActive } from '../../lib/detectors/design-system-detector.js';
|
|
16
|
-
import { getOutputPath } from '../../core/paths/output-schema.js';
|
|
17
|
-
import { shouldAutoApprove
|
|
16
|
+
import { getOutputPath, getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
|
|
17
|
+
import { shouldAutoApprove } from '../../lib/trust/trust-manager.js';
|
|
18
18
|
import { validateSpec } from '../../lib/validators/spec-validator.js';
|
|
19
|
-
import { validateTransition
|
|
20
|
-
import { validateSpecContent, validateTasksContent
|
|
19
|
+
import { validateTransition } from '../../core/state/phase-state-machine.js';
|
|
20
|
+
import { validateSpecContent, validateTasksContent } from '../../lib/validators/content/content-validator.js';
|
|
21
21
|
import { getWorkflowConfig } from '../../core/workflows/workflow-detector.js';
|
|
22
|
+
import { getStackProfile } from '../../lib/stack/stack-profile.js';
|
|
22
23
|
import { readFileSync, existsSync } from 'fs';
|
|
23
24
|
import { join, dirname } from 'path';
|
|
24
25
|
import { fileURLToPath } from 'url';
|
|
26
|
+
import { emitValidatorDispatch } from '../../lib/validators/shared/emit-validator-dispatch.js';
|
|
25
27
|
|
|
26
28
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
27
29
|
|
|
28
30
|
// Phase order for advancing (skips optional phases unless active)
|
|
29
|
-
const PHASE_ORDER = ['proposal', 'setup', 'uiux', 'design', 'clarify', 'tasks', 'implement', 'sync'];
|
|
31
|
+
const PHASE_ORDER = ['proposal', 'setup', 'uiux', 'design', 'clarify', 'plan', 'tasks', 'implement', 'sync'];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Phases that require an explicit approval gate before advancing.
|
|
35
|
+
* When --skip-approval is passed, these gates are bypassed and a warning is logged.
|
|
36
|
+
* Exported for use in tests and in phase-runner warnings.
|
|
37
|
+
*/
|
|
38
|
+
export const APPROVAL_GATE_MAP = {
|
|
39
|
+
'design': 'design',
|
|
40
|
+
'plan': 'plan',
|
|
41
|
+
'tasks': 'tasks',
|
|
42
|
+
'uiux': 'uiux'
|
|
43
|
+
};
|
|
30
44
|
|
|
31
45
|
/**
|
|
32
46
|
* Get the next phase after the current one.
|
|
@@ -66,13 +80,16 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
|
|
|
66
80
|
|
|
67
81
|
// Check if this phase is part of a combined phase
|
|
68
82
|
if (workflowConfig.phases.combined) {
|
|
83
|
+
let isCombined = false;
|
|
69
84
|
for (const [combinedName, phases] of Object.entries(workflowConfig.phases.combined)) {
|
|
70
85
|
if (phases.includes(candidate) && candidate !== combinedName) {
|
|
71
86
|
console.log(chalk.gray(` ⏩ Skipping ${candidate}: combined into ${combinedName} phase`));
|
|
72
87
|
skippedPhases.push(candidate);
|
|
73
|
-
|
|
88
|
+
isCombined = true;
|
|
89
|
+
break;
|
|
74
90
|
}
|
|
75
91
|
}
|
|
92
|
+
if (isCombined) continue;
|
|
76
93
|
}
|
|
77
94
|
|
|
78
95
|
// Check if workflow only runs specific phases
|
|
@@ -119,14 +136,11 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
|
|
|
119
136
|
}
|
|
120
137
|
}
|
|
121
138
|
|
|
122
|
-
//
|
|
123
|
-
if (candidate === 'sync') {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
skippedPhases.push(candidate);
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
139
|
+
// sync is optional — only skip if --skip-optional is set
|
|
140
|
+
if (candidate === 'sync' && skipOptional) {
|
|
141
|
+
console.log(chalk.gray(` ⏩ Skipping ${candidate}: optional phase`));
|
|
142
|
+
skippedPhases.push(candidate);
|
|
143
|
+
continue;
|
|
130
144
|
}
|
|
131
145
|
}
|
|
132
146
|
|
|
@@ -189,13 +203,15 @@ export async function advancePhaseCommand(feature, options = {}) {
|
|
|
189
203
|
|
|
190
204
|
// === GATE 2: Approval Gate Check ===
|
|
191
205
|
// Check if current phase requires approval before advancing
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
206
|
+
// Track gates that get approved implicitly so we can persist them after state load.
|
|
207
|
+
let gateApprovedImplicitly = null; // { gate, approvedBy, reason }
|
|
208
|
+
|
|
209
|
+
const requiredGate = APPROVAL_GATE_MAP[currentPhase];
|
|
210
|
+
if (requiredGate && options.skipApproval) {
|
|
211
|
+
console.log(chalk.yellow(`\n⚠ --skip-approval: Approval gate "${requiredGate}" bypassed for phase "${currentPhase}"`));
|
|
212
|
+
console.log(chalk.gray(' Intended for CI/automation only. Run without --skip-approval for normal workflow.\n'));
|
|
213
|
+
gateApprovedImplicitly = { gate: requiredGate, approvedBy: 'skip-approval' };
|
|
214
|
+
}
|
|
199
215
|
if (requiredGate && !options.skipApproval) {
|
|
200
216
|
const gateStatus = getApprovalGate(feature, requiredGate);
|
|
201
217
|
|
|
@@ -206,6 +222,7 @@ export async function advancePhaseCommand(feature, options = {}) {
|
|
|
206
222
|
if (trustResult.autoApprove) {
|
|
207
223
|
console.log(chalk.green(`\n✓ Auto-approved gate "${requiredGate}" (${trustResult.reason})`));
|
|
208
224
|
// Proceed — trust bypasses manual gate requirement
|
|
225
|
+
gateApprovedImplicitly = { gate: requiredGate, approvedBy: `trust:${trustResult.level}` };
|
|
209
226
|
} else {
|
|
210
227
|
console.log(chalk.red(`\n✗ Phase "${currentPhase}" requires approval before advancing`));
|
|
211
228
|
console.log(chalk.gray(` Trust level: ${trustResult.level} (${trustResult.reason})`));
|
|
@@ -245,9 +262,10 @@ export async function advancePhaseCommand(feature, options = {}) {
|
|
|
245
262
|
// === GATE 4: Content Validation ===
|
|
246
263
|
// Validate spec.md and contracts.cs when advancing from design phase
|
|
247
264
|
if (currentPhase === 'design' && (nextPhase === 'clarify' || nextPhase === 'tasks')) {
|
|
248
|
-
// Check spec.md structure and content
|
|
249
|
-
|
|
250
|
-
|
|
265
|
+
// Check spec.md structure and content (filesystem-based)
|
|
266
|
+
const specAbsPath = getAbsoluteOutputPath(process.cwd(), feature, 'spec');
|
|
267
|
+
if (existsSync(specAbsPath)) {
|
|
268
|
+
const specContentValidation = validateSpecContent(specAbsPath);
|
|
251
269
|
|
|
252
270
|
if (!specContentValidation.valid) {
|
|
253
271
|
console.log(chalk.red('\n✗ Spec content validation failed:'));
|
|
@@ -294,8 +312,9 @@ export async function advancePhaseCommand(feature, options = {}) {
|
|
|
294
312
|
// === GATE 5: Tasks Content Validation ===
|
|
295
313
|
// Validate tasks.md structure when advancing to implement
|
|
296
314
|
if (currentPhase === 'tasks' && nextPhase === 'implement') {
|
|
297
|
-
|
|
298
|
-
|
|
315
|
+
const tasksAbsPath = getAbsoluteOutputPath(process.cwd(), feature, 'tasks');
|
|
316
|
+
if (existsSync(tasksAbsPath)) {
|
|
317
|
+
const tasksContentValidation = validateTasksContent(tasksAbsPath);
|
|
299
318
|
|
|
300
319
|
if (!tasksContentValidation.valid) {
|
|
301
320
|
console.log(chalk.red('\n✗ Tasks content validation failed:'));
|
|
@@ -318,6 +337,19 @@ export async function advancePhaseCommand(feature, options = {}) {
|
|
|
318
337
|
}
|
|
319
338
|
}
|
|
320
339
|
|
|
340
|
+
// === GATE 6: Clarifications Required ===
|
|
341
|
+
// When advancing FROM clarify, clarifications.md must exist.
|
|
342
|
+
// This enforces that morph:phase-clarify was actually run (not just skipped).
|
|
343
|
+
if (currentPhase === 'clarify') {
|
|
344
|
+
const clarPath = join(process.cwd(), '.morph', 'features', feature, '1-design', 'clarifications.md');
|
|
345
|
+
if (!existsSync(clarPath)) {
|
|
346
|
+
console.log(chalk.red('\n✗ Cannot advance from clarify — clarifications.md is missing'));
|
|
347
|
+
console.log(chalk.yellow(' Run the phase-clarify skill first: /morph:phase-clarify'));
|
|
348
|
+
console.log(chalk.gray(' Output expected at: .morph/features/' + feature + '/1-design/clarifications.md\n'));
|
|
349
|
+
process.exit(1);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
321
353
|
// Gate: Check design system when advancing to implement with UI agents
|
|
322
354
|
if (nextPhase === 'implement') {
|
|
323
355
|
const gateResult = designSystemGate(feature, process.cwd());
|
|
@@ -345,6 +377,40 @@ export async function advancePhaseCommand(feature, options = {}) {
|
|
|
345
377
|
const existingSkipped = state.features[feature].skippedPhases || [];
|
|
346
378
|
state.features[feature].skippedPhases = [...new Set([...existingSkipped, ...skippedPhases])];
|
|
347
379
|
}
|
|
380
|
+
|
|
381
|
+
// Ensure approvalGates object exists
|
|
382
|
+
if (!state.features[feature].approvalGates) {
|
|
383
|
+
state.features[feature].approvalGates = {};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const now = new Date().toISOString();
|
|
387
|
+
const gates = state.features[feature].approvalGates;
|
|
388
|
+
|
|
389
|
+
// Auto-approve proposal gate when advancing past the proposal phase.
|
|
390
|
+
// The act of advancing is itself the approval — no explicit gate check needed.
|
|
391
|
+
if (currentPhase === 'proposal' && !gates.proposal?.approved) {
|
|
392
|
+
gates.proposal = { approved: true, timestamp: now, approvedBy: 'workflow-advance' };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Persist gates approved implicitly (trust auto-approve or --skip-approval bypass).
|
|
396
|
+
if (gateApprovedImplicitly && !gates[gateApprovedImplicitly.gate]?.approved) {
|
|
397
|
+
gates[gateApprovedImplicitly.gate] = {
|
|
398
|
+
approved: true,
|
|
399
|
+
timestamp: now,
|
|
400
|
+
approvedBy: gateApprovedImplicitly.approvedBy
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Auto-approve gates for phases that were legitimately skipped.
|
|
405
|
+
// Skipped phases never run their explicit approve step, so their gate would
|
|
406
|
+
// otherwise stay false in state — making approval-status misleading.
|
|
407
|
+
for (const skippedPhase of skippedPhases) {
|
|
408
|
+
const skippedGate = APPROVAL_GATE_MAP[skippedPhase];
|
|
409
|
+
if (skippedGate && !gates[skippedGate]?.approved) {
|
|
410
|
+
gates[skippedGate] = { approved: true, timestamp: now, approvedBy: 'phase-skipped' };
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
348
414
|
saveState(state);
|
|
349
415
|
|
|
350
416
|
// Run PhaseAdvanced agent-teams hook (non-blocking)
|
|
@@ -369,6 +435,9 @@ export async function advancePhaseCommand(feature, options = {}) {
|
|
|
369
435
|
await emitValidatorDispatch(feature, validationPhase, process.cwd());
|
|
370
436
|
}
|
|
371
437
|
|
|
438
|
+
// Emit SKILL DISPATCH for any phase that has requiredSkills
|
|
439
|
+
await emitSkillDispatch(nextPhase, process.cwd());
|
|
440
|
+
|
|
372
441
|
console.log(chalk.green(`\n✓ Advanced to ${nextPhaseDef.name}`));
|
|
373
442
|
|
|
374
443
|
// Show what's needed in the new phase
|
|
@@ -407,15 +476,23 @@ function getPhaseGuidance(phase, feature) {
|
|
|
407
476
|
'Provide layout references and preferences',
|
|
408
477
|
'Review wireframes before proceeding'
|
|
409
478
|
],
|
|
410
|
-
'design':
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
479
|
+
'design': (() => {
|
|
480
|
+
const { isDotnet } = getStackProfile();
|
|
481
|
+
return [
|
|
482
|
+
`Resume spec pipeline: /morph-proposal ${feature}`,
|
|
483
|
+
'Review DECISION POINTS carefully',
|
|
484
|
+
isDotnet ? 'Approve spec.md and contracts.cs' : 'Approve spec.md and contracts.ts (TypeScript + Zod schemas)'
|
|
485
|
+
];
|
|
486
|
+
})(),
|
|
415
487
|
'clarify': [
|
|
416
488
|
`Resume spec pipeline: /morph-proposal ${feature}`,
|
|
417
489
|
'Resolve edge cases and unknowns'
|
|
418
490
|
],
|
|
491
|
+
'plan': [
|
|
492
|
+
`Resume spec pipeline: /morph-proposal ${feature}`,
|
|
493
|
+
'Review implementation plan for completeness',
|
|
494
|
+
'Approve plan before generating task breakdown'
|
|
495
|
+
],
|
|
419
496
|
'tasks': [
|
|
420
497
|
`Resume spec pipeline: /morph-proposal ${feature}`,
|
|
421
498
|
'Review task order and dependencies',
|
|
@@ -428,7 +505,7 @@ function getPhaseGuidance(phase, feature) {
|
|
|
428
505
|
],
|
|
429
506
|
'sync': [
|
|
430
507
|
'Review decisions.md for standards to promote',
|
|
431
|
-
`
|
|
508
|
+
`Decisions file: ${getOutputPath(feature, 'decisions')}`
|
|
432
509
|
]
|
|
433
510
|
};
|
|
434
511
|
|
|
@@ -466,7 +543,7 @@ function designSystemGate(feature, projectPath = '.') {
|
|
|
466
543
|
'Create a design system with one of these options:',
|
|
467
544
|
' 1. Project-level: .morph/context/design-system.md (shared across features)',
|
|
468
545
|
` 2. Feature-level: ${getOutputPath(feature, 'uiDesignSystem')} (feature-specific)`,
|
|
469
|
-
' 3.
|
|
546
|
+
' 3. Run /morph:phase-uiux skill to auto-generate from existing codebase',
|
|
470
547
|
'',
|
|
471
548
|
'Or remove UI agents if they are not needed:',
|
|
472
549
|
` morph-spec state remove-agent ${feature} blazor-builder`
|
|
@@ -475,49 +552,36 @@ function designSystemGate(feature, projectPath = '.') {
|
|
|
475
552
|
}
|
|
476
553
|
|
|
477
554
|
/**
|
|
478
|
-
* Emit
|
|
479
|
-
* Outputs
|
|
480
|
-
* as read-only subagents before proceeding to implementation.
|
|
555
|
+
* Emit a SKILL DISPATCH block when advancing to a phase that has requiredSkills.
|
|
556
|
+
* Outputs structured JSON for the LLM to read and follow.
|
|
481
557
|
* Non-blocking — fails silently.
|
|
482
558
|
*
|
|
483
|
-
* @param {string}
|
|
484
|
-
* @param {string}
|
|
485
|
-
* @param {string} cwd
|
|
559
|
+
* @param {string} nextPhase - The phase being advanced into
|
|
560
|
+
* @param {string} cwd - Project root path (for locating phases.json)
|
|
486
561
|
*/
|
|
487
|
-
async function
|
|
562
|
+
async function emitSkillDispatch(nextPhase, cwd) {
|
|
488
563
|
try {
|
|
489
|
-
|
|
490
|
-
const
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const agentData = allAgents[v.id];
|
|
500
|
-
return {
|
|
501
|
-
id: v.id,
|
|
502
|
-
title: v.title,
|
|
503
|
-
severity: agentData?.hook_behavior?.severity || 'error',
|
|
504
|
-
blocksOnFail: agentData?.hook_behavior?.blocks_on_fail ?? true,
|
|
505
|
-
checks: agentData?.hook_behavior?.validates || [],
|
|
506
|
-
taskPrompt: v.taskPrompt
|
|
507
|
-
};
|
|
508
|
-
});
|
|
564
|
+
// Try project-local phases.json first, fall back to package-level
|
|
565
|
+
const localPath = join(cwd, 'framework', 'phases.json');
|
|
566
|
+
const packagePath = join(__dirname, '../../../framework/phases.json');
|
|
567
|
+
const resolvedPath = existsSync(localPath) ? localPath : packagePath;
|
|
568
|
+
|
|
569
|
+
if (!existsSync(resolvedPath)) return;
|
|
570
|
+
|
|
571
|
+
const phasesData = JSON.parse(readFileSync(resolvedPath, 'utf8'));
|
|
572
|
+
const skills = phasesData?.phases?.[nextPhase]?.requiredSkills;
|
|
573
|
+
if (!skills || skills.length === 0) return;
|
|
509
574
|
|
|
510
575
|
const dispatch = {
|
|
511
|
-
|
|
512
|
-
phase,
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
instruction: 'Dispatch these validators as READ-ONLY subagents before continuing. Each must output { "passed": boolean, "issues": [] }. Blocking validators (blocksOnFail: true) must pass before marking phase complete.'
|
|
576
|
+
skillDispatch: true,
|
|
577
|
+
phase: nextPhase,
|
|
578
|
+
skills,
|
|
579
|
+
instruction: `These skills are MANDATORY for phase '${nextPhase}'. Invoke them at the specified trigger points using Skill(). Do NOT skip them.`,
|
|
516
580
|
};
|
|
517
581
|
|
|
518
|
-
console.log(chalk.cyan('\n---
|
|
582
|
+
console.log(chalk.cyan('\n--- SKILL DISPATCH ---'));
|
|
519
583
|
console.log(JSON.stringify(dispatch, null, 2));
|
|
520
|
-
console.log(chalk.cyan('--- END
|
|
584
|
+
console.log(chalk.cyan('--- END SKILL DISPATCH ---\n'));
|
|
521
585
|
} catch {
|
|
522
586
|
// Non-blocking — fail silently
|
|
523
587
|
}
|
|
@@ -25,7 +25,7 @@ export async function unapproveCommand(featureName, gateName, options = {}) {
|
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
setApprovalGate(featureName, gateName, false, {
|
|
28
|
+
await setApprovalGate(featureName, gateName, false, {
|
|
29
29
|
revokedBy: options.revoker || process.env.USER || process.env.USERNAME || 'user',
|
|
30
30
|
revokedAt: new Date().toISOString(),
|
|
31
31
|
reason: options.reason
|
|
@@ -85,7 +85,7 @@ export async function approveCommand(featureName, gateName, options = {}) {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
// Set approval
|
|
88
|
-
setApprovalGate(featureName, gateName, true, {
|
|
88
|
+
await setApprovalGate(featureName, gateName, true, {
|
|
89
89
|
approvedBy: options.approver || process.env.USER || process.env.USERNAME || 'user',
|
|
90
90
|
approvedAt: new Date().toISOString(),
|
|
91
91
|
notes: options.notes
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* State Management Commands
|
|
3
|
-
*/
|
|
4
|
-
export { stateCommand } from './state.js';
|
|
5
|
-
export { advancePhaseCommand } from './advance-phase.js';
|
|
6
|
-
export { approveCommand, unapproveCommand, approvalStatusCommand } from './approve.js';
|
|
7
|
-
export {
|
|
8
|
-
export { phaseRunCommand } from './phase-runner.js';
|
|
1
|
+
/**
|
|
2
|
+
* State Management Commands
|
|
3
|
+
*/
|
|
4
|
+
export { stateCommand } from './state.js';
|
|
5
|
+
export { advancePhaseCommand } from './advance-phase.js';
|
|
6
|
+
export { approveCommand, unapproveCommand, approvalStatusCommand } from './approve.js';
|
|
7
|
+
export { phaseRunCommand } from './phase-runner.js';
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
import chalk from 'chalk';
|
|
25
25
|
import { join } from 'path';
|
|
26
|
-
import { checkPhaseEligibility, computePassRate } from '../../lib/phase-chain/
|
|
26
|
+
import { checkPhaseEligibility, computePassRate } from '../../lib/phase-chain/phase-validator.js';
|
|
27
27
|
import { advancePhaseCommand } from './advance-phase.js';
|
|
28
28
|
import { loadState } from '../../core/state/state-manager.js';
|
|
29
29
|
import { derivePhase } from '../../core/state/state-manager.js';
|
|
@@ -11,6 +11,36 @@ import { logger } from '../../utils/logger.js';
|
|
|
11
11
|
import * as StateManager from '../../core/state/state-manager.js';
|
|
12
12
|
import { derivePhase } from '../../core/state/state-manager.js';
|
|
13
13
|
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Phase Protection
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Phases that have approval gates and cannot be set directly via `state set`.
|
|
20
|
+
* These phases must be reached through `morph-spec phase advance` which enforces
|
|
21
|
+
* all validation gates (approval, output requirements, spec content, etc.).
|
|
22
|
+
*
|
|
23
|
+
* Lightweight phases (proposal, setup, clarify, sync) are not in this list
|
|
24
|
+
* because they have no approval gates and are safe to set directly.
|
|
25
|
+
*/
|
|
26
|
+
export const PROTECTED_PHASES = ['design', 'tasks', 'uiux', 'implement'];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Validate a direct phase-set request.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} phase - The target phase value
|
|
32
|
+
* @returns {{ blocked: boolean, message?: string }}
|
|
33
|
+
*/
|
|
34
|
+
export function validatePhaseSet(phase) {
|
|
35
|
+
if (PROTECTED_PHASES.includes(phase)) {
|
|
36
|
+
return {
|
|
37
|
+
blocked: true,
|
|
38
|
+
message: `Phase "${phase}" has approval gates — use "morph-spec phase advance" instead of setting it directly.`
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return { blocked: false };
|
|
42
|
+
}
|
|
43
|
+
|
|
14
44
|
// ============================================================================
|
|
15
45
|
// Command Functions
|
|
16
46
|
// ============================================================================
|
|
@@ -174,10 +204,30 @@ async function setCommand(featureName, key, value, options) {
|
|
|
174
204
|
}
|
|
175
205
|
|
|
176
206
|
try {
|
|
177
|
-
//
|
|
207
|
+
// Guard: block protected phases unless --force is explicitly passed
|
|
178
208
|
if (key === 'phase') {
|
|
179
|
-
|
|
180
|
-
|
|
209
|
+
const phaseValidation = validatePhaseSet(value);
|
|
210
|
+
if (phaseValidation.blocked && !options.force) {
|
|
211
|
+
logger.error(phaseValidation.message);
|
|
212
|
+
logger.blank();
|
|
213
|
+
logger.dim(` Protected phases (have approval gates): ${PROTECTED_PHASES.join(', ')}`);
|
|
214
|
+
logger.dim(` Use the validated advance flow instead:`);
|
|
215
|
+
logger.dim(` morph-spec phase advance ${featureName}`);
|
|
216
|
+
logger.blank();
|
|
217
|
+
logger.dim(` To bypass all gates (expert use only):`);
|
|
218
|
+
logger.dim(` morph-spec state set ${featureName} phase ${value} --force`);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (phaseValidation.blocked) {
|
|
223
|
+
logger.warn(`FORCED phase override to "${value}" — all approval gates bypassed.`);
|
|
224
|
+
} else {
|
|
225
|
+
logger.warn(`Direct phase override — validation gates may be bypassed.`);
|
|
226
|
+
logger.dim(` The actual phase is derived from filesystem subdirectories`);
|
|
227
|
+
logger.dim(` (0-proposal/, 1-design/, 4-tasks/, 5-implement/). This sets a state.json`);
|
|
228
|
+
logger.dim(` override used only for lightweight phases (setup, clarify, sync).`);
|
|
229
|
+
logger.dim(` Recommended: morph-spec phase advance ${featureName}`);
|
|
230
|
+
}
|
|
181
231
|
logger.blank();
|
|
182
232
|
}
|
|
183
233
|
|
|
@@ -328,13 +378,18 @@ async function markOutputCommand(featureName, outputType, options) {
|
|
|
328
378
|
logger.dim(' Usage: morph-spec state mark-output <feature> <output-type>');
|
|
329
379
|
logger.blank();
|
|
330
380
|
logger.dim(' Valid types:');
|
|
331
|
-
logger.dim('
|
|
332
|
-
logger.dim('
|
|
381
|
+
logger.dim(' Proposal: proposal');
|
|
382
|
+
logger.dim(' Design: schemaAnalysis, spec, contracts, contractsVsa, decisions');
|
|
383
|
+
logger.dim(' Clarify: clarifications');
|
|
384
|
+
logger.dim(' Tasks: tasks');
|
|
385
|
+
logger.dim(' UI/UX: uiDesignSystem, uiMockups, uiComponents, uiFlows');
|
|
386
|
+
logger.dim(' Implement: recap');
|
|
333
387
|
logger.blank();
|
|
334
|
-
logger.dim(' Note:
|
|
388
|
+
logger.dim(' Note: kebab-case aliases accepted (e.g., ui-design-system, schema-analysis)');
|
|
335
389
|
logger.blank();
|
|
336
390
|
logger.dim(' Examples:');
|
|
337
391
|
logger.dim(' morph-spec state mark-output my-feature spec');
|
|
392
|
+
logger.dim(' morph-spec state mark-output my-feature schemaAnalysis');
|
|
338
393
|
logger.dim(' morph-spec state mark-output my-feature uiDesignSystem');
|
|
339
394
|
logger.dim(' morph-spec state mark-output my-feature ui-design-system');
|
|
340
395
|
process.exit(1);
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MORPH-SPEC Task Expand Command
|
|
3
|
+
*
|
|
4
|
+
* Expands a task into sub-tasks for low-impact scope escalation.
|
|
5
|
+
* The original task gets status "expanded" and sub-tasks are appended to taskList.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* morph-spec task expand <feature> <task-id> --into "T017a: Title" "T017b: Title"
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
import { loadState, saveState } from '../../core/state/state-manager.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse a sub-task string "T017a: Title" into { id, title }.
|
|
16
|
+
*/
|
|
17
|
+
function parseSubTask(str) {
|
|
18
|
+
const match = str.match(/^(T\w+)\s*[:\-\u2014]\s*(.+)$/);
|
|
19
|
+
if (match) return { id: match[1], title: match[2].trim() };
|
|
20
|
+
// Fallback: use entire string as title, no id parseable
|
|
21
|
+
return { id: str.split(/\s/)[0], title: str };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Core expansion logic — exported for testing.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} featureName
|
|
28
|
+
* @param {string} taskId - ID of the task to expand
|
|
29
|
+
* @param {string[]} subTaskStrings - Sub-task definitions ("T017a: Title")
|
|
30
|
+
* @returns {{ success: boolean, error?: string }}
|
|
31
|
+
*/
|
|
32
|
+
export function expandTask(featureName, taskId, subTaskStrings) {
|
|
33
|
+
const state = loadState();
|
|
34
|
+
const feature = state.features[featureName];
|
|
35
|
+
|
|
36
|
+
if (!feature) {
|
|
37
|
+
return { success: false, error: `Feature "${featureName}" not found` };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!feature.taskList) {
|
|
41
|
+
return { success: false, error: `No taskList found for "${featureName}"` };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const task = feature.taskList.find(t => t.id === taskId);
|
|
45
|
+
if (!task) {
|
|
46
|
+
return { success: false, error: `Task "${taskId}" not found in taskList` };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (task.status === 'done' || task.status === 'completed') {
|
|
50
|
+
return { success: false, error: `Task "${taskId}" is already completed — cannot expand` };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Parse sub-tasks
|
|
54
|
+
const subTasks = subTaskStrings.map(parseSubTask);
|
|
55
|
+
const subTaskIds = subTasks.map(s => s.id);
|
|
56
|
+
|
|
57
|
+
// Mark original as expanded
|
|
58
|
+
task.status = 'expanded';
|
|
59
|
+
task.subTasks = subTaskIds;
|
|
60
|
+
task.expandedAt = new Date().toISOString();
|
|
61
|
+
|
|
62
|
+
// Append sub-tasks to taskList
|
|
63
|
+
for (const sub of subTasks) {
|
|
64
|
+
feature.taskList.push({
|
|
65
|
+
id: sub.id,
|
|
66
|
+
title: sub.title,
|
|
67
|
+
status: 'pending',
|
|
68
|
+
dependencies: [],
|
|
69
|
+
files: [],
|
|
70
|
+
checkpoint: null,
|
|
71
|
+
parentTask: taskId
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
feature.updatedAt = new Date().toISOString();
|
|
76
|
+
saveState(state);
|
|
77
|
+
|
|
78
|
+
return { success: true, subTaskIds };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* CLI command handler
|
|
83
|
+
*/
|
|
84
|
+
export async function taskExpandCommand(feature, taskId, options) {
|
|
85
|
+
if (!options.into || options.into.length === 0) {
|
|
86
|
+
console.error(chalk.red('--into is required with at least one sub-task'));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const result = expandTask(feature, taskId, options.into);
|
|
91
|
+
|
|
92
|
+
if (!result.success) {
|
|
93
|
+
console.error(chalk.red(`\n ${result.error}`));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
console.log(chalk.green(`\n Task ${taskId} expanded into ${result.subTaskIds.length} sub-tasks:`));
|
|
98
|
+
result.subTaskIds.forEach(id => console.log(chalk.gray(` - ${id}`)));
|
|
99
|
+
console.log('');
|
|
100
|
+
}
|