@polymorphism-tech/morph-spec 4.8.7 → 4.8.9
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 +22 -1
- package/bin/task-manager.cjs +120 -16
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/agents.json +1855 -1815
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +141 -23
- package/framework/hooks/claude-code/statusline.py +0 -12
- package/framework/hooks/claude-code/statusline.sh +6 -2
- package/framework/hooks/claude-code/stop/validate-completion.js +70 -23
- package/framework/hooks/dev/guard-version-numbers.js +1 -1
- package/framework/skills/level-0-meta/morph-init/SKILL.md +44 -6
- package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +67 -16
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +77 -7
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +115 -51
- package/framework/skills/level-1-workflows/phase-implement/SKILL.md +139 -1
- package/framework/skills/level-1-workflows/phase-setup/SKILL.md +29 -6
- package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +115 -5
- package/framework/skills/level-1-workflows/phase-tasks/references/tasks-example.md +173 -0
- package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +944 -933
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +429 -0
- package/framework/templates/REGISTRY.json +1909 -1888
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +282 -0
- package/framework/templates/docs/spec.md +33 -1
- package/package.json +1 -1
- package/src/commands/agents/dispatch-agents.js +430 -0
- package/src/commands/agents/index.js +2 -1
- package/src/commands/project/doctor.js +138 -2
- package/src/commands/state/state.js +20 -4
- package/src/commands/templates/generate-contracts.js +445 -0
- package/src/commands/templates/index.js +1 -0
- package/src/lib/validators/validation-runner.js +19 -7
|
@@ -1,20 +1,47 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* PreCompact Hook:
|
|
4
|
+
* PreCompact Hook: Inject MORPH Context into Compact Summary
|
|
5
5
|
*
|
|
6
6
|
* Event: PreCompact
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Injects a rich morph-spec state snapshot as additionalContext so that
|
|
9
|
+
* Claude's compacted summary includes feature state, task progress, outputs,
|
|
10
|
+
* and next steps. Ensures the session resumes with full morph awareness
|
|
11
|
+
* without having to re-read all the spec files.
|
|
12
|
+
*
|
|
13
|
+
* Also saves snapshot to .morph/memory/ for SessionStart to reference.
|
|
10
14
|
*
|
|
11
15
|
* Fail-open: exits 0 on any error.
|
|
12
16
|
*/
|
|
13
17
|
|
|
14
|
-
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
18
|
+
import { writeFileSync, mkdirSync, existsSync, readFileSync } from 'fs';
|
|
15
19
|
import { join } from 'path';
|
|
16
|
-
import {
|
|
17
|
-
|
|
20
|
+
import {
|
|
21
|
+
stateExists, loadState, getActiveFeature,
|
|
22
|
+
derivePhaseForFeature, getPendingGates, getOutputs,
|
|
23
|
+
} from '../../shared/state-reader.js';
|
|
24
|
+
import { injectContext, pass } from '../../shared/hook-response.js';
|
|
25
|
+
|
|
26
|
+
const DECISIONS_MAX_CHARS = 1500;
|
|
27
|
+
const MAX_PENDING_TASKS = 8;
|
|
28
|
+
|
|
29
|
+
const PHASE_POSITIONS = {
|
|
30
|
+
proposal: 1, setup: 1,
|
|
31
|
+
uiux: 2, design: 2,
|
|
32
|
+
clarify: 3,
|
|
33
|
+
tasks: 4,
|
|
34
|
+
implement: 5, sync: 5,
|
|
35
|
+
};
|
|
36
|
+
const PIPELINE_TOTAL = 5;
|
|
37
|
+
|
|
38
|
+
const NEXT_STEP_HINT = {
|
|
39
|
+
proposal: (n) => `Continue spec pipeline: /morph-proposal ${n}`,
|
|
40
|
+
design: (n) => `Approve design gate: morph-spec approve ${n} design`,
|
|
41
|
+
uiux: (n) => `Approve UI gate: morph-spec approve ${n} uiux`,
|
|
42
|
+
tasks: (n) => `Approve tasks gate: morph-spec approve ${n} tasks`,
|
|
43
|
+
implement: (n) => `Continue implementation: morph-spec task next ${n}`,
|
|
44
|
+
};
|
|
18
45
|
|
|
19
46
|
try {
|
|
20
47
|
if (!stateExists()) pass();
|
|
@@ -23,39 +50,130 @@ try {
|
|
|
23
50
|
if (!state) pass();
|
|
24
51
|
|
|
25
52
|
const active = getActiveFeature();
|
|
53
|
+
const cwd = process.cwd();
|
|
26
54
|
|
|
55
|
+
// ── Disk snapshot (for SessionStart restore) ──────────────────────────────
|
|
27
56
|
const snapshot = {
|
|
28
|
-
timestamp:
|
|
29
|
-
event:
|
|
57
|
+
timestamp: new Date().toISOString(),
|
|
58
|
+
event: 'pre-compact',
|
|
30
59
|
activeFeature: active ? active.name : null,
|
|
31
|
-
features:
|
|
60
|
+
features: {},
|
|
32
61
|
};
|
|
33
62
|
|
|
34
|
-
// Save minimal feature summaries
|
|
35
63
|
for (const [name, feature] of Object.entries(state.features || {})) {
|
|
36
64
|
snapshot.features[name] = {
|
|
37
|
-
phase:
|
|
38
|
-
status:
|
|
65
|
+
phase: feature.phase || derivePhaseForFeature(name),
|
|
66
|
+
status: feature.status,
|
|
39
67
|
workflow: feature.workflow,
|
|
40
|
-
tasks:
|
|
68
|
+
tasks: feature.tasks,
|
|
41
69
|
approvalGates: Object.fromEntries(
|
|
42
70
|
Object.entries(feature.approvalGates || {}).map(([gate, data]) => [gate, data.approved])
|
|
43
|
-
)
|
|
71
|
+
),
|
|
44
72
|
};
|
|
45
73
|
}
|
|
46
74
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
75
|
+
const memoryDir = join(cwd, '.morph', 'memory');
|
|
76
|
+
if (!existsSync(memoryDir)) mkdirSync(memoryDir, { recursive: true });
|
|
77
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
78
|
+
writeFileSync(join(memoryDir, `pre-compact-${ts}.json`), JSON.stringify(snapshot, null, 2), 'utf-8');
|
|
79
|
+
|
|
80
|
+
// ── Nothing more to inject if no active feature ───────────────────────────
|
|
81
|
+
if (!active) pass();
|
|
82
|
+
|
|
83
|
+
const { name, feature } = active;
|
|
84
|
+
const phase = derivePhaseForFeature(name);
|
|
85
|
+
const pos = PHASE_POSITIONS[phase];
|
|
86
|
+
const phaseLabel = pos ? `${phase} (${pos}/${PIPELINE_TOTAL})` : phase;
|
|
87
|
+
|
|
88
|
+
const lines = [
|
|
89
|
+
'=== MORPH-SPEC COMPACT CONTEXT ===',
|
|
90
|
+
`Preserve this section verbatim in the compact summary.`,
|
|
91
|
+
'',
|
|
92
|
+
`Feature : ${name}`,
|
|
93
|
+
`Phase : ${phaseLabel}`,
|
|
94
|
+
`Status : ${feature.status} | Workflow: ${feature.workflow || 'auto'}`,
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
// Task progress
|
|
98
|
+
const tasks = feature.tasks || {};
|
|
99
|
+
const done = tasks.completed || 0;
|
|
100
|
+
const total = tasks.total || 0;
|
|
101
|
+
if (total > 0) {
|
|
102
|
+
const pct = Math.round(done / total * 100);
|
|
103
|
+
lines.push(`Tasks : ${done}/${total} completed (${pct}%)`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// In-progress + next pending tasks from taskList
|
|
107
|
+
const taskList = Array.isArray(feature.taskList) ? feature.taskList : [];
|
|
108
|
+
const inProg = taskList.filter(t => t.status === 'in_progress');
|
|
109
|
+
const pending = taskList.filter(t => t.status === 'pending').slice(0, MAX_PENDING_TASKS);
|
|
110
|
+
|
|
111
|
+
if (inProg.length > 0) {
|
|
112
|
+
lines.push('');
|
|
113
|
+
lines.push('In progress:');
|
|
114
|
+
for (const t of inProg) lines.push(` [${t.id}] ${t.title}`);
|
|
115
|
+
}
|
|
116
|
+
if (pending.length > 0) {
|
|
117
|
+
lines.push('');
|
|
118
|
+
lines.push('Next pending tasks:');
|
|
119
|
+
for (const t of pending) lines.push(` [${t.id}] ${t.title}`);
|
|
120
|
+
if (taskList.filter(t => t.status === 'pending').length > MAX_PENDING_TASKS) {
|
|
121
|
+
lines.push(` ... (use morph-spec task next ${name} for full list)`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Created outputs
|
|
126
|
+
const outputs = getOutputs(name) || {};
|
|
127
|
+
const created = Object.entries(outputs).filter(([, o]) => o.created);
|
|
128
|
+
const missing = Object.entries(outputs).filter(([, o]) => !o.created);
|
|
129
|
+
if (created.length > 0) {
|
|
130
|
+
lines.push('');
|
|
131
|
+
lines.push('Created outputs:');
|
|
132
|
+
for (const [type, o] of created) lines.push(` ✓ ${type}: ${o.path}`);
|
|
133
|
+
}
|
|
134
|
+
if (missing.length > 0) {
|
|
135
|
+
lines.push('Pending outputs:');
|
|
136
|
+
for (const [type, o] of missing) lines.push(` ✗ ${type}: ${o.path}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Active agents
|
|
140
|
+
const agents = feature.activeAgents || [];
|
|
141
|
+
if (agents.length > 0) {
|
|
142
|
+
lines.push('');
|
|
143
|
+
lines.push(`Active agents: ${agents.join(', ')}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Pending approval gates
|
|
147
|
+
const pendingGates = getPendingGates(name);
|
|
148
|
+
if (pendingGates.length > 0) {
|
|
149
|
+
lines.push(`Pending approvals: ${pendingGates.join(', ')}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Key decisions (truncated to stay within context budget)
|
|
153
|
+
const decisionsPath = join(cwd, `.morph/features/${name}/1-design/decisions.md`);
|
|
154
|
+
if (existsSync(decisionsPath)) {
|
|
155
|
+
try {
|
|
156
|
+
const raw = readFileSync(decisionsPath, 'utf-8');
|
|
157
|
+
const content = raw.length > DECISIONS_MAX_CHARS
|
|
158
|
+
? raw.slice(0, DECISIONS_MAX_CHARS) + '\n[... truncated — full file at decisions.md]'
|
|
159
|
+
: raw;
|
|
160
|
+
lines.push('');
|
|
161
|
+
lines.push('Key decisions (decisions.md):');
|
|
162
|
+
lines.push(content);
|
|
163
|
+
} catch { /* non-blocking */ }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Next step
|
|
167
|
+
const hintFn = NEXT_STEP_HINT[phase];
|
|
168
|
+
if (hintFn) {
|
|
169
|
+
lines.push('');
|
|
170
|
+
lines.push(`Next step: ${hintFn(name)}`);
|
|
51
171
|
}
|
|
52
172
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2), 'utf-8');
|
|
173
|
+
lines.push('');
|
|
174
|
+
lines.push('=== END MORPH CONTEXT ===');
|
|
56
175
|
|
|
57
|
-
|
|
176
|
+
injectContext(lines.join('\n'));
|
|
58
177
|
} catch {
|
|
59
|
-
// Fail-open
|
|
60
178
|
process.exit(0);
|
|
61
179
|
}
|
|
@@ -544,18 +544,6 @@ def main():
|
|
|
544
544
|
line += f" ({toks})"
|
|
545
545
|
parts2.append(line + suffix)
|
|
546
546
|
|
|
547
|
-
# Token breakdown from JSONL (session totals: input / output / cached)
|
|
548
|
-
if entries:
|
|
549
|
-
tok = get_token_metrics(entries)
|
|
550
|
-
tok_parts = []
|
|
551
|
-
if tok['input']:
|
|
552
|
-
tok_parts.append(f"in:{format_tokens(tok['input'])}")
|
|
553
|
-
if tok['output']:
|
|
554
|
-
tok_parts.append(f"out:{format_tokens(tok['output'])}")
|
|
555
|
-
if tok['cached']:
|
|
556
|
-
tok_parts.append(f"↩{format_tokens(tok['cached'])}")
|
|
557
|
-
if tok_parts:
|
|
558
|
-
parts2.append(f"{GRAY}{' '.join(tok_parts)}{R}")
|
|
559
547
|
|
|
560
548
|
# Git info (branch + diff stats)
|
|
561
549
|
git = get_git_info(cwd)
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# morph-spec statusline — installed globally to ~/.claude/statusline.sh
|
|
3
3
|
# Claude Code invokes this with JSON via stdin after each response.
|
|
4
|
-
# Requires: Python 3 available as `python3` on PATH.
|
|
4
|
+
# Requires: Python 3 available as `python3` or `python` on PATH.
|
|
5
5
|
|
|
6
6
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
-
python3
|
|
7
|
+
if command -v python3 &>/dev/null; then
|
|
8
|
+
python3 "${SCRIPT_DIR}/statusline.py" 2>/dev/null || true
|
|
9
|
+
elif command -v python &>/dev/null; then
|
|
10
|
+
python "${SCRIPT_DIR}/statusline.py" 2>/dev/null || true
|
|
11
|
+
fi
|
|
@@ -1,23 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Stop Hook: Validate Completion
|
|
4
|
+
* Stop Hook: Validate Completion + Phase Transition Alerts
|
|
5
5
|
*
|
|
6
6
|
* Event: Stop
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* 1. Detects transitions into the `implement` phase and recommends /compact
|
|
9
|
+
* before starting — ensures Claude has a clean context for writing code.
|
|
10
|
+
* 2. Checks for incomplete work (tasks, missing outputs, pending gates).
|
|
11
|
+
*
|
|
12
|
+
* Phase transition tracking: stores last-seen phase per feature in
|
|
13
|
+
* .morph/memory/phase-watch.json so the alert fires exactly once,
|
|
14
|
+
* the first Stop after the transition occurs.
|
|
12
15
|
*
|
|
13
16
|
* Uses stop_hook_active env check to prevent infinite loops.
|
|
14
17
|
*
|
|
15
18
|
* Fail-open: exits 0 on any error.
|
|
16
19
|
*/
|
|
17
20
|
|
|
18
|
-
import {
|
|
21
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
22
|
+
import { join } from 'path';
|
|
23
|
+
import {
|
|
24
|
+
stateExists, getActiveFeature, getMissingOutputs, derivePhaseForFeature,
|
|
25
|
+
} from '../../shared/state-reader.js';
|
|
19
26
|
import { injectContext, pass } from '../../shared/hook-response.js';
|
|
20
27
|
|
|
28
|
+
// Phases where compaction is strongly recommended before starting
|
|
29
|
+
const COMPACT_TRIGGER_PHASES = new Set(['implement']);
|
|
30
|
+
|
|
31
|
+
// Phases where compaction is suggested when arriving from a heavy spec phase
|
|
32
|
+
const HEAVY_SOURCE_PHASES = new Set(['tasks', 'design', 'clarify', 'uiux']);
|
|
33
|
+
|
|
21
34
|
try {
|
|
22
35
|
// Prevent infinite loop
|
|
23
36
|
if (process.env.MORPH_STOP_HOOK_ACTIVE === '1') pass();
|
|
@@ -28,10 +41,48 @@ try {
|
|
|
28
41
|
if (!active) pass();
|
|
29
42
|
|
|
30
43
|
const { name, feature } = active;
|
|
44
|
+
const cwd = process.cwd();
|
|
31
45
|
const warnings = [];
|
|
32
46
|
|
|
33
|
-
//
|
|
34
|
-
|
|
47
|
+
// ── Phase transition detection ────────────────────────────────────────────
|
|
48
|
+
const currentPhase = feature.phase || derivePhaseForFeature(name, cwd);
|
|
49
|
+
|
|
50
|
+
const watchPath = join(cwd, '.morph', 'memory', 'phase-watch.json');
|
|
51
|
+
let phaseWatch = {};
|
|
52
|
+
try {
|
|
53
|
+
if (existsSync(watchPath)) phaseWatch = JSON.parse(readFileSync(watchPath, 'utf-8'));
|
|
54
|
+
} catch { /* corrupt file — start fresh */ }
|
|
55
|
+
|
|
56
|
+
const lastPhase = phaseWatch[name];
|
|
57
|
+
|
|
58
|
+
// Update stored phase (always, so the alert only fires once per transition)
|
|
59
|
+
try {
|
|
60
|
+
phaseWatch[name] = currentPhase;
|
|
61
|
+
writeFileSync(watchPath, JSON.stringify(phaseWatch, null, 2), 'utf-8');
|
|
62
|
+
} catch { /* non-blocking */ }
|
|
63
|
+
|
|
64
|
+
// Alert when entering implement from a heavy spec phase
|
|
65
|
+
const isPhaseTransition = lastPhase && lastPhase !== currentPhase;
|
|
66
|
+
const enteringImplement = COMPACT_TRIGGER_PHASES.has(currentPhase);
|
|
67
|
+
const comingFromHeavyPhase = HEAVY_SOURCE_PHASES.has(lastPhase);
|
|
68
|
+
|
|
69
|
+
if (isPhaseTransition && enteringImplement && comingFromHeavyPhase) {
|
|
70
|
+
warnings.push(
|
|
71
|
+
`⚡ Phase advanced: ${lastPhase} → implement`,
|
|
72
|
+
``,
|
|
73
|
+
`💡 Recommended before starting implementation:`,
|
|
74
|
+
` Run /compact to free up context — the spec, design and task files`,
|
|
75
|
+
` loaded during planning take significant space. A compact now gives`,
|
|
76
|
+
` Claude a clean slate focused on writing code.`,
|
|
77
|
+
``,
|
|
78
|
+
` After /compact, resume with: morph-spec task next ${name}`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── Incomplete work checks ────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
// Incomplete tasks during implement phase
|
|
85
|
+
if (currentPhase === 'implement' && feature.tasks) {
|
|
35
86
|
const remaining = (feature.tasks.total || 0) - (feature.tasks.completed || 0);
|
|
36
87
|
if (remaining > 0) {
|
|
37
88
|
warnings.push(`${remaining} task(s) remaining for feature '${name}'`);
|
|
@@ -41,11 +92,11 @@ try {
|
|
|
41
92
|
}
|
|
42
93
|
}
|
|
43
94
|
|
|
44
|
-
//
|
|
45
|
-
if (['proposal', 'design', 'clarify', 'tasks', 'uiux'].includes(
|
|
95
|
+
// Missing outputs in spec phases
|
|
96
|
+
if (['proposal', 'design', 'clarify', 'tasks', 'uiux'].includes(currentPhase)) {
|
|
46
97
|
const missing = getMissingOutputs(name);
|
|
47
98
|
if (missing.length > 0) {
|
|
48
|
-
warnings.push(`Missing outputs for '${name}' (${
|
|
99
|
+
warnings.push(`Missing outputs for '${name}' (${currentPhase} phase):`);
|
|
49
100
|
for (const output of missing.slice(0, 5)) {
|
|
50
101
|
warnings.push(` - ${output.type}: ${output.path}`);
|
|
51
102
|
}
|
|
@@ -55,34 +106,30 @@ try {
|
|
|
55
106
|
}
|
|
56
107
|
}
|
|
57
108
|
|
|
58
|
-
//
|
|
109
|
+
// Pending approval gates
|
|
59
110
|
if (feature.approvalGates) {
|
|
60
111
|
const pendingGates = Object.entries(feature.approvalGates)
|
|
61
112
|
.filter(([, gate]) => !gate.approved && !gate.timestamp)
|
|
62
|
-
.map(([
|
|
113
|
+
.map(([gateName]) => gateName);
|
|
63
114
|
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
const gatePhaseMap = { proposal: 'proposal', uiux: 'uiux', design: 'design', tasks: 'tasks' };
|
|
67
|
-
return gatePhaseMap[gate] !== undefined;
|
|
68
|
-
});
|
|
115
|
+
const gatePhaseMap = { proposal: 'proposal', uiux: 'uiux', design: 'design', tasks: 'tasks' };
|
|
116
|
+
const relevant = pendingGates.filter(gate => gatePhaseMap[gate] !== undefined);
|
|
69
117
|
|
|
70
|
-
if (
|
|
71
|
-
warnings.push(`Pending approvals: ${
|
|
118
|
+
if (relevant.length > 0) {
|
|
119
|
+
warnings.push(`Pending approvals: ${relevant.join(', ')}`);
|
|
72
120
|
}
|
|
73
121
|
}
|
|
74
122
|
|
|
75
123
|
if (warnings.length === 0) pass();
|
|
76
124
|
|
|
77
125
|
const message = [
|
|
78
|
-
'MORPH-SPEC:
|
|
126
|
+
'MORPH-SPEC:',
|
|
79
127
|
...warnings.map(w => ` ${w}`),
|
|
80
128
|
'',
|
|
81
|
-
|
|
129
|
+
`Status: morph-spec status ${name}`,
|
|
82
130
|
].join('\n');
|
|
83
131
|
|
|
84
132
|
injectContext(message);
|
|
85
133
|
} catch {
|
|
86
|
-
// Fail-open
|
|
87
134
|
process.exit(0);
|
|
88
135
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Scope: Framework codebase only (dev hook)
|
|
8
8
|
*
|
|
9
9
|
* Blocks Write/Edit to files that contain hardcoded version patterns
|
|
10
|
-
* like "MORPH-SPEC v4.8.
|
|
10
|
+
* like "MORPH-SPEC v4.8.9". Version should only be in package.json.
|
|
11
11
|
*
|
|
12
12
|
* Checked extensions: .md, .cs, .css, .js (covers templates + source)
|
|
13
13
|
* Exceptions: CHANGELOG.md, node_modules/, test/, package.json, package-lock.json
|
|
@@ -111,6 +111,9 @@ Gather evidence to build a stack map. Run these in parallel:
|
|
|
111
111
|
| `Glob` | `database/migrations/**` | Supabase local dev |
|
|
112
112
|
| `Glob` | `supabase/**` | Supabase project config |
|
|
113
113
|
| `Glob` | `**/*.razor` | Blazor |
|
|
114
|
+
| `Glob` | `**/Abstractions/IHandler.cs` | VSA pattern (KanaiyaKatarmal) |
|
|
115
|
+
| `Glob` | `**/Pipelines/ValidationDecorator.cs` | VSA pipeline |
|
|
116
|
+
| `Glob` | `**/Features/**/*Feature` | VSA feature folder suffix |
|
|
114
117
|
| `Read` | `.env.example` | Env vars reveal integrations |
|
|
115
118
|
| `Read` | `README.md` | Existing project description |
|
|
116
119
|
| `Grep` | `@supabase/supabase-js` | Supabase dep in any package.json |
|
|
@@ -120,14 +123,20 @@ Gather evidence to build a stack map. Run these in parallel:
|
|
|
120
123
|
Read each found `package.json` for dependency names. Build an **evidence map**:
|
|
121
124
|
|
|
122
125
|
```
|
|
123
|
-
stack:
|
|
124
|
-
integrations:
|
|
125
|
-
uiLibrary:
|
|
126
|
-
monorepo:
|
|
127
|
-
frontendPath:
|
|
128
|
-
backendPath:
|
|
126
|
+
stack: [nextjs, dotnet, ...]
|
|
127
|
+
integrations: [supabase, docker, clerk, ...]
|
|
128
|
+
uiLibrary: shadcn | fluent-ui | mudblazor | null
|
|
129
|
+
monorepo: true | false
|
|
130
|
+
frontendPath: src/frontend (if monorepo)
|
|
131
|
+
backendPath: src/backend (if monorepo)
|
|
132
|
+
architectureStyle: vertical-slice | null ← novo
|
|
129
133
|
```
|
|
130
134
|
|
|
135
|
+
**VSA detection:** `architectureStyle: "vertical-slice"` se qualquer um destes existir:
|
|
136
|
+
- `Abstractions/IHandler.cs` encontrado pelo Glob
|
|
137
|
+
- `Pipelines/ValidationDecorator.cs` encontrado pelo Glob
|
|
138
|
+
- 2+ pastas com sufixo `Feature` em `Features/` (ex: `BookFeature`, `OrderFeature`)
|
|
139
|
+
|
|
131
140
|
---
|
|
132
141
|
|
|
133
142
|
## Step 4 — Ask Targeted Questions
|
|
@@ -195,6 +204,35 @@ Read `.morph/config/config.json` and merge into `project`:
|
|
|
195
204
|
}
|
|
196
205
|
```
|
|
197
206
|
|
|
207
|
+
**Se `architectureStyle: "vertical-slice"` foi detectado no Step 3**, adicione também:
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{
|
|
211
|
+
"architecture": {
|
|
212
|
+
"style": "vertical-slice"
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
E informe o usuário:
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
✓ Detected: Vertical Slice Architecture (IHandler.cs / *Feature folders)
|
|
221
|
+
→ config.json: architecture.style = "vertical-slice"
|
|
222
|
+
→ morph-spec will use vsa-architect + contracts-vsa.cs for features
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
> Esta key é o que ativa o `vsa-architect` em vez do `domain-architect` durante `/morph-proposal`.
|
|
226
|
+
|
|
227
|
+
**Se o projeto ainda não existe (novo projeto VSA do zero)**, informe também:
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
Para criar um novo projeto VSA:
|
|
231
|
+
dotnet new install https://github.com/polymorphism-tech/Morph_Template_VerticalSliceArchitecture
|
|
232
|
+
dotnet new vsa -n MyProject
|
|
233
|
+
cd MyProject && npx morph-spec init
|
|
234
|
+
```
|
|
235
|
+
|
|
198
236
|
---
|
|
199
237
|
|
|
200
238
|
## Step 7 — Configure MCPs
|
|
@@ -34,18 +34,52 @@ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
|
|
|
34
34
|
└──┬──────────────┬────────┘
|
|
35
35
|
│ YES │ NO
|
|
36
36
|
▼ ▼
|
|
37
|
-
Use MCP tool
|
|
38
|
-
(+ fallback) │ Does it
|
|
39
|
-
│
|
|
40
|
-
│
|
|
41
|
-
│
|
|
42
|
-
|
|
43
|
-
│ YES
|
|
44
|
-
▼
|
|
45
|
-
|
|
46
|
-
|
|
37
|
+
Use MCP tool ┌──────────────────────────────┐
|
|
38
|
+
(+ fallback) │ Does it involve MULTIPLE │
|
|
39
|
+
│ independent specialist tasks │
|
|
40
|
+
│ (domain analysis + schema, │
|
|
41
|
+
│ backend + frontend tasks)? │
|
|
42
|
+
└──┬───────────────┬───────────┘
|
|
43
|
+
│ YES │ NO
|
|
44
|
+
▼ ▼
|
|
45
|
+
Dispatch parallel ┌──────────────────┐
|
|
46
|
+
Task subagents │ Does it require │
|
|
47
|
+
(see below) │ MULTI-STEP │
|
|
48
|
+
│ exploration of │
|
|
49
|
+
│ many files? │
|
|
50
|
+
└──┬──────────┬─────┘
|
|
51
|
+
│ YES │ NO
|
|
52
|
+
▼ ▼
|
|
53
|
+
Use Task Do it
|
|
54
|
+
(subagent) directly
|
|
47
55
|
```
|
|
48
56
|
|
|
57
|
+
### When to use Task subagents for agent dispatch
|
|
58
|
+
|
|
59
|
+
Dispatch specialist agents as **Task subagents** when ALL conditions are met:
|
|
60
|
+
- Phase is `design`, `tasks`, or `implement`
|
|
61
|
+
- Feature has **2+ active specialist agents** (check `activeAgents` in state.json)
|
|
62
|
+
- Work can be divided into **independent parallel tasks** with clear outputs
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
Feature with multiple active agents?
|
|
66
|
+
│
|
|
67
|
+
├─ design phase → Dispatch domain-architect (complexity analysis)
|
|
68
|
+
│ + tech leads (spec validation) — in PARALLEL
|
|
69
|
+
│
|
|
70
|
+
├─ tasks phase → Dispatch per-domain task planners (if spec has 50+ reqs
|
|
71
|
+
│ OR 3+ independent domains detected)
|
|
72
|
+
│
|
|
73
|
+
└─ implement phase → Dispatch per-domain implementers when tasks.total ≥ 6
|
|
74
|
+
AND feature spans multiple domains (backend + frontend,
|
|
75
|
+
or backend + infra, etc.)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Key principle (from real-world usage):** Define subagents by **TASK**, not by **ROLE**.
|
|
79
|
+
- ✅ "Analyze this proposal for DDD complexity and return level + blueprint" (task)
|
|
80
|
+
- ✅ "Implement backend tasks T001-T005 for feature X" (task)
|
|
81
|
+
- ❌ "Be a domain architect" (vague role assignment — poor results)
|
|
82
|
+
|
|
49
83
|
---
|
|
50
84
|
|
|
51
85
|
## Tools Per Phase
|
|
@@ -80,18 +114,19 @@ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
|
|
|
80
114
|
| Action | Tool | Why |
|
|
81
115
|
|--------|------|-----|
|
|
82
116
|
| Verify feature state | **Bash** `npx morph-spec state get {feature}` | CLI command |
|
|
83
|
-
| Detect agents + standards | **Read** `.morph/framework/agents.json` → match keywords → **Bash** `npx morph-spec state add-agent {f} {id}` per match |
|
|
117
|
+
| Detect agents + standards | **Read** `.morph/framework/agents.json` → match keywords → **Bash** `npx morph-spec state add-agent {f} {id}` per match | Keyword matching is fast inline |
|
|
84
118
|
| Read project context | **Read** `.morph/context/README.md` | Single file |
|
|
85
119
|
| Read config | **Read** `.morph/config.json` | Single file |
|
|
86
120
|
| Scan project structure | **Glob** `src/**/*.{ts,tsx,cs}` | Understand codebase layout |
|
|
87
121
|
| Get repo metadata | **GitHub MCP** `get_repo()` | Structured repo info (if MCP available) |
|
|
122
|
+
| Get dispatch config for next phases | **Bash** `npx morph-spec dispatch-agents {feature} design` | Shows which agents will be dispatched in design phase |
|
|
88
123
|
| Update state | **Bash** `npx morph-spec state set ...` | CLI command |
|
|
89
124
|
|
|
90
125
|
**MCPs used:** GitHub (optional — for repo metadata).
|
|
91
126
|
|
|
92
127
|
**Anti-patterns:**
|
|
93
128
|
- ❌ Calling `detect-agents` CLI (it doesn't exist — read `.morph/framework/agents.json` directly)
|
|
94
|
-
- ❌ Task agent to detect project stack (
|
|
129
|
+
- ❌ Task agent to detect project stack inline (keyword matching is fast enough directly)
|
|
95
130
|
- ❌ WebSearch for project info (it's local, use Read/Glob)
|
|
96
131
|
|
|
97
132
|
---
|
|
@@ -132,6 +167,9 @@ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
|
|
|
132
167
|
| Action | Tool | Why |
|
|
133
168
|
|--------|------|-----|
|
|
134
169
|
| Read proposal + UI specs | **Read** output files | Single known files |
|
|
170
|
+
| **Get dispatch config** | **Bash** `npx morph-spec dispatch-agents {feature} design` | Which agents to spawn + their task prompts |
|
|
171
|
+
| **Dispatch domain-architect** (parallel) | **Task** subagent with prompt from dispatch config | Analyze DDD complexity independently |
|
|
172
|
+
| **Dispatch tech leads** (parallel) | **Task** subagents (dotnet-senior, nextjs-expert, etc.) | Validate architecture independently |
|
|
135
173
|
| Get database schema | **Supabase MCP** `list_tables()`, `get_table_schema()` | **PREFERRED** — real schema data |
|
|
136
174
|
| Get table relationships | **Supabase MCP** `get_relationships()` | Real FK/constraint data |
|
|
137
175
|
| Get RLS policies | **Supabase MCP** `query()` with pg_policies | Security analysis |
|
|
@@ -149,11 +187,14 @@ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
|
|
|
149
187
|
|
|
150
188
|
**MCPs used:** Supabase (schema analysis), Context7 (library research), GitHub (code search).
|
|
151
189
|
|
|
190
|
+
**Parallel dispatch opportunity:** Domain complexity analysis (domain-architect) and schema analysis (phase-codebase-analysis) are **independent** — run them as parallel Task subagents to cut design phase time.
|
|
191
|
+
|
|
152
192
|
**Anti-patterns:**
|
|
153
193
|
- ❌ Guessing field names without checking schema (use MCP or Grep first!)
|
|
154
|
-
- ❌ Task agent to read a single spec file (
|
|
194
|
+
- ❌ Task agent to read a single spec file (use Read directly)
|
|
155
195
|
- ❌ WebSearch for database schema (use Supabase MCP or code analysis)
|
|
156
196
|
- ❌ Manually writing contracts-level{N}.cs from scratch (use template render)
|
|
197
|
+
- ❌ Running domain analysis and schema analysis sequentially (they're independent — run in parallel)
|
|
157
198
|
|
|
158
199
|
---
|
|
159
200
|
|
|
@@ -189,8 +230,10 @@ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
|
|
|
189
230
|
| Action | Tool | Why |
|
|
190
231
|
|--------|------|-----|
|
|
191
232
|
| Read spec + contracts + decisions | **Read** all output files | Full context needed |
|
|
233
|
+
| **Get dispatch config** | **Bash** `npx morph-spec dispatch-agents {feature} tasks` | Which domain experts to consult |
|
|
192
234
|
| Analyze implementation complexity | **Grep** patterns in existing code | Estimate effort from codebase size |
|
|
193
235
|
| Count existing similar patterns | **Glob** `**/Services/**/*.cs` | Understand scope |
|
|
236
|
+
| **Dispatch domain experts** (when spec is complex) | **Task** subagents per domain | Parallel per-domain task planning |
|
|
194
237
|
| Look up implementation patterns | **Context7 MCP** `query_docs()` | Estimate task complexity accurately |
|
|
195
238
|
| Create GitHub issues from tasks | **GitHub MCP** `create_issue()` | Sync tasks to project management |
|
|
196
239
|
| **Fallback:** Create issues via CLI | **Bash** `gh issue create ...` | When no GitHub MCP |
|
|
@@ -200,8 +243,9 @@ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
|
|
|
200
243
|
**MCPs used:** Context7 (complexity estimation), GitHub (issue creation).
|
|
201
244
|
|
|
202
245
|
**Anti-patterns:**
|
|
203
|
-
- ❌ Task agent to break down a simple spec (do it directly
|
|
204
|
-
- ✅ Task agent for complex specs
|
|
246
|
+
- ❌ Task agent to break down a simple 1-domain spec (do it directly)
|
|
247
|
+
- ✅ Task agent for complex multi-domain specs (backend + frontend + infra = 3 independent planners)
|
|
248
|
+
- ✅ Task agent when spec has 20+ requirements across multiple bounded contexts
|
|
205
249
|
- ❌ Manually writing tasks.json without reading all outputs first
|
|
206
250
|
|
|
207
251
|
---
|
|
@@ -213,6 +257,8 @@ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
|
|
|
213
257
|
| Action | Tool | Why |
|
|
214
258
|
|--------|------|-----|
|
|
215
259
|
| Read task details | **Read** tasks.json, spec.md, contracts.cs | Implementation reference |
|
|
260
|
+
| **Check parallelization viability** | **Bash** `npx morph-spec dispatch-agents {feature} implement` | Whether to dispatch parallel implementers |
|
|
261
|
+
| **Dispatch per-domain implementers** (when viable) | **Task** subagents — one per domain group | Backend tasks + frontend tasks run in parallel |
|
|
216
262
|
| Create new files | **Write** new source files | New entities, services, pages |
|
|
217
263
|
| Modify existing files | **Edit** existing source files | Add features to existing code |
|
|
218
264
|
| Look up API during coding | **Context7 MCP** `query_docs()` | Accurate library usage |
|
|
@@ -228,17 +274,22 @@ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
|
|
|
228
274
|
| Smoke test feature no browser | **Playwright MCP** `browser_navigate()` + `browser_snapshot()` | Manual testing |
|
|
229
275
|
| Verificar erros de console | **Playwright MCP** `browser_console_messages()` | Browser DevTools |
|
|
230
276
|
| Screenshot para recap.md | **Playwright MCP** `browser_take_screenshot()` | Manual screenshot |
|
|
231
|
-
| Multi-file refactoring | **Task** (subagent) | Complex parallel changes |
|
|
232
277
|
| Update state | **Bash** `npx morph-spec state set ...` | CLI command |
|
|
233
278
|
|
|
234
279
|
**MCPs used:** Supabase (migrations, RLS), Context7 (API lookup), GitHub (PRs), Playwright (smoke test, verification).
|
|
235
280
|
|
|
281
|
+
**Parallel dispatch threshold:**
|
|
282
|
+
- `tasks.total ≥ 6` AND feature spans 2+ domains (e.g. `dotnet-senior` + `nextjs-expert`) → dispatch parallel implementers
|
|
283
|
+
- Each subagent gets its own task group (backend T001-T005, frontend T006-T009) with isolated file scope
|
|
284
|
+
|
|
236
285
|
**Anti-patterns:**
|
|
237
286
|
- ❌ Task agent for a single file edit (use Edit directly)
|
|
238
287
|
- ✅ Task agent for implementing a full service layer across 5+ files
|
|
288
|
+
- ✅ Task agent for each domain group when feature has 6+ tasks across multiple domains
|
|
239
289
|
- ❌ Bash `cat` to create files (use Write tool)
|
|
240
290
|
- ❌ Bash `sed` to modify code (use Edit tool)
|
|
241
291
|
- ❌ Implementing without reading contracts.cs first (contracts are the source of truth)
|
|
292
|
+
- ❌ Implementing backend and frontend tasks sequentially when they have no cross-dependencies
|
|
242
293
|
|
|
243
294
|
---
|
|
244
295
|
|
|
@@ -4,7 +4,7 @@ description: MORPH-SPEC Phase 3 (Clarify). Reviews spec.md for ambiguities, gene
|
|
|
4
4
|
argument-hint: "[feature-name]"
|
|
5
5
|
user-invocable: false
|
|
6
6
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep
|
|
7
|
-
cliVersion: "4.8.
|
|
7
|
+
cliVersion: "4.8.9"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MORPH Clarify - FASE 3
|