@polymorphism-tech/morph-spec 4.8.1 → 4.8.4
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/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/hooks/dev/guard-version-numbers.js +1 -1
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-implement/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-setup/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +1 -1
- package/package.json +4 -4
- package/.morph/analytics/threads-log.jsonl +0 -54
- package/.morph/state.json +0 -198
- package/docs/ARCHITECTURE.md +0 -328
- package/docs/COMMAND-FLOWS.md +0 -398
- package/docs/plans/2026-02-22-claude-docs-morph-alignment-analysis.md +0 -514
- package/docs/plans/2026-02-22-claude-settings.md +0 -517
- package/docs/plans/2026-02-22-morph-cc-alignment-impl.md +0 -730
- package/docs/plans/2026-02-22-morph-spec-next.md +0 -480
- package/docs/plans/2026-02-22-native-alignment-design.md +0 -201
- package/docs/plans/2026-02-22-native-alignment-impl.md +0 -927
- package/docs/plans/2026-02-22-native-enrichment-design.md +0 -246
- package/docs/plans/2026-02-22-native-enrichment.md +0 -737
- package/docs/plans/2026-02-23-ddd-architecture-refactor.md +0 -1155
- package/docs/plans/2026-02-23-ddd-nextsteps.md +0 -684
- package/docs/plans/2026-02-23-infra-architect-refactor.md +0 -439
- package/docs/plans/2026-02-23-nextjs-code-review-design.md +0 -157
- package/docs/plans/2026-02-23-nextjs-code-review-impl.md +0 -1256
- package/docs/plans/2026-02-23-nextjs-standards-design.md +0 -150
- package/docs/plans/2026-02-23-nextjs-standards-impl.md +0 -1848
- package/docs/plans/2026-02-24-cli-radical-simplification.md +0 -592
- package/docs/plans/2026-02-24-framework-failure-points.md +0 -125
- package/docs/plans/2026-02-24-morph-init-design.md +0 -337
- package/docs/plans/2026-02-24-morph-init-impl.md +0 -1269
- package/docs/plans/2026-02-24-tutorial-command-design.md +0 -71
- package/docs/plans/2026-02-24-tutorial-command.md +0 -298
- package/scripts/bump-version.js +0 -248
- package/scripts/generate-refs.js +0 -336
- package/scripts/generate-standards-registry.js +0 -44
- package/scripts/install-dev-hooks.js +0 -138
- package/scripts/scan-nextjs.mjs +0 -169
- package/scripts/validate-real.mjs +0 -255
|
@@ -1,730 +0,0 @@
|
|
|
1
|
-
# Claude Code Native Alignment — Implementation Plan
|
|
2
|
-
|
|
3
|
-
**Status:** COMPLETE
|
|
4
|
-
|
|
5
|
-
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
6
|
-
|
|
7
|
-
**Goal:** Implement all actionable improvements from the CC native alignment analysis: fix a broken SubagentStart hook, convert bash validation to a prompt-type hook, inject active feature spec into session context, add `$ARGUMENTS` support to phase skills, add `disable-model-invocation` + `context: fork` where appropriate, and convert level-2-domain specialists from skills to native Claude Code subagents.
|
|
8
|
-
|
|
9
|
-
**Architecture:** The changes touch hooks-installer.js (MORPH_HOOKS array + HOOKS_VERSION), the SessionStart hook script, all six phase workflow skill files, skills-installer.js, and agents-installer.js (new `installDomainAgents` function). Tests must pass after each task. No shared-module rewrites (too risky); no trust→defaultMode mapping (deeply integrated, separate concern).
|
|
10
|
-
|
|
11
|
-
**Tech Stack:** Node.js ESM, node:test, YAML frontmatter in .md files, Commander.js CLI
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## Phase 1: Hooks cleanup and improvements
|
|
16
|
-
|
|
17
|
-
### Task 1: Remove broken SubagentStart hook entry
|
|
18
|
-
|
|
19
|
-
`MORPH_HOOKS` references `subagent/log-agent-start.js` which does not exist. This causes a `hooks` entry pointing at a missing file — fail-open semantics mean it silently passes, but the entry is dead weight.
|
|
20
|
-
|
|
21
|
-
**Files:**
|
|
22
|
-
- Modify: `src/utils/hooks-installer.js`
|
|
23
|
-
|
|
24
|
-
**Step 1: Remove the SubagentStart entry from MORPH_HOOKS**
|
|
25
|
-
|
|
26
|
-
In `src/utils/hooks-installer.js`, delete the entire SubagentStart block (lines 159–168):
|
|
27
|
-
|
|
28
|
-
```js
|
|
29
|
-
// === SubagentStart ===
|
|
30
|
-
{
|
|
31
|
-
event: 'SubagentStart',
|
|
32
|
-
matcher: 'morph-.*',
|
|
33
|
-
hooks: [{
|
|
34
|
-
type: 'command',
|
|
35
|
-
command: 'node framework/hooks/claude-code/subagent/log-agent-start.js'
|
|
36
|
-
}]
|
|
37
|
-
},
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
**Step 2: Bump HOOKS_VERSION to 2.3.0**
|
|
41
|
-
|
|
42
|
-
```js
|
|
43
|
-
const HOOKS_VERSION = '2.3.0';
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
**Step 3: Verify no SubagentStart key appears in hooks tests**
|
|
47
|
-
|
|
48
|
-
Run: `npm test -- test/hooks/hooks-installer.test.js`
|
|
49
|
-
Expected: All pass (the test that checks installed events doesn't assert SubagentStart)
|
|
50
|
-
|
|
51
|
-
**Step 4: Commit**
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
git add src/utils/hooks-installer.js
|
|
55
|
-
git commit -m "fix(hooks): remove SubagentStart hook pointing at missing log-agent-start.js"
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
### Task 2: Convert validate-bash-commands.js to a prompt-type hook
|
|
61
|
-
|
|
62
|
-
The Node.js `validate-bash-commands.js` performs 3 regex checks. These can be replaced by a CC-native `type: "prompt"` hook that uses Haiku to evaluate the command — simpler, no file required.
|
|
63
|
-
|
|
64
|
-
**Files:**
|
|
65
|
-
- Modify: `src/utils/hooks-installer.js`
|
|
66
|
-
- No file to delete yet (keep validate-bash-commands.js for reference, remove in Task 2b)
|
|
67
|
-
|
|
68
|
-
**Step 1: Write failing test that expects the Bash PreToolUse hook to be type "prompt"**
|
|
69
|
-
|
|
70
|
-
In `test/hooks/hooks-installer.test.js`, add inside `describe('installClaudeHooks', ...)`:
|
|
71
|
-
|
|
72
|
-
```js
|
|
73
|
-
test('Bash PreToolUse hook is type prompt (not command)', async () => {
|
|
74
|
-
await installClaudeHooks(tempDir);
|
|
75
|
-
const settings = readSettings();
|
|
76
|
-
const bashEntry = settings.hooks.PreToolUse.find(e => e.matcher === 'Bash');
|
|
77
|
-
assert.ok(bashEntry, 'Bash PreToolUse entry exists');
|
|
78
|
-
assert.strictEqual(bashEntry.hooks[0].type, 'prompt');
|
|
79
|
-
assert.ok(bashEntry.hooks[0].prompt, 'prompt field is set');
|
|
80
|
-
});
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
**Step 2: Run test to confirm it fails**
|
|
84
|
-
|
|
85
|
-
Run: `npm test -- test/hooks/hooks-installer.test.js 2>&1 | grep -A3 "Bash PreToolUse hook is type prompt"`
|
|
86
|
-
Expected: FAIL — hook type is currently "command"
|
|
87
|
-
|
|
88
|
-
**Step 3: Replace the Bash PreToolUse hook in MORPH_HOOKS**
|
|
89
|
-
|
|
90
|
-
In `src/utils/hooks-installer.js`, replace the `// === PreToolUse: Bash ===` block:
|
|
91
|
-
|
|
92
|
-
```js
|
|
93
|
-
// === PreToolUse: Bash ===
|
|
94
|
-
{
|
|
95
|
-
event: 'PreToolUse',
|
|
96
|
-
matcher: 'Bash',
|
|
97
|
-
hooks: [{
|
|
98
|
-
type: 'prompt',
|
|
99
|
-
prompt: `You are a guard for a morph-spec project. Read the bash command from the hook input and check:
|
|
100
|
-
1. Does the command delete .morph/ (e.g. "rm -rf .morph" or "rm -rf .morph/")? → BLOCK
|
|
101
|
-
2. Does the command edit state.json directly via sed, jq, awk, echo, cat, or printf with a shell redirect (>)? → BLOCK
|
|
102
|
-
3. Does the command write to .morph/framework/ via shell redirect (>)? → BLOCK
|
|
103
|
-
If any condition is true, respond: {"ok": false, "reason": "MORPH-SPEC: <brief explanation and suggested alternative>"}
|
|
104
|
-
Otherwise respond: {"ok": true}`
|
|
105
|
-
}]
|
|
106
|
-
},
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
**Step 4: Run the new test — expect PASS**
|
|
110
|
-
|
|
111
|
-
Run: `npm test -- test/hooks/hooks-installer.test.js 2>&1 | grep -A3 "Bash PreToolUse hook is type prompt"`
|
|
112
|
-
Expected: PASS
|
|
113
|
-
|
|
114
|
-
**Step 5: Run full hooks installer tests**
|
|
115
|
-
|
|
116
|
-
Run: `npm test -- test/hooks/hooks-installer.test.js`
|
|
117
|
-
Expected: All pass (existing "installs all hook events" test doesn't assert Bash hook type)
|
|
118
|
-
|
|
119
|
-
**Step 6: Commit**
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
git add src/utils/hooks-installer.js test/hooks/hooks-installer.test.js
|
|
123
|
-
git commit -m "feat(hooks): convert Bash guard to native prompt-type hook, remove Node.js dependency"
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
---
|
|
127
|
-
|
|
128
|
-
### Task 3: Inject active feature spec into SessionStart context
|
|
129
|
-
|
|
130
|
-
The framework claims to be spec-driven but the active feature's spec.md is never auto-loaded into Claude's context. Fix `inject-morph-context.js` to include the spec content (truncated to 3000 chars) when an active feature exists and has a spec.
|
|
131
|
-
|
|
132
|
-
**Files:**
|
|
133
|
-
- Modify: `framework/hooks/claude-code/session-start/inject-morph-context.js`
|
|
134
|
-
|
|
135
|
-
**Step 1: Write failing test for spec injection behavior**
|
|
136
|
-
|
|
137
|
-
In `test/hooks/shared-utils.test.js` (or create `test/hooks/inject-context.test.js`):
|
|
138
|
-
|
|
139
|
-
```js
|
|
140
|
-
import { test, describe } from 'node:test';
|
|
141
|
-
import assert from 'node:assert';
|
|
142
|
-
import { readFileSync, existsSync } from 'fs';
|
|
143
|
-
|
|
144
|
-
describe('inject-morph-context spec injection', () => {
|
|
145
|
-
test('inject-morph-context.js reads spec file content', () => {
|
|
146
|
-
const src = readFileSync(
|
|
147
|
-
'framework/hooks/claude-code/session-start/inject-morph-context.js',
|
|
148
|
-
'utf-8'
|
|
149
|
-
);
|
|
150
|
-
// Must read the spec file for the active feature
|
|
151
|
-
assert.ok(src.includes('1-design/spec.md'), 'Should reference spec.md path');
|
|
152
|
-
assert.ok(src.includes('Active feature spec'), 'Should label the spec section');
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
**Step 2: Run to confirm it fails**
|
|
158
|
-
|
|
159
|
-
Run: `npm test -- test/hooks/inject-context.test.js`
|
|
160
|
-
Expected: FAIL — "Should reference spec.md path"
|
|
161
|
-
|
|
162
|
-
**Step 3: Modify inject-morph-context.js to add spec injection**
|
|
163
|
-
|
|
164
|
-
After the `if (active) { ... }` block (after the "Key commands" line), add the spec reading logic. The full modified file:
|
|
165
|
-
|
|
166
|
-
```js
|
|
167
|
-
#!/usr/bin/env node
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* SessionStart Hook: Inject MORPH-SPEC Context
|
|
171
|
-
*
|
|
172
|
-
* Event: SessionStart | Matcher: startup|resume|compact
|
|
173
|
-
*
|
|
174
|
-
* Reads state.json and injects a summary + active feature spec as
|
|
175
|
-
* additionalContext so Claude knows the current morph-spec state and
|
|
176
|
-
* spec at session start.
|
|
177
|
-
*
|
|
178
|
-
* Fail-open: exits 0 on any error.
|
|
179
|
-
*/
|
|
180
|
-
|
|
181
|
-
import { readFileSync, existsSync } from 'fs';
|
|
182
|
-
import { join } from 'path';
|
|
183
|
-
import { loadState, getActiveFeature, getPendingGates, getMissingOutputs } from '../../shared/state-reader.js';
|
|
184
|
-
import { stateExists } from '../../shared/state-reader.js';
|
|
185
|
-
import { injectContext, pass } from '../../shared/hook-response.js';
|
|
186
|
-
|
|
187
|
-
const SPEC_MAX_CHARS = 3000;
|
|
188
|
-
|
|
189
|
-
try {
|
|
190
|
-
if (!stateExists()) pass();
|
|
191
|
-
|
|
192
|
-
const state = loadState();
|
|
193
|
-
if (!state?.features || Object.keys(state.features).length === 0) pass();
|
|
194
|
-
|
|
195
|
-
const active = getActiveFeature();
|
|
196
|
-
const lines = ['MORPH-SPEC Status:'];
|
|
197
|
-
|
|
198
|
-
if (active) {
|
|
199
|
-
const { name, feature } = active;
|
|
200
|
-
lines.push(`- Active feature: ${name} (phase: ${feature.phase}, workflow: ${feature.workflow || 'auto'})`);
|
|
201
|
-
lines.push(`- Status: ${feature.status}`);
|
|
202
|
-
|
|
203
|
-
// Task progress
|
|
204
|
-
if (feature.tasks) {
|
|
205
|
-
lines.push(`- Tasks: ${feature.tasks.completed || 0}/${feature.tasks.total || 0} completed`);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Pending approvals
|
|
209
|
-
const pending = getPendingGates(name);
|
|
210
|
-
if (pending.length > 0) {
|
|
211
|
-
lines.push(`- Pending approvals: ${pending.join(', ')}`);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Next required output
|
|
215
|
-
const missing = getMissingOutputs(name);
|
|
216
|
-
if (missing.length > 0) {
|
|
217
|
-
const next = missing[0];
|
|
218
|
-
lines.push(`- Next required output: ${next.type} → ${next.path}`);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Checkpoints
|
|
222
|
-
if (feature.checkpoints?.length > 0) {
|
|
223
|
-
const lastCp = feature.checkpoints[feature.checkpoints.length - 1];
|
|
224
|
-
lines.push(`- Last checkpoint: #${lastCp.checkpointNum} (${lastCp.passed ? 'passed' : 'failed'})`);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Active feature spec (truncated)
|
|
228
|
-
const specPath = join(process.cwd(), `.morph/features/${name}/1-design/spec.md`);
|
|
229
|
-
if (existsSync(specPath)) {
|
|
230
|
-
try {
|
|
231
|
-
const specContent = readFileSync(specPath, 'utf-8');
|
|
232
|
-
const truncated = specContent.length > SPEC_MAX_CHARS
|
|
233
|
-
? specContent.slice(0, SPEC_MAX_CHARS) + '\n\n[... spec truncated — read full file at ' + specPath + ']'
|
|
234
|
-
: specContent;
|
|
235
|
-
lines.push('');
|
|
236
|
-
lines.push(`--- Active feature spec (${name}/1-design/spec.md) ---`);
|
|
237
|
-
lines.push(truncated);
|
|
238
|
-
lines.push('--- End spec ---');
|
|
239
|
-
} catch {
|
|
240
|
-
// Non-blocking: skip spec injection on read error
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
} else {
|
|
244
|
-
// Show summary of all features
|
|
245
|
-
const featureNames = Object.keys(state.features);
|
|
246
|
-
lines.push(`- Features: ${featureNames.length} (${featureNames.join(', ')})`);
|
|
247
|
-
|
|
248
|
-
for (const [name, feature] of Object.entries(state.features)) {
|
|
249
|
-
lines.push(` - ${name}: phase=${feature.phase}, status=${feature.status}`);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Remind about key commands
|
|
254
|
-
lines.push('');
|
|
255
|
-
lines.push('Key commands: morph-spec status <feature> | morph-spec phase advance <feature> | morph-spec approve <feature> <gate>');
|
|
256
|
-
|
|
257
|
-
injectContext(lines.join('\n'));
|
|
258
|
-
} catch {
|
|
259
|
-
// Fail-open
|
|
260
|
-
process.exit(0);
|
|
261
|
-
}
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
**Step 4: Run test**
|
|
265
|
-
|
|
266
|
-
Run: `npm test -- test/hooks/inject-context.test.js`
|
|
267
|
-
Expected: PASS
|
|
268
|
-
|
|
269
|
-
**Step 5: Commit**
|
|
270
|
-
|
|
271
|
-
```bash
|
|
272
|
-
git add framework/hooks/claude-code/session-start/inject-morph-context.js test/hooks/inject-context.test.js
|
|
273
|
-
git commit -m "feat(hooks): inject active feature spec.md into SessionStart context"
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
---
|
|
277
|
-
|
|
278
|
-
## Phase 2: Skills improvements
|
|
279
|
-
|
|
280
|
-
### Task 4: Add $ARGUMENTS to all phase workflow skills
|
|
281
|
-
|
|
282
|
-
Phase skills currently have no way to accept a feature name argument. `/phase-design` and `/phase-design my-feature` both work but the latter doesn't bind to the feature. Add `$ARGUMENTS` to enable bound invocations like `/phase-design my-feature`.
|
|
283
|
-
|
|
284
|
-
**Files to modify:**
|
|
285
|
-
- `framework/skills/level-1-workflows/phase-setup.md`
|
|
286
|
-
- `framework/skills/level-1-workflows/phase-design.md`
|
|
287
|
-
- `framework/skills/level-1-workflows/phase-clarify.md`
|
|
288
|
-
- `framework/skills/level-1-workflows/phase-uiux.md`
|
|
289
|
-
- `framework/skills/level-1-workflows/phase-tasks.md`
|
|
290
|
-
- `framework/skills/level-1-workflows/phase-implement.md`
|
|
291
|
-
|
|
292
|
-
**Step 1: Read each file to see current argument references**
|
|
293
|
-
|
|
294
|
-
For each file, check if `{feature-name}`, `{feature}`, or `$ARGUMENTS` is already used.
|
|
295
|
-
|
|
296
|
-
Run: `grep -n "feature-name\|ARGUMENTS" framework/skills/level-1-workflows/*.md`
|
|
297
|
-
|
|
298
|
-
**Step 2: Add argument-hint frontmatter and $ARGUMENTS to phase-design.md**
|
|
299
|
-
|
|
300
|
-
In `framework/skills/level-1-workflows/phase-design.md`, add to frontmatter:
|
|
301
|
-
```yaml
|
|
302
|
-
argument-hint: "[feature-name]"
|
|
303
|
-
```
|
|
304
|
-
Then replace all instances of `{feature-name}` with `$ARGUMENTS` in the skill body (wherever the user is expected to fill in a feature name). If no placeholder exists, add at top of body:
|
|
305
|
-
```markdown
|
|
306
|
-
Feature: **$ARGUMENTS**
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
Repeat this same pattern for all 6 phase skill files.
|
|
310
|
-
|
|
311
|
-
**Step 3: Write test that verifies $ARGUMENTS appears in installed skills**
|
|
312
|
-
|
|
313
|
-
Add to `test/utils/skills-installer.test.js` (or create it):
|
|
314
|
-
```js
|
|
315
|
-
test('installed phase-design skill contains $ARGUMENTS', async () => {
|
|
316
|
-
await installSkills(tempDir);
|
|
317
|
-
const skillPath = join(tempDir, '.claude', 'skills', 'phase-design.md');
|
|
318
|
-
assert.ok(existsSync(skillPath), 'phase-design.md installed');
|
|
319
|
-
const content = readFileSync(skillPath, 'utf-8');
|
|
320
|
-
assert.ok(content.includes('$ARGUMENTS') || content.includes('argument-hint'),
|
|
321
|
-
'skill supports arguments');
|
|
322
|
-
});
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
**Step 4: Run test, fix any issues, run all tests**
|
|
326
|
-
|
|
327
|
-
Run: `npm test -- test/utils/skills-installer.test.js`
|
|
328
|
-
Run: `npm test`
|
|
329
|
-
Expected: All pass
|
|
330
|
-
|
|
331
|
-
**Step 5: Commit**
|
|
332
|
-
|
|
333
|
-
```bash
|
|
334
|
-
git add framework/skills/level-1-workflows/
|
|
335
|
-
git commit -m "feat(skills): add \$ARGUMENTS support to all phase workflow skills"
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
---
|
|
339
|
-
|
|
340
|
-
### Task 5: Add disable-model-invocation and context:fork to appropriate skills
|
|
341
|
-
|
|
342
|
-
**phase-implement** and **phase-tasks** should not be auto-triggered by Claude (they have side effects). Add `disable-model-invocation: true`. **phase-implement** should also run in an isolated context via `context: fork`.
|
|
343
|
-
|
|
344
|
-
**Files:**
|
|
345
|
-
- Modify: `framework/skills/level-1-workflows/phase-implement.md`
|
|
346
|
-
- Modify: `framework/skills/level-1-workflows/phase-tasks.md`
|
|
347
|
-
|
|
348
|
-
**Step 1: Add to phase-implement.md frontmatter**
|
|
349
|
-
|
|
350
|
-
```yaml
|
|
351
|
-
disable-model-invocation: true
|
|
352
|
-
context: fork
|
|
353
|
-
agent: general-purpose
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
Note: `phase-implement` already has `user-invocable: false`. Keep that. Add the two new fields.
|
|
357
|
-
|
|
358
|
-
**Step 2: Add to phase-tasks.md frontmatter**
|
|
359
|
-
|
|
360
|
-
```yaml
|
|
361
|
-
disable-model-invocation: true
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
**Step 3: Verify the frontmatter parses correctly**
|
|
365
|
-
|
|
366
|
-
Read both files after editing and verify YAML is valid (no duplicate keys, correct indentation).
|
|
367
|
-
|
|
368
|
-
**Step 4: Commit**
|
|
369
|
-
|
|
370
|
-
```bash
|
|
371
|
-
git add framework/skills/level-1-workflows/phase-implement.md \
|
|
372
|
-
framework/skills/level-1-workflows/phase-tasks.md
|
|
373
|
-
git commit -m "feat(skills): disable auto-invocation and add context:fork to implementation phases"
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
---
|
|
377
|
-
|
|
378
|
-
## Phase 3: Domain specialists as native subagents
|
|
379
|
-
|
|
380
|
-
### Task 6: Add installDomainAgents() to agents-installer.js
|
|
381
|
-
|
|
382
|
-
Domain specialist skills in `framework/skills/level-2-domains/` should be installed as Claude Code subagents (`.claude/agents/`) with `memory: project`, not as flat skill files. Create a function that converts each skill .md file into a proper agent .md file.
|
|
383
|
-
|
|
384
|
-
**Files:**
|
|
385
|
-
- Modify: `src/utils/agents-installer.js`
|
|
386
|
-
|
|
387
|
-
**Step 1: Write failing test**
|
|
388
|
-
|
|
389
|
-
Add to `test/utils/agents-installer.test.js`:
|
|
390
|
-
```js
|
|
391
|
-
import { installDomainAgents } from '../../src/utils/agents-installer.js';
|
|
392
|
-
|
|
393
|
-
describe('installDomainAgents', () => {
|
|
394
|
-
test('creates agent files from level-2-domains skills', async () => {
|
|
395
|
-
await installDomainAgents(tempDir, 'framework');
|
|
396
|
-
const agentsDir = join(tempDir, '.claude', 'agents');
|
|
397
|
-
assert.ok(existsSync(agentsDir));
|
|
398
|
-
|
|
399
|
-
// blazor-builder skill → agent file
|
|
400
|
-
const blazorAgent = join(agentsDir, 'morph-domain-blazor-builder.md');
|
|
401
|
-
assert.ok(existsSync(blazorAgent), 'blazor-builder domain agent created');
|
|
402
|
-
|
|
403
|
-
const content = readFileSync(blazorAgent, 'utf-8');
|
|
404
|
-
assert.ok(content.includes('memory: project'), 'agent has project memory');
|
|
405
|
-
assert.ok(content.includes('model: sonnet'), 'agent uses sonnet model');
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
test('domain agent files have required frontmatter fields', async () => {
|
|
409
|
-
await installDomainAgents(tempDir, 'framework');
|
|
410
|
-
const blazorAgent = join(tempDir, '.claude', 'agents', 'morph-domain-blazor-builder.md');
|
|
411
|
-
const content = readFileSync(blazorAgent, 'utf-8');
|
|
412
|
-
assert.ok(content.startsWith('---'), 'has frontmatter');
|
|
413
|
-
assert.ok(content.includes('name:'), 'has name');
|
|
414
|
-
assert.ok(content.includes('description:'), 'has description');
|
|
415
|
-
assert.ok(content.includes('tools:'), 'has tools');
|
|
416
|
-
assert.ok(content.includes('memory: project'), 'has memory');
|
|
417
|
-
});
|
|
418
|
-
});
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
**Step 2: Run test to confirm it fails**
|
|
422
|
-
|
|
423
|
-
Run: `npm test -- test/utils/agents-installer.test.js 2>&1 | grep "installDomainAgents"`
|
|
424
|
-
Expected: FAIL — function not exported
|
|
425
|
-
|
|
426
|
-
**Step 3: Implement installDomainAgents() in agents-installer.js**
|
|
427
|
-
|
|
428
|
-
Add after the existing `installAgents` function:
|
|
429
|
-
|
|
430
|
-
```js
|
|
431
|
-
import { readdirSync, readFileSync as readFileSyncFS, statSync } from 'node:fs';
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* Install level-2-domains specialist skills as native Claude Code subagents.
|
|
435
|
-
* Each skill .md file becomes a .claude/agents/morph-domain-{name}.md file
|
|
436
|
-
* with memory: project and specialist tool access.
|
|
437
|
-
*
|
|
438
|
-
* @param {string} projectDir - Target project directory
|
|
439
|
-
* @param {string} frameworkDir - Path to morph-spec framework/ directory
|
|
440
|
-
*/
|
|
441
|
-
export async function installDomainAgents(projectDir, frameworkDir = 'framework') {
|
|
442
|
-
const domainSkillsDir = join(frameworkDir, 'skills', 'level-2-domains');
|
|
443
|
-
if (!existsSync(domainSkillsDir)) return;
|
|
444
|
-
|
|
445
|
-
const targetDir = join(projectDir, '.claude', 'agents');
|
|
446
|
-
mkdirSync(targetDir, { recursive: true });
|
|
447
|
-
|
|
448
|
-
const skillFiles = collectSkillFiles(domainSkillsDir);
|
|
449
|
-
|
|
450
|
-
for (const filePath of skillFiles) {
|
|
451
|
-
const raw = readFileSyncFS(filePath, 'utf-8');
|
|
452
|
-
const { frontmatter, body } = parseSkillFile(raw);
|
|
453
|
-
|
|
454
|
-
const name = frontmatter.name;
|
|
455
|
-
if (!name) continue;
|
|
456
|
-
|
|
457
|
-
const description = frontmatter.description?.trim().replace(/^>\s*/, '') || `${name} domain specialist`;
|
|
458
|
-
const tools = frontmatter['allowed-tools'] || 'Read, Edit, Write, Bash, Grep, Glob';
|
|
459
|
-
|
|
460
|
-
const agentFrontmatter = [
|
|
461
|
-
`name: ${name}`,
|
|
462
|
-
`description: ${description}`,
|
|
463
|
-
`model: sonnet`,
|
|
464
|
-
`tools: ${tools}`,
|
|
465
|
-
`maxTurns: 20`,
|
|
466
|
-
`memory: project`,
|
|
467
|
-
].join('\n');
|
|
468
|
-
|
|
469
|
-
const content = `---\n${agentFrontmatter}\n---\n\n${body}\n`;
|
|
470
|
-
const filename = `morph-domain-${name}.md`;
|
|
471
|
-
writeFileSync(join(targetDir, filename), content, 'utf-8');
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
* Recursively collect all .md files (excluding README.md) from a directory.
|
|
477
|
-
* @param {string} dir
|
|
478
|
-
* @returns {string[]}
|
|
479
|
-
*/
|
|
480
|
-
function collectSkillFiles(dir) {
|
|
481
|
-
const result = [];
|
|
482
|
-
for (const entry of readdirSync(dir)) {
|
|
483
|
-
const fullPath = join(dir, entry);
|
|
484
|
-
if (statSync(fullPath).isDirectory()) {
|
|
485
|
-
result.push(...collectSkillFiles(fullPath));
|
|
486
|
-
} else if (entry.endsWith('.md') && entry !== 'README.md') {
|
|
487
|
-
result.push(fullPath);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
return result;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
* Parse a skill .md file with YAML frontmatter.
|
|
495
|
-
* Returns { frontmatter: Object, body: string }.
|
|
496
|
-
* @param {string} content
|
|
497
|
-
* @returns {{ frontmatter: Record<string, string>, body: string }}
|
|
498
|
-
*/
|
|
499
|
-
function parseSkillFile(content) {
|
|
500
|
-
const frontmatter = {};
|
|
501
|
-
let body = content;
|
|
502
|
-
|
|
503
|
-
if (content.startsWith('---')) {
|
|
504
|
-
const end = content.indexOf('\n---', 3);
|
|
505
|
-
if (end !== -1) {
|
|
506
|
-
const fmText = content.slice(3, end).trim();
|
|
507
|
-
body = content.slice(end + 4).trim();
|
|
508
|
-
|
|
509
|
-
for (const line of fmText.split('\n')) {
|
|
510
|
-
const colonIdx = line.indexOf(':');
|
|
511
|
-
if (colonIdx === -1) continue;
|
|
512
|
-
const key = line.slice(0, colonIdx).trim();
|
|
513
|
-
const val = line.slice(colonIdx + 1).trim();
|
|
514
|
-
if (key && !key.startsWith('-')) {
|
|
515
|
-
frontmatter[key] = val.replace(/^['"]|['"]$/g, '');
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
return { frontmatter, body };
|
|
522
|
-
}
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
**Step 4: Add `existsSync` to existing imports at top of agents-installer.js**
|
|
526
|
-
|
|
527
|
-
The file currently imports from `node:fs`:
|
|
528
|
-
```js
|
|
529
|
-
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
530
|
-
```
|
|
531
|
-
Change to:
|
|
532
|
-
```js
|
|
533
|
-
import { readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, existsSync } from 'node:fs';
|
|
534
|
-
```
|
|
535
|
-
|
|
536
|
-
**Step 5: Run the new tests**
|
|
537
|
-
|
|
538
|
-
Run: `npm test -- test/utils/agents-installer.test.js`
|
|
539
|
-
Expected: Both new tests PASS
|
|
540
|
-
|
|
541
|
-
**Step 6: Run full test suite**
|
|
542
|
-
|
|
543
|
-
Run: `npm test`
|
|
544
|
-
Expected: All pass
|
|
545
|
-
|
|
546
|
-
**Step 7: Commit**
|
|
547
|
-
|
|
548
|
-
```bash
|
|
549
|
-
git add src/utils/agents-installer.js test/utils/agents-installer.test.js
|
|
550
|
-
git commit -m "feat(agents): add installDomainAgents() — converts level-2-domain skills to native subagents with memory:project"
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
---
|
|
554
|
-
|
|
555
|
-
### Task 7: Stop installing level-2-domains as flat skills
|
|
556
|
-
|
|
557
|
-
Now that domain specialists are installed as subagents, remove them from the skills installer to avoid duplication.
|
|
558
|
-
|
|
559
|
-
**Files:**
|
|
560
|
-
- Modify: `src/utils/skills-installer.js`
|
|
561
|
-
|
|
562
|
-
**Step 1: Write test that verifies level-2-domains are NOT installed as flat skills**
|
|
563
|
-
|
|
564
|
-
In `test/utils/skills-installer.test.js`, add:
|
|
565
|
-
```js
|
|
566
|
-
test('level-2-domains skills are NOT installed as flat skill files', async () => {
|
|
567
|
-
await installSkills(tempDir);
|
|
568
|
-
const skillsDir = join(tempDir, '.claude', 'skills');
|
|
569
|
-
// blazor-builder should not appear as a skill (it's now a domain agent)
|
|
570
|
-
const blazorSkill = join(skillsDir, 'blazor-builder.md');
|
|
571
|
-
assert.ok(!existsSync(blazorSkill), 'blazor-builder.md should NOT be in skills (it is a domain agent now)');
|
|
572
|
-
});
|
|
573
|
-
```
|
|
574
|
-
|
|
575
|
-
**Step 2: Run test to confirm it fails (blazor-builder.md currently IS installed)**
|
|
576
|
-
|
|
577
|
-
Run: `npm test -- test/utils/skills-installer.test.js 2>&1 | grep "level-2-domains skills"`
|
|
578
|
-
Expected: FAIL
|
|
579
|
-
|
|
580
|
-
**Step 3: Remove level-2-domains from SKILL_LEVELS_TO_INSTALL**
|
|
581
|
-
|
|
582
|
-
In `src/utils/skills-installer.js`, change:
|
|
583
|
-
```js
|
|
584
|
-
const SKILL_LEVELS_TO_INSTALL = ['level-0-meta', 'level-1-workflows', 'level-2-domains'];
|
|
585
|
-
```
|
|
586
|
-
to:
|
|
587
|
-
```js
|
|
588
|
-
// level-2-domains are installed as native subagents via installDomainAgents() — not as skills
|
|
589
|
-
const SKILL_LEVELS_TO_INSTALL = ['level-0-meta', 'level-1-workflows'];
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
**Step 4: Run tests**
|
|
593
|
-
|
|
594
|
-
Run: `npm test -- test/utils/skills-installer.test.js`
|
|
595
|
-
Expected: All pass including new test
|
|
596
|
-
|
|
597
|
-
**Step 5: Commit**
|
|
598
|
-
|
|
599
|
-
```bash
|
|
600
|
-
git add src/utils/skills-installer.js test/utils/skills-installer.test.js
|
|
601
|
-
git commit -m "feat(skills): remove level-2-domains from skill installer (now installed as domain subagents)"
|
|
602
|
-
```
|
|
603
|
-
|
|
604
|
-
---
|
|
605
|
-
|
|
606
|
-
### Task 8: Wire installDomainAgents() into init.js and update.js
|
|
607
|
-
|
|
608
|
-
`init.js` already calls `installAgents()`. Add `installDomainAgents()` call after it. Same for `update.js`.
|
|
609
|
-
|
|
610
|
-
**Files:**
|
|
611
|
-
- Modify: `src/commands/project/init.js`
|
|
612
|
-
- Modify: `src/commands/project/update.js`
|
|
613
|
-
|
|
614
|
-
**Step 1: Read the relevant section of init.js to find where installAgents is called**
|
|
615
|
-
|
|
616
|
-
Look for the `installAgents(` call in init.js.
|
|
617
|
-
|
|
618
|
-
**Step 2: Add installDomainAgents import and call in init.js**
|
|
619
|
-
|
|
620
|
-
Add to imports at top of init.js (after existing agents-installer import):
|
|
621
|
-
```js
|
|
622
|
-
import { installAgents, installDomainAgents } from '../../utils/agents-installer.js';
|
|
623
|
-
```
|
|
624
|
-
|
|
625
|
-
After the `await installAgents(targetDir, frameworkDir)` call, add:
|
|
626
|
-
```js
|
|
627
|
-
await installDomainAgents(targetDir, frameworkDir);
|
|
628
|
-
```
|
|
629
|
-
|
|
630
|
-
**Step 3: Add installDomainAgents import and call in update.js**
|
|
631
|
-
|
|
632
|
-
Same pattern — add import and call after the existing `installAgents()` call.
|
|
633
|
-
|
|
634
|
-
**Step 4: Run integration tests**
|
|
635
|
-
|
|
636
|
-
Run: `npm test -- test/commands/init.test.js`
|
|
637
|
-
Run: `npm test -- test/commands/update.test.js`
|
|
638
|
-
Expected: All pass (new call is additive, doesn't break existing behavior)
|
|
639
|
-
|
|
640
|
-
**Step 5: Commit**
|
|
641
|
-
|
|
642
|
-
```bash
|
|
643
|
-
git add src/commands/project/init.js src/commands/project/update.js
|
|
644
|
-
git commit -m "feat(init,update): install domain specialist subagents alongside tier-1/2 agents"
|
|
645
|
-
```
|
|
646
|
-
|
|
647
|
-
---
|
|
648
|
-
|
|
649
|
-
## Phase 4: Cleanup and version bump
|
|
650
|
-
|
|
651
|
-
### Task 9: Remove validate-bash-commands.js dead file and update HOOKS_VERSION
|
|
652
|
-
|
|
653
|
-
The Bash guard is now a prompt-type hook. The Node.js file is no longer called. Remove it to reduce confusion.
|
|
654
|
-
|
|
655
|
-
**Files:**
|
|
656
|
-
- Delete: `framework/hooks/claude-code/pre-tool-use/validate-bash-commands.js`
|
|
657
|
-
- Bump HOOKS_VERSION in `src/utils/hooks-installer.js` to `2.4.0`
|
|
658
|
-
|
|
659
|
-
**Step 1: Verify the file is no longer referenced in MORPH_HOOKS**
|
|
660
|
-
|
|
661
|
-
Run: `grep -r "validate-bash-commands" src/ framework/hooks/`
|
|
662
|
-
Expected: Zero matches in production code (tests may reference it)
|
|
663
|
-
|
|
664
|
-
**Step 2: Delete the file**
|
|
665
|
-
|
|
666
|
-
```bash
|
|
667
|
-
rm "framework/hooks/claude-code/pre-tool-use/validate-bash-commands.js"
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
**Step 3: Bump HOOKS_VERSION**
|
|
671
|
-
|
|
672
|
-
In `src/utils/hooks-installer.js`:
|
|
673
|
-
```js
|
|
674
|
-
const HOOKS_VERSION = '2.4.0';
|
|
675
|
-
```
|
|
676
|
-
|
|
677
|
-
**Step 4: Run all tests**
|
|
678
|
-
|
|
679
|
-
Run: `npm test`
|
|
680
|
-
Expected: All pass
|
|
681
|
-
|
|
682
|
-
**Step 5: Commit**
|
|
683
|
-
|
|
684
|
-
```bash
|
|
685
|
-
git add src/utils/hooks-installer.js
|
|
686
|
-
git rm framework/hooks/claude-code/pre-tool-use/validate-bash-commands.js
|
|
687
|
-
git commit -m "chore(hooks): remove validate-bash-commands.js (replaced by prompt-type hook), bump HOOKS_VERSION to 2.4.0"
|
|
688
|
-
```
|
|
689
|
-
|
|
690
|
-
---
|
|
691
|
-
|
|
692
|
-
### Task 10: Update package.json version and run full test suite
|
|
693
|
-
|
|
694
|
-
**Files:**
|
|
695
|
-
- Modify: `package.json`
|
|
696
|
-
- Modify: `framework/CLAUDE.md` (version string)
|
|
697
|
-
- Modify: `framework/CLAUDE_runtime.md` (version string)
|
|
698
|
-
|
|
699
|
-
**Step 1: Bump version to 4.5.0**
|
|
700
|
-
|
|
701
|
-
In `package.json`, change `"version": "4.4.0"` to `"version": "4.5.0"`.
|
|
702
|
-
|
|
703
|
-
**Step 2: Update version strings in framework files**
|
|
704
|
-
|
|
705
|
-
In `framework/CLAUDE.md` and `framework/CLAUDE_runtime.md`, update `v4.4.0` to `v4.5.0`.
|
|
706
|
-
|
|
707
|
-
**Step 3: Run full test suite**
|
|
708
|
-
|
|
709
|
-
Run: `npm test`
|
|
710
|
-
Expected: All tests pass, zero failures
|
|
711
|
-
|
|
712
|
-
**Step 4: Final commit**
|
|
713
|
-
|
|
714
|
-
```bash
|
|
715
|
-
git add package.json framework/CLAUDE.md framework/CLAUDE_runtime.md
|
|
716
|
-
git commit -m "chore: bump version to 4.5.0 — CC native alignment round 3"
|
|
717
|
-
```
|
|
718
|
-
|
|
719
|
-
---
|
|
720
|
-
|
|
721
|
-
## Deferred (out of scope for this plan)
|
|
722
|
-
|
|
723
|
-
The following items from the analysis require deeper investigation or have higher risk:
|
|
724
|
-
|
|
725
|
-
- **Rewrite hooks shared modules to bash/jq** — high refactor risk; hooks work correctly today
|
|
726
|
-
- **Map trust scoring to `permissions.defaultMode`** — trust is deeply integrated into state + approval gating; separate PR
|
|
727
|
-
- **Migrate standards CLI to skills** — large UX change; needs design discussion
|
|
728
|
-
- **Reduce workflow configs from 10 to ≤4** — needs usage data
|
|
729
|
-
- **Verify `.claude/rules/` native CC support** — needs official CC team confirmation
|
|
730
|
-
- **Replace workflow-detector LLM call with AskUserQuestion** — behavior change requiring testing
|