@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
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MORPH-SPEC Worktree Setup Command
|
|
3
|
+
*
|
|
4
|
+
* Creates a git worktree at .worktrees/{feature}/ on branch morph/{feature}.
|
|
5
|
+
* Used by SessionStart hook to isolate feature work from the main branch.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* morph-spec worktree setup <feature>
|
|
9
|
+
* morph-spec worktree setup <feature> --fresh
|
|
10
|
+
*
|
|
11
|
+
* Exit codes:
|
|
12
|
+
* 0 — success (created) or fail-open (not a git repo, git error)
|
|
13
|
+
* 2 — worktree already exists (signals resume scenario to hook caller)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { execSync } from 'child_process';
|
|
17
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
18
|
+
import { join } from 'path';
|
|
19
|
+
|
|
20
|
+
// ── Pure helpers (exported for tests) ──────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if a directory is inside a git repository.
|
|
24
|
+
* @param {string} cwd
|
|
25
|
+
* @returns {boolean}
|
|
26
|
+
*/
|
|
27
|
+
export function isGitRepo(cwd = process.cwd()) {
|
|
28
|
+
try {
|
|
29
|
+
execSync('git rev-parse --git-dir', { cwd, stdio: 'pipe' });
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if a branch exists locally.
|
|
38
|
+
* @param {string} branch
|
|
39
|
+
* @param {string} cwd
|
|
40
|
+
* @returns {boolean}
|
|
41
|
+
*/
|
|
42
|
+
export function branchExists(branch, cwd = process.cwd()) {
|
|
43
|
+
try {
|
|
44
|
+
execSync(`git show-ref --verify --quiet refs/heads/${branch}`, { cwd, stdio: 'pipe' });
|
|
45
|
+
return true;
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if a worktree path is already registered with git.
|
|
53
|
+
* @param {string} worktreePath Relative or absolute path
|
|
54
|
+
* @param {string} cwd
|
|
55
|
+
* @returns {boolean}
|
|
56
|
+
*/
|
|
57
|
+
export function worktreeExists(worktreePath, cwd = process.cwd()) {
|
|
58
|
+
try {
|
|
59
|
+
const output = execSync('git worktree list --porcelain', { cwd, stdio: 'pipe' }).toString();
|
|
60
|
+
const normalizedPath = worktreePath.replace(/\\/g, '/');
|
|
61
|
+
const absPath = normalizedPath.startsWith('/') ? normalizedPath : `${cwd.replace(/\\/g, '/')}/${normalizedPath}`;
|
|
62
|
+
return output.replace(/\\/g, '/').includes(absPath);
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Build the worktree directory path (always forward slashes).
|
|
70
|
+
* @param {string} feature
|
|
71
|
+
* @param {string} cwd
|
|
72
|
+
* @param {{ fresh?: boolean }} options
|
|
73
|
+
* @returns {string}
|
|
74
|
+
*/
|
|
75
|
+
export function buildWorktreePath(feature, cwd = process.cwd(), options = {}) {
|
|
76
|
+
const suffix = options.fresh ? `-${getDateSuffix()}` : '';
|
|
77
|
+
const normalizedCwd = cwd.replace(/\\/g, '/');
|
|
78
|
+
return `${normalizedCwd}/.worktrees/${feature}${suffix}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Build the branch name for a feature worktree.
|
|
83
|
+
* @param {string} feature
|
|
84
|
+
* @param {{ fresh?: boolean }} options
|
|
85
|
+
* @returns {string}
|
|
86
|
+
*/
|
|
87
|
+
export function buildBranchName(feature, options = {}) {
|
|
88
|
+
const suffix = options.fresh ? `-${getDateSuffix()}` : '';
|
|
89
|
+
return `morph/${feature}${suffix}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getDateSuffix() {
|
|
93
|
+
return new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Command handler ─────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Main command handler for `morph-spec worktree setup <feature>`
|
|
100
|
+
* @param {string} feature
|
|
101
|
+
* @param {{ fresh?: boolean, cwd?: string }} options
|
|
102
|
+
*/
|
|
103
|
+
export async function worktreeSetupCommand(feature, options = {}) {
|
|
104
|
+
const cwd = options.cwd || process.cwd();
|
|
105
|
+
|
|
106
|
+
// Gate 1: Must be a git repo
|
|
107
|
+
if (!isGitRepo(cwd)) {
|
|
108
|
+
console.log(JSON.stringify({ created: false, error: 'not-a-git-repo', feature }));
|
|
109
|
+
process.exit(0); // Fail-open
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const branch = buildBranchName(feature, options);
|
|
113
|
+
const worktreePath = buildWorktreePath(feature, cwd, options);
|
|
114
|
+
|
|
115
|
+
// Gate 2: If worktree already registered and not --fresh, signal resume
|
|
116
|
+
if (!options.fresh && worktreeExists(worktreePath, cwd)) {
|
|
117
|
+
console.log(JSON.stringify({ created: false, alreadyExists: true, path: worktreePath, branch, feature }));
|
|
118
|
+
process.exit(2); // Convention: 2 = already exists
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
// Create branch if needed (based off current HEAD)
|
|
123
|
+
if (!branchExists(branch, cwd)) {
|
|
124
|
+
execSync(`git branch ${branch}`, { cwd, stdio: 'pipe' });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Create worktree directory parent if needed
|
|
128
|
+
const parentDir = join(cwd, '.worktrees');
|
|
129
|
+
if (!existsSync(parentDir)) {
|
|
130
|
+
mkdirSync(parentDir, { recursive: true });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Add worktree
|
|
134
|
+
execSync(`git worktree add "${worktreePath}" ${branch}`, { cwd, stdio: 'pipe' });
|
|
135
|
+
|
|
136
|
+
// Persist in state (non-blocking — feature may not be in state yet)
|
|
137
|
+
try {
|
|
138
|
+
const { loadState, saveState } = await import('../../lib/state/state-manager.js');
|
|
139
|
+
const state = loadState(cwd);
|
|
140
|
+
if (state?.features?.[feature]) {
|
|
141
|
+
state.features[feature].worktree = { path: worktreePath, branch, createdAt: new Date().toISOString() };
|
|
142
|
+
saveState(state, cwd);
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
// Non-blocking
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
console.log(JSON.stringify({ created: true, path: worktreePath, branch, feature }));
|
|
149
|
+
process.exit(0);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
console.log(JSON.stringify({ created: false, error: (err.message || 'unknown').slice(0, 200), feature }));
|
|
152
|
+
process.exit(0); // Fail-open
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -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
|
+
}
|