@polymorphism-tech/morph-spec 4.9.0 → 4.10.1
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/README.md +2 -2
- package/bin/morph-spec.js +30 -0
- package/bin/task-manager.js +34 -22
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +35 -98
- package/framework/agents/backend/api-designer.md +3 -0
- package/framework/agents/backend/dotnet-senior.md +3 -0
- package/framework/agents/backend/ef-modeler.md +2 -0
- package/framework/agents/backend/hangfire-orchestrator.md +2 -0
- package/framework/agents/backend/ms-agent-expert.md +2 -0
- package/framework/agents/frontend/blazor-builder.md +2 -0
- package/framework/agents/frontend/nextjs-expert.md +2 -0
- package/framework/agents/infrastructure/azure-architect.md +2 -0
- package/framework/agents/infrastructure/azure-deploy-specialist.md +2 -0
- package/framework/agents/infrastructure/bicep-architect.md +2 -0
- package/framework/agents/infrastructure/container-specialist.md +2 -0
- package/framework/agents/infrastructure/devops-engineer.md +3 -0
- package/framework/agents/infrastructure/infra-architect.md +3 -0
- package/framework/agents/integrations/asaas-financial.md +2 -0
- package/framework/agents/integrations/azure-identity.md +2 -0
- package/framework/agents/integrations/clerk-auth.md +3 -0
- package/framework/agents/integrations/hangfire-integration.md +2 -0
- package/framework/agents/integrations/resend-email.md +2 -0
- package/framework/agents.json +37 -7
- package/framework/commands/commit.md +166 -0
- package/framework/commands/morph-apply.md +156 -155
- package/framework/commands/morph-archive.md +33 -27
- package/framework/commands/morph-infra.md +83 -77
- package/framework/commands/morph-preflight.md +97 -55
- package/framework/commands/morph-proposal.md +131 -58
- package/framework/commands/morph-status.md +36 -30
- package/framework/commands/morph-troubleshoot.md +68 -59
- package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +154 -31
- package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +7 -84
- 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 +3 -2
- 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 +55 -2
- package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
- 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/shared/compact-restore.js +100 -0
- package/framework/hooks/shared/dispatch-helpers.js +116 -0
- package/framework/hooks/shared/phase-utils.js +9 -5
- package/framework/hooks/shared/state-reader.js +27 -3
- package/framework/phases.json +30 -7
- package/framework/rules/csharp-standards.md +3 -0
- package/framework/rules/frontend-standards.md +2 -0
- package/framework/rules/infrastructure-standards.md +3 -0
- package/framework/rules/morph-workflow.md +143 -86
- package/framework/rules/nextjs-standards.md +2 -0
- package/framework/rules/testing-standards.md +3 -0
- package/framework/skills/level-0-meta/mcp-registry.json +86 -51
- package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +139 -0
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +42 -19
- package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +8 -5
- package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +8 -6
- package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +362 -0
- package/framework/skills/level-0-meta/morph-init/SKILL.md +114 -20
- package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +362 -0
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +95 -87
- package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +24 -0
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +43 -43
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +1 -2
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +23 -12
- 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 +247 -0
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +270 -0
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +499 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +38 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +472 -0
- 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 +246 -0
- package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +238 -0
- package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +14 -0
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +312 -0
- package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +324 -0
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +146 -0
- package/framework/standards/integration/mcp/mcp-tools.md +25 -7
- package/framework/templates/docs/onboarding.md +2 -2
- package/package.json +3 -4
- package/src/commands/agents/dispatch-agents.js +50 -3
- package/src/commands/mcp/mcp-setup.js +39 -2
- package/src/commands/phase/phase-reset.js +74 -0
- package/src/commands/project/doctor.js +26 -7
- package/src/commands/project/update.js +4 -4
- package/src/commands/scope/escalate.js +215 -0
- package/src/commands/state/advance-phase.js +27 -53
- package/src/commands/state/state.js +1 -1
- package/src/commands/task/expand.js +100 -0
- package/src/core/paths/output-schema.js +4 -3
- package/src/core/state/phase-state-machine.js +7 -4
- package/src/core/state/state-manager.js +4 -3
- 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 +22 -16
- package/src/lib/scope/impact-analyzer.js +106 -0
- package/src/lib/stack-filter.js +58 -0
- package/src/lib/tasks/task-parser.js +1 -1
- package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
- package/src/scripts/setup-infra.js +68 -18
- package/src/utils/agents-installer.js +51 -17
- package/src/utils/claude-md-injector.js +90 -0
- package/src/utils/file-copier.js +0 -1
- package/src/utils/hooks-installer.js +16 -5
- package/src/utils/skills-installer.js +67 -7
- package/CLAUDE.md +0 -98
- package/framework/memory/patterns-learned.md +0 -766
- package/framework/skills/level-0-meta/brainstorming/SKILL.md +0 -137
- package/framework/skills/level-0-meta/frontend-review/SKILL.md +0 -359
- package/framework/skills/level-0-meta/post-implementation/SKILL.md +0 -362
- package/framework/skills/level-0-meta/terminal-title/SKILL.md +0 -61
- package/framework/skills/level-0-meta/terminal-title/scripts/set_title.sh +0 -65
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +0 -216
- package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +0 -252
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -383
- package/framework/skills/level-1-workflows/phase-implement/SKILL.md +0 -492
- package/framework/skills/level-1-workflows/phase-setup/SKILL.md +0 -195
- package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +0 -271
- package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +0 -286
- package/src/commands/project/index.js +0 -8
- package/src/core/index.js +0 -10
- 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/workflows/index.js +0 -7
- 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/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/sanitizer/context-sanitizer.js +0 -221
- package/src/sanitizer/patterns.js +0 -163
- 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-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
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MORPH-SPEC Scope Escalate Command
|
|
3
|
+
*
|
|
4
|
+
* Handles mid-implementation scope discovery by analyzing impact,
|
|
5
|
+
* regressing phase or expanding tasks, and creating an audit trail.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* morph-spec scope escalate <feature> --task <id> --reason "..."
|
|
9
|
+
* morph-spec scope escalate <feature> --task <id> --reason "..." --dry-run
|
|
10
|
+
* morph-spec scope escalate <feature> --task <id> --reason "..." --target design
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
15
|
+
import { join } from 'path';
|
|
16
|
+
import { loadState, saveState } from '../../core/state/state-manager.js';
|
|
17
|
+
import { analyzeImpact } from '../../lib/scope/impact-analyzer.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Core escalation logic — exported for testing.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} featureName
|
|
23
|
+
* @param {Object} options
|
|
24
|
+
* @param {string} options.task - Trigger task ID
|
|
25
|
+
* @param {string} options.reason - Reason for escalation
|
|
26
|
+
* @param {string} [options.target] - Override target phase
|
|
27
|
+
* @param {boolean} [options.dryRun] - Don't execute, just recommend
|
|
28
|
+
* @returns {Promise<{ success: boolean, error?: string, recommendation?: Object }>}
|
|
29
|
+
*/
|
|
30
|
+
export async function escalateScope(featureName, options) {
|
|
31
|
+
const state = loadState();
|
|
32
|
+
const feature = state.features[featureName];
|
|
33
|
+
|
|
34
|
+
if (!feature) {
|
|
35
|
+
return { success: false, error: `Feature "${featureName}" not found in state.json` };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (feature.phase !== 'implement') {
|
|
39
|
+
return { success: false, error: `Scope escalation only available during implement phase (current: ${feature.phase})` };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Read tasks.md
|
|
43
|
+
const tasksPath = join(process.cwd(), '.morph', 'features', featureName, '4-tasks', 'tasks.md');
|
|
44
|
+
let tasksMd = '';
|
|
45
|
+
try {
|
|
46
|
+
tasksMd = readFileSync(tasksPath, 'utf-8');
|
|
47
|
+
} catch {
|
|
48
|
+
return { success: false, error: 'tasks.md not found' };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Read spec.md (optional — not all features have it)
|
|
52
|
+
const specPath = join(process.cwd(), '.morph', 'features', featureName, '1-design', 'spec.md');
|
|
53
|
+
let specMd = '';
|
|
54
|
+
try { specMd = readFileSync(specPath, 'utf-8'); } catch { /* ok */ }
|
|
55
|
+
|
|
56
|
+
// Analyze impact
|
|
57
|
+
const analysis = analyzeImpact({
|
|
58
|
+
triggerTaskId: options.task,
|
|
59
|
+
tasksMd,
|
|
60
|
+
specMd,
|
|
61
|
+
reason: options.reason
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Apply target override
|
|
65
|
+
if (options.target) {
|
|
66
|
+
analysis.recommendation = `regress-${options.target}`;
|
|
67
|
+
analysis.targetPhase = options.target;
|
|
68
|
+
if (analysis.targetPhase === 'design') analysis.impact = 'high';
|
|
69
|
+
else if (analysis.targetPhase === 'tasks') analysis.impact = 'medium';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Dry-run: return recommendation without modifying state
|
|
73
|
+
if (options.dryRun) {
|
|
74
|
+
return { success: true, recommendation: analysis };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Build escalation record
|
|
78
|
+
const escId = `ESC-${String((feature.escalations?.length || 0) + 1).padStart(3, '0')}`;
|
|
79
|
+
const record = {
|
|
80
|
+
id: escId,
|
|
81
|
+
triggerTask: options.task,
|
|
82
|
+
reason: options.reason,
|
|
83
|
+
impact: analysis.impact,
|
|
84
|
+
action: analysis.recommendation,
|
|
85
|
+
affectedTasks: analysis.affectedTasks,
|
|
86
|
+
reviewedTasks: [],
|
|
87
|
+
targetPhase: analysis.targetPhase || 'tasks',
|
|
88
|
+
previousPhase: 'implement',
|
|
89
|
+
createdAt: new Date().toISOString(),
|
|
90
|
+
resolvedAt: null
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Execute based on recommendation
|
|
94
|
+
if (analysis.recommendation.startsWith('regress')) {
|
|
95
|
+
const targetPhase = analysis.targetPhase || 'tasks';
|
|
96
|
+
|
|
97
|
+
// Flag completed tasks as needsReview (supports both v2 'done' and v3 'completed')
|
|
98
|
+
if (feature.taskList) {
|
|
99
|
+
for (const task of feature.taskList) {
|
|
100
|
+
if (task.status === 'done' || task.status === 'completed') {
|
|
101
|
+
task.needsReview = true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Reset phase
|
|
107
|
+
feature.phase = targetPhase;
|
|
108
|
+
|
|
109
|
+
// Reset relevant approval gates
|
|
110
|
+
if (feature.approvalGates) {
|
|
111
|
+
if (targetPhase === 'tasks' && feature.approvalGates.tasks) {
|
|
112
|
+
feature.approvalGates.tasks = { approved: false, timestamp: null, approvedBy: null };
|
|
113
|
+
}
|
|
114
|
+
if (targetPhase === 'design') {
|
|
115
|
+
if (feature.approvalGates.design) {
|
|
116
|
+
feature.approvalGates.design = { approved: false, timestamp: null, approvedBy: null };
|
|
117
|
+
}
|
|
118
|
+
if (feature.approvalGates.tasks) {
|
|
119
|
+
feature.approvalGates.tasks = { approved: false, timestamp: null, approvedBy: null };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Write escalation record to state
|
|
126
|
+
if (!feature.escalations) feature.escalations = [];
|
|
127
|
+
feature.escalations.push(record);
|
|
128
|
+
feature.updatedAt = new Date().toISOString();
|
|
129
|
+
|
|
130
|
+
saveState(state);
|
|
131
|
+
|
|
132
|
+
// Create/append escalation-log.md
|
|
133
|
+
writeEscalationLog(featureName, record);
|
|
134
|
+
|
|
135
|
+
return { success: true, recommendation: analysis, escalationId: escId };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Write escalation log entry to 4-tasks/escalation-log.md
|
|
140
|
+
*/
|
|
141
|
+
function writeEscalationLog(featureName, record) {
|
|
142
|
+
const logDir = join(process.cwd(), '.morph', 'features', featureName, '4-tasks');
|
|
143
|
+
mkdirSync(logDir, { recursive: true });
|
|
144
|
+
const logPath = join(logDir, 'escalation-log.md');
|
|
145
|
+
|
|
146
|
+
const doneTasks = record.reviewedTasks?.length > 0
|
|
147
|
+
? record.reviewedTasks.join(', ')
|
|
148
|
+
: '(all completed tasks flagged)';
|
|
149
|
+
|
|
150
|
+
const entry = `
|
|
151
|
+
## ${record.id} — ${record.createdAt.split('T')[0]}
|
|
152
|
+
- **Trigger:** ${record.triggerTask}
|
|
153
|
+
- **Discovery:** ${record.reason}
|
|
154
|
+
- **Impact:** ${record.impact} (${record.affectedTasks.length} tasks affected: ${record.affectedTasks.join(', ')})
|
|
155
|
+
- **Action:** ${record.action} → ${record.targetPhase} phase
|
|
156
|
+
- **Affected completed tasks flagged for review:** ${doneTasks}
|
|
157
|
+
`;
|
|
158
|
+
|
|
159
|
+
let existing = '';
|
|
160
|
+
try { existing = readFileSync(logPath, 'utf-8'); } catch { /* new file */ }
|
|
161
|
+
|
|
162
|
+
if (!existing) {
|
|
163
|
+
writeFileSync(logPath, `# Scope Escalation Log\n${entry}`);
|
|
164
|
+
} else {
|
|
165
|
+
writeFileSync(logPath, existing + entry);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* CLI command handler
|
|
171
|
+
*/
|
|
172
|
+
export async function scopeEscalateCommand(feature, options) {
|
|
173
|
+
console.log(chalk.cyan('\n========================================'));
|
|
174
|
+
console.log(chalk.cyan(' MORPH-SPEC SCOPE ESCALATION'));
|
|
175
|
+
console.log(chalk.cyan('========================================\n'));
|
|
176
|
+
|
|
177
|
+
if (!options.task) {
|
|
178
|
+
console.error(chalk.red('--task <id> is required'));
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
if (!options.reason) {
|
|
182
|
+
console.error(chalk.red('--reason "..." is required'));
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const result = await escalateScope(feature, options);
|
|
187
|
+
|
|
188
|
+
if (!result.success) {
|
|
189
|
+
console.error(chalk.red(`\n ${result.error}`));
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const rec = result.recommendation;
|
|
194
|
+
console.log(chalk.gray('Trigger task:'), rec.triggerTask);
|
|
195
|
+
console.log(chalk.gray('Impact:'), rec.impact);
|
|
196
|
+
console.log(chalk.gray('Affected tasks:'), rec.affectedTasks.join(', '));
|
|
197
|
+
console.log(chalk.gray('Recommendation:'), rec.recommendation);
|
|
198
|
+
if (rec.targetPhase) console.log(chalk.gray('Target phase:'), rec.targetPhase);
|
|
199
|
+
|
|
200
|
+
if (options.dryRun) {
|
|
201
|
+
console.log(chalk.cyan('\n Dry-run — no changes made'));
|
|
202
|
+
process.exit(2);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (rec.recommendation === 'expand') {
|
|
206
|
+
console.log(chalk.green(`\n Escalation ${result.escalationId} recorded (low impact)`));
|
|
207
|
+
console.log(chalk.yellow(` Action: Use task expand to split the task:`));
|
|
208
|
+
console.log(chalk.gray(` morph-spec task expand ${feature} ${options.task} --into "ID: Title" ...`));
|
|
209
|
+
} else {
|
|
210
|
+
console.log(chalk.green(`\n Escalation ${result.escalationId} executed`));
|
|
211
|
+
console.log(chalk.yellow(` Phase regressed to: ${rec.targetPhase || 'tasks'}`));
|
|
212
|
+
}
|
|
213
|
+
console.log(chalk.gray(` Escalation log: .morph/features/${feature}/4-tasks/escalation-log.md`));
|
|
214
|
+
console.log('');
|
|
215
|
+
}
|
|
@@ -16,18 +16,19 @@ import { detectDesignSystem, hasUIAgentsActive } from '../../lib/detectors/desig
|
|
|
16
16
|
import { getOutputPath, getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
|
|
17
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
22
|
import { getStackProfile } from '../../lib/stack/stack-profile.js';
|
|
23
23
|
import { readFileSync, existsSync } from 'fs';
|
|
24
24
|
import { join, dirname } from 'path';
|
|
25
25
|
import { fileURLToPath } from 'url';
|
|
26
|
+
import { emitValidatorDispatch } from '../../lib/validators/shared/emit-validator-dispatch.js';
|
|
26
27
|
|
|
27
28
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
28
29
|
|
|
29
30
|
// Phase order for advancing (skips optional phases unless active)
|
|
30
|
-
const PHASE_ORDER = ['proposal', 'setup', 'uiux', 'design', 'clarify', 'tasks', 'implement', 'sync'];
|
|
31
|
+
const PHASE_ORDER = ['proposal', 'setup', 'uiux', 'design', 'clarify', 'plan', 'tasks', 'implement', 'sync'];
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* Phases that require an explicit approval gate before advancing.
|
|
@@ -36,6 +37,7 @@ const PHASE_ORDER = ['proposal', 'setup', 'uiux', 'design', 'clarify', 'tasks',
|
|
|
36
37
|
*/
|
|
37
38
|
export const APPROVAL_GATE_MAP = {
|
|
38
39
|
'design': 'design',
|
|
40
|
+
'plan': 'plan',
|
|
39
41
|
'tasks': 'tasks',
|
|
40
42
|
'uiux': 'uiux'
|
|
41
43
|
};
|
|
@@ -78,13 +80,16 @@ function getNextPhase(currentPhase, feature, skipOptional, featureName) {
|
|
|
78
80
|
|
|
79
81
|
// Check if this phase is part of a combined phase
|
|
80
82
|
if (workflowConfig.phases.combined) {
|
|
83
|
+
let isCombined = false;
|
|
81
84
|
for (const [combinedName, phases] of Object.entries(workflowConfig.phases.combined)) {
|
|
82
85
|
if (phases.includes(candidate) && candidate !== combinedName) {
|
|
83
86
|
console.log(chalk.gray(` ⏩ Skipping ${candidate}: combined into ${combinedName} phase`));
|
|
84
87
|
skippedPhases.push(candidate);
|
|
85
|
-
|
|
88
|
+
isCombined = true;
|
|
89
|
+
break;
|
|
86
90
|
}
|
|
87
91
|
}
|
|
92
|
+
if (isCombined) continue;
|
|
88
93
|
}
|
|
89
94
|
|
|
90
95
|
// Check if workflow only runs specific phases
|
|
@@ -332,6 +337,19 @@ export async function advancePhaseCommand(feature, options = {}) {
|
|
|
332
337
|
}
|
|
333
338
|
}
|
|
334
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
|
+
|
|
335
353
|
// Gate: Check design system when advancing to implement with UI agents
|
|
336
354
|
if (nextPhase === 'implement') {
|
|
337
355
|
const gateResult = designSystemGate(feature, process.cwd());
|
|
@@ -470,6 +488,11 @@ function getPhaseGuidance(phase, feature) {
|
|
|
470
488
|
`Resume spec pipeline: /morph-proposal ${feature}`,
|
|
471
489
|
'Resolve edge cases and unknowns'
|
|
472
490
|
],
|
|
491
|
+
'plan': [
|
|
492
|
+
`Resume spec pipeline: /morph-proposal ${feature}`,
|
|
493
|
+
'Review implementation plan for completeness',
|
|
494
|
+
'Approve plan before generating task breakdown'
|
|
495
|
+
],
|
|
473
496
|
'tasks': [
|
|
474
497
|
`Resume spec pipeline: /morph-proposal ${feature}`,
|
|
475
498
|
'Review task order and dependencies',
|
|
@@ -528,55 +551,6 @@ function designSystemGate(feature, projectPath = '.') {
|
|
|
528
551
|
};
|
|
529
552
|
}
|
|
530
553
|
|
|
531
|
-
/**
|
|
532
|
-
* Emit Tier-4 validation dispatch block after phase advance.
|
|
533
|
-
* Outputs a JSON block listing active Tier-4 validators for the LLM to dispatch
|
|
534
|
-
* as read-only subagents before proceeding to implementation.
|
|
535
|
-
* Non-blocking — fails silently.
|
|
536
|
-
*
|
|
537
|
-
* @param {string} featureName
|
|
538
|
-
* @param {string} phase
|
|
539
|
-
* @param {string} cwd
|
|
540
|
-
*/
|
|
541
|
-
async function emitValidatorDispatch(featureName, phase, cwd) {
|
|
542
|
-
try {
|
|
543
|
-
const { buildDispatchConfig } = await import('../agents/dispatch-agents.js');
|
|
544
|
-
const config = await buildDispatchConfig(cwd, featureName, phase, { mode: 'validate' });
|
|
545
|
-
const validators = config.agents?.filter(a => a.tier === 4);
|
|
546
|
-
if (!validators || validators.length === 0) return;
|
|
547
|
-
|
|
548
|
-
const agentsPath = join(__dirname, '../../../framework/agents.json');
|
|
549
|
-
const agentsData = JSON.parse(readFileSync(agentsPath, 'utf8'));
|
|
550
|
-
const allAgents = agentsData.agents || {};
|
|
551
|
-
|
|
552
|
-
const validatorEntries = validators.map(v => {
|
|
553
|
-
const agentData = allAgents[v.id];
|
|
554
|
-
return {
|
|
555
|
-
id: v.id,
|
|
556
|
-
title: v.title,
|
|
557
|
-
severity: agentData?.hook_behavior?.severity || 'error',
|
|
558
|
-
blocksOnFail: agentData?.hook_behavior?.blocks_on_fail ?? true,
|
|
559
|
-
checks: agentData?.hook_behavior?.validates || [],
|
|
560
|
-
taskPrompt: v.taskPrompt
|
|
561
|
-
};
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
const dispatch = {
|
|
565
|
-
validationRequired: true,
|
|
566
|
-
phase,
|
|
567
|
-
feature: featureName,
|
|
568
|
-
validators: validatorEntries,
|
|
569
|
-
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.'
|
|
570
|
-
};
|
|
571
|
-
|
|
572
|
-
console.log(chalk.cyan('\n--- VALIDATION DISPATCH ---'));
|
|
573
|
-
console.log(JSON.stringify(dispatch, null, 2));
|
|
574
|
-
console.log(chalk.cyan('--- END VALIDATION DISPATCH ---\n'));
|
|
575
|
-
} catch {
|
|
576
|
-
// Non-blocking — fail silently
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
554
|
/**
|
|
581
555
|
* Emit a SKILL DISPATCH block when advancing to a phase that has requiredSkills.
|
|
582
556
|
* Outputs structured JSON for the LLM to read and follow.
|
|
@@ -224,7 +224,7 @@ async function setCommand(featureName, key, value, options) {
|
|
|
224
224
|
} else {
|
|
225
225
|
logger.warn(`Direct phase override — validation gates may be bypassed.`);
|
|
226
226
|
logger.dim(` The actual phase is derived from filesystem subdirectories`);
|
|
227
|
-
logger.dim(` (0-proposal/, 1-design/,
|
|
227
|
+
logger.dim(` (0-proposal/, 1-design/, 4-tasks/, 5-implement/). This sets a state.json`);
|
|
228
228
|
logger.dim(` override used only for lightweight phases (setup, clarify, sync).`);
|
|
229
229
|
logger.dim(` Recommended: morph-spec phase advance ${featureName}`);
|
|
230
230
|
}
|
|
@@ -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
|
+
}
|
|
@@ -26,17 +26,18 @@ export const OUTPUT_SCHEMA = {
|
|
|
26
26
|
proposal: { filename: 'proposal.md', phaseDir: '0-proposal', phase: 'proposal', protected: false },
|
|
27
27
|
schemaAnalysis:{ filename: 'schema-analysis.md', phaseDir: '1-design', phase: 'design', protected: true, approvalGate: 'design' },
|
|
28
28
|
spec: { filename: 'spec.md', phaseDir: '1-design', phase: 'design', protected: true, approvalGate: 'design' },
|
|
29
|
-
clarifications:{ filename: 'clarifications.md', phaseDir: '
|
|
29
|
+
clarifications:{ filename: 'clarifications.md', phaseDir: '1-design', phase: 'clarify', protected: false },
|
|
30
30
|
contracts: { filename: 'contracts.cs', phaseDir: '1-design', phase: 'design', protected: true, approvalGate: 'design' },
|
|
31
31
|
contractsTs: { filename: 'contracts.ts', phaseDir: '1-design', phase: 'design', protected: true, approvalGate: 'design' },
|
|
32
32
|
contractsVsa: { filename: 'contracts-vsa.cs', phaseDir: '1-design', phase: 'design', protected: true, approvalGate: 'design' },
|
|
33
|
-
|
|
33
|
+
plan: { filename: 'plan.md', phaseDir: '3-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
|
|
34
|
+
tasks: { filename: 'tasks.md', phaseDir: '4-tasks', phase: 'tasks', protected: true, approvalGate: 'tasks' },
|
|
34
35
|
uiDesignSystem:{ filename: 'design-system.md', phaseDir: '2-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
35
36
|
uiMockups: { filename: 'mockups.md', phaseDir: '2-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
36
37
|
uiComponents: { filename: 'components.md', phaseDir: '2-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
37
38
|
uiFlows: { filename: 'flows.md', phaseDir: '2-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
38
39
|
decisions: { filename: 'decisions.md', phaseDir: '1-design', phase: 'design', protected: false },
|
|
39
|
-
recap: { filename: 'recap.md', phaseDir: '
|
|
40
|
+
recap: { filename: 'recap.md', phaseDir: '5-implement', phase: 'implement', protected: false },
|
|
40
41
|
};
|
|
41
42
|
|
|
42
43
|
// ============================================================================
|
|
@@ -14,7 +14,8 @@ const VALID_TRANSITIONS = {
|
|
|
14
14
|
'setup': ['uiux', 'design'], // Can skip UI/UX if no frontend
|
|
15
15
|
'uiux': ['design'],
|
|
16
16
|
'design': ['clarify'],
|
|
17
|
-
'clarify': ['
|
|
17
|
+
'clarify': ['plan'],
|
|
18
|
+
'plan': ['tasks'],
|
|
18
19
|
'tasks': ['implement'],
|
|
19
20
|
'implement': ['sync', 'archived'], // Can skip sync
|
|
20
21
|
'sync': ['archived']
|
|
@@ -29,9 +30,10 @@ const PHASE_NAMES = {
|
|
|
29
30
|
'uiux': 'UI/UX Design (Phase 1.5)',
|
|
30
31
|
'design': 'Design (Phase 2)',
|
|
31
32
|
'clarify': 'Clarify (Phase 3)',
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
'
|
|
33
|
+
'plan': 'Plan (Phase 4)',
|
|
34
|
+
'tasks': 'Tasks (Phase 5)',
|
|
35
|
+
'implement': 'Implement (Phase 6)',
|
|
36
|
+
'sync': 'Sync (Phase 7)',
|
|
35
37
|
'archived': 'Archived'
|
|
36
38
|
};
|
|
37
39
|
|
|
@@ -124,6 +126,7 @@ export function getPhaseSequence() {
|
|
|
124
126
|
'uiux', // optional
|
|
125
127
|
'design',
|
|
126
128
|
'clarify',
|
|
129
|
+
'plan',
|
|
127
130
|
'tasks',
|
|
128
131
|
'implement',
|
|
129
132
|
'sync', // optional
|
|
@@ -27,12 +27,13 @@ export function getStatePath() {
|
|
|
27
27
|
* Returns the phase corresponding to the highest-numbered folder present.
|
|
28
28
|
*
|
|
29
29
|
* @param {string} featurePath - Absolute path to .morph/features/{feature}/
|
|
30
|
-
* @returns {string} Phase name: 'implement' | 'tasks' | 'uiux' | 'design' | 'proposal' | 'setup'
|
|
30
|
+
* @returns {string} Phase name: 'implement' | 'tasks' | 'plan' | 'uiux' | 'design' | 'proposal' | 'setup'
|
|
31
31
|
*/
|
|
32
32
|
export function derivePhase(featurePath) {
|
|
33
33
|
const phaseMap = [
|
|
34
|
-
['
|
|
35
|
-
['
|
|
34
|
+
['5-implement', 'implement'],
|
|
35
|
+
['4-tasks', 'tasks'],
|
|
36
|
+
['3-plan', 'plan'],
|
|
36
37
|
['2-ui', 'uiux'],
|
|
37
38
|
['1-design', 'design'],
|
|
38
39
|
['0-proposal', 'proposal'],
|