@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.
Files changed (44) hide show
  1. package/README.md +2 -2
  2. package/claude-plugin.json +1 -1
  3. package/docs/CHEATSHEET.md +1 -1
  4. package/docs/QUICKSTART.md +1 -1
  5. package/framework/hooks/dev/guard-version-numbers.js +1 -1
  6. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +1 -1
  7. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +1 -1
  8. package/framework/skills/level-1-workflows/phase-design/SKILL.md +1 -1
  9. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +1 -1
  10. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +1 -1
  11. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +1 -1
  12. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +1 -1
  13. package/package.json +4 -4
  14. package/.morph/analytics/threads-log.jsonl +0 -54
  15. package/.morph/state.json +0 -198
  16. package/docs/ARCHITECTURE.md +0 -328
  17. package/docs/COMMAND-FLOWS.md +0 -398
  18. package/docs/plans/2026-02-22-claude-docs-morph-alignment-analysis.md +0 -514
  19. package/docs/plans/2026-02-22-claude-settings.md +0 -517
  20. package/docs/plans/2026-02-22-morph-cc-alignment-impl.md +0 -730
  21. package/docs/plans/2026-02-22-morph-spec-next.md +0 -480
  22. package/docs/plans/2026-02-22-native-alignment-design.md +0 -201
  23. package/docs/plans/2026-02-22-native-alignment-impl.md +0 -927
  24. package/docs/plans/2026-02-22-native-enrichment-design.md +0 -246
  25. package/docs/plans/2026-02-22-native-enrichment.md +0 -737
  26. package/docs/plans/2026-02-23-ddd-architecture-refactor.md +0 -1155
  27. package/docs/plans/2026-02-23-ddd-nextsteps.md +0 -684
  28. package/docs/plans/2026-02-23-infra-architect-refactor.md +0 -439
  29. package/docs/plans/2026-02-23-nextjs-code-review-design.md +0 -157
  30. package/docs/plans/2026-02-23-nextjs-code-review-impl.md +0 -1256
  31. package/docs/plans/2026-02-23-nextjs-standards-design.md +0 -150
  32. package/docs/plans/2026-02-23-nextjs-standards-impl.md +0 -1848
  33. package/docs/plans/2026-02-24-cli-radical-simplification.md +0 -592
  34. package/docs/plans/2026-02-24-framework-failure-points.md +0 -125
  35. package/docs/plans/2026-02-24-morph-init-design.md +0 -337
  36. package/docs/plans/2026-02-24-morph-init-impl.md +0 -1269
  37. package/docs/plans/2026-02-24-tutorial-command-design.md +0 -71
  38. package/docs/plans/2026-02-24-tutorial-command.md +0 -298
  39. package/scripts/bump-version.js +0 -248
  40. package/scripts/generate-refs.js +0 -336
  41. package/scripts/generate-standards-registry.js +0 -44
  42. package/scripts/install-dev-hooks.js +0 -138
  43. package/scripts/scan-nextjs.mjs +0 -169
  44. package/scripts/validate-real.mjs +0 -255
@@ -1,927 +0,0 @@
1
- # 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:** Align MORPH-SPEC with Claude Code's native platform patterns across 6 areas: skills format, rules as @-imports, CLAUDE.md consolidation, test file policy, standards additions, and state.json simplification.
8
-
9
- **Architecture:** Non-breaking changes first (Tasks 1-5), state.json simplification last (Tasks 6-8). All 614 existing tests must pass at each commit. Each task is independently testable.
10
-
11
- **Tech Stack:** Node.js ESM, `node:test`, `node:fs`, `node:path`. Run tests with `npm test`. Single test file: `node --test test/path/to/file.test.js`.
12
-
13
- ---
14
-
15
- ## Task 1: Fix Skills File Format (flat → subdirectory/SKILL.md)
16
-
17
- **Files:**
18
- - Modify: `src/utils/skills-installer.js`
19
- - Modify: `test/utils/skills-installer.test.js`
20
-
21
- **Context:** Claude Code's native skill format is `.claude/skills/<name>/SKILL.md`, not `.claude/skills/<name>.md`. Flat files prevent frontmatter features (allowed-tools, argument-hint) from working. Current code in `installSkillsFromDir()` does `copyFileSync(srcPath, join(destDir, entry))` — we change it to `mkdir(join(destDir, skillName))` + `copyFile(srcPath, join(destDir, skillName, 'SKILL.md'))`.
22
-
23
- **Step 1: Update skills-installer.js**
24
-
25
- Replace the body of `installSkillsFromDir` in `src/utils/skills-installer.js`:
26
-
27
- ```javascript
28
- function installSkillsFromDir(srcDir, destDir) {
29
- const entries = readdirSync(srcDir);
30
- for (const entry of entries) {
31
- const srcPath = join(srcDir, entry);
32
- const stat = statSync(srcPath);
33
-
34
- if (stat.isDirectory()) {
35
- installSkillsFromDir(srcPath, destDir);
36
- } else if (entry.endsWith('.md') && entry !== 'README.md') {
37
- const skillName = entry.slice(0, -3); // strip .md
38
- const skillDir = join(destDir, skillName);
39
- mkdirSync(skillDir, { recursive: true });
40
- copyFileSync(srcPath, join(skillDir, 'SKILL.md'));
41
- }
42
- }
43
- }
44
- ```
45
-
46
- Also add `mkdirSync` to the imports at line 11:
47
- ```javascript
48
- import { mkdirSync, copyFileSync, existsSync, readdirSync, statSync } from 'fs';
49
- ```
50
- (mkdirSync is already imported — verify it's there, add if missing)
51
-
52
- **Step 2: Run single test file to verify it fails**
53
-
54
- ```bash
55
- node --test test/utils/skills-installer.test.js
56
- ```
57
-
58
- Expected: FAIL — assertions check for `.md` filenames like `morph-checklist.md` which no longer exist.
59
-
60
- **Step 3: Update skills-installer.test.js**
61
-
62
- Replace the entire test file content:
63
-
64
- ```javascript
65
- /**
66
- * Tests for src/utils/skills-installer.js
67
- */
68
-
69
- import { test, describe, before, after } from 'node:test';
70
- import assert from 'node:assert/strict';
71
- import { mkdtemp, rm, readdir, stat } from 'node:fs/promises';
72
- import { join } from 'path';
73
- import { tmpdir } from 'os';
74
- import { installSkills } from '../../src/utils/skills-installer.js';
75
-
76
- describe('installSkills', () => {
77
- let tmpDir;
78
-
79
- before(async () => {
80
- tmpDir = await mkdtemp(join(tmpdir(), 'morph-skills-'));
81
- });
82
-
83
- after(async () => {
84
- await rm(tmpDir, { recursive: true, force: true });
85
- });
86
-
87
- test('creates .claude/skills/ directory', async () => {
88
- await installSkills(tmpDir);
89
- const entries = await readdir(join(tmpDir, '.claude', 'skills'));
90
- assert.ok(entries.length > 0, 'should have skill directories installed');
91
- });
92
-
93
- test('installs skills as subdirectories (not flat .md files)', async () => {
94
- await installSkills(tmpDir);
95
- const entries = await readdir(join(tmpDir, '.claude', 'skills'));
96
- // All entries should be directories, not .md files
97
- for (const entry of entries) {
98
- assert.ok(!entry.endsWith('.md'), `${entry} should be a directory, not a flat .md file`);
99
- }
100
- });
101
-
102
- test('each skill directory contains SKILL.md', async () => {
103
- await installSkills(tmpDir);
104
- const entries = await readdir(join(tmpDir, '.claude', 'skills'));
105
- for (const entry of entries) {
106
- const skillMd = join(tmpDir, '.claude', 'skills', entry, 'SKILL.md');
107
- const s = await stat(skillMd).catch(() => null);
108
- assert.ok(s?.isFile(), `${entry}/SKILL.md should exist and be a file`);
109
- }
110
- });
111
-
112
- test('installs level-0-meta skills (morph-checklist, code-review)', async () => {
113
- await installSkills(tmpDir);
114
- const entries = await readdir(join(tmpDir, '.claude', 'skills'));
115
- assert.ok(entries.includes('morph-checklist'), 'morph-checklist/ directory should be installed');
116
- assert.ok(entries.includes('code-review'), 'code-review/ directory should be installed');
117
- });
118
-
119
- test('installs level-1-workflows skills (phase-design, phase-implement)', async () => {
120
- await installSkills(tmpDir);
121
- const entries = await readdir(join(tmpDir, '.claude', 'skills'));
122
- assert.ok(entries.includes('phase-design'), 'phase-design/ directory should be installed');
123
- assert.ok(entries.includes('phase-implement'), 'phase-implement/ directory should be installed');
124
- });
125
-
126
- test('does not install README.md files as skills', async () => {
127
- await installSkills(tmpDir);
128
- const entries = await readdir(join(tmpDir, '.claude', 'skills'));
129
- assert.ok(!entries.includes('README'), 'README should not be installed as a skill');
130
- assert.ok(!entries.includes('README.md'), 'README.md should not appear in skills dir');
131
- });
132
-
133
- test('installs at least 10 skills total', async () => {
134
- await installSkills(tmpDir);
135
- const entries = await readdir(join(tmpDir, '.claude', 'skills'));
136
- assert.ok(entries.length >= 10, `should install at least 10 skills, got ${entries.length}`);
137
- });
138
-
139
- test('is idempotent — running twice produces same directory count', async () => {
140
- await installSkills(tmpDir);
141
- const firstCount = (await readdir(join(tmpDir, '.claude', 'skills'))).length;
142
- await installSkills(tmpDir);
143
- const secondCount = (await readdir(join(tmpDir, '.claude', 'skills'))).length;
144
- assert.strictEqual(firstCount, secondCount, 'directory count should not change on second run');
145
- });
146
- });
147
- ```
148
-
149
- **Step 4: Run test to verify it passes**
150
-
151
- ```bash
152
- node --test test/utils/skills-installer.test.js
153
- ```
154
-
155
- Expected: all 8 tests PASS.
156
-
157
- **Step 5: Run full suite**
158
-
159
- ```bash
160
- npm test
161
- ```
162
-
163
- Expected: 614+ tests pass, 0 fail.
164
-
165
- **Step 6: Commit**
166
-
167
- ```bash
168
- git add src/utils/skills-installer.js test/utils/skills-installer.test.js
169
- git commit -m "feat(skills): install as subdirectory/SKILL.md — native Claude Code format"
170
- ```
171
-
172
- ---
173
-
174
- ## Task 2: Rules as Dynamic @-imports
175
-
176
- **Files:**
177
- - Modify: `framework/rules/csharp-standards.md`
178
- - Modify: `framework/rules/frontend-standards.md`
179
- - Modify: `framework/rules/testing-standards.md`
180
- - Modify: `framework/rules/infrastructure-standards.md`
181
-
182
- **Context:** Rules currently contain static copies of standards content. Replacing with @-imports makes them always current. The @-import paths use `.morph/framework/standards/` because rules are installed into user projects where that path is valid after `morph-spec init`. `morph-workflow.md` is NOT changed here — it has its own content (not a standards file).
183
-
184
- No code changes — only markdown content changes. No tests needed (framework content files have no unit tests).
185
-
186
- **Step 1: Replace csharp-standards.md body**
187
-
188
- Replace everything after the frontmatter (after line 5 `---`) in `framework/rules/csharp-standards.md` with:
189
-
190
- ```markdown
191
- ---
192
- paths:
193
- - "**/*.cs"
194
- - "**/*.csproj"
195
- ---
196
-
197
- # C# and .NET Standards
198
-
199
- @.morph/framework/standards/core/coding.md
200
- @.morph/framework/standards/backend/dotnet.md
201
- ```
202
-
203
- > **Note:** Verify the exact filenames exist in `framework/standards/core/` and `framework/standards/backend/`. Run `ls framework/standards/core/` and `ls framework/standards/backend/` first. Use the actual filenames found.
204
-
205
- **Step 2: Replace frontend-standards.md body**
206
-
207
- ```markdown
208
- ---
209
- paths:
210
- - "**/*.razor"
211
- - "**/*.tsx"
212
- - "**/*.ts"
213
- - "**/*.css"
214
- - "**/*.scss"
215
- ---
216
-
217
- # Frontend Standards
218
-
219
- @.morph/framework/standards/frontend/blazor.md
220
- @.morph/framework/standards/frontend/nextjs.md
221
- @.morph/framework/standards/frontend/css.md
222
- ```
223
-
224
- > Same note: verify filenames first with `ls framework/standards/frontend/`.
225
-
226
- **Step 3: Replace testing-standards.md body**
227
-
228
- ```markdown
229
- ---
230
- paths:
231
- - "tests/**"
232
- - "**/*.test.*"
233
- - "**/*.spec.*"
234
- - "**/*Tests.cs"
235
- ---
236
-
237
- # Testing Standards
238
-
239
- @.morph/framework/standards/core/testing.md
240
- ```
241
-
242
- **Step 4: Replace infrastructure-standards.md body**
243
-
244
- ```markdown
245
- ---
246
- paths:
247
- - "**/*.bicep"
248
- - "**/Dockerfile"
249
- - "**/docker-compose*.yml"
250
- - "**/pipelines/**"
251
- - "**/.github/workflows/**"
252
- ---
253
-
254
- # Infrastructure Standards
255
-
256
- @.morph/framework/standards/infrastructure/azure.md
257
- @.morph/framework/standards/infrastructure/docker.md
258
- ```
259
-
260
- **Step 5: Verify actual standard filenames before writing**
261
-
262
- ```bash
263
- ls framework/standards/core/
264
- ls framework/standards/backend/
265
- ls framework/standards/frontend/
266
- ls framework/standards/infrastructure/
267
- ```
268
-
269
- Adjust @-import paths above to match actual filenames.
270
-
271
- **Step 6: Run full suite (no tests for framework content, but verify nothing broke)**
272
-
273
- ```bash
274
- npm test
275
- ```
276
-
277
- Expected: same pass count as before.
278
-
279
- **Step 7: Commit**
280
-
281
- ```bash
282
- git add framework/rules/csharp-standards.md framework/rules/frontend-standards.md framework/rules/testing-standards.md framework/rules/infrastructure-standards.md
283
- git commit -m "feat(rules): replace static content with @-imports from framework/standards"
284
- ```
285
-
286
- ---
287
-
288
- ## Task 3: CLAUDE.md Consolidation
289
-
290
- **Files:**
291
- - Modify: `framework/CLAUDE.md` — merge both files into one
292
- - Delete: `framework/CLAUDE_runtime.md`
293
- - Modify: `src/commands/project/init.js` — change CLAUDE_runtime.md → CLAUDE.md as source
294
- - Modify: `src/commands/project/update.js` — same
295
- - Modify: `test/commands/init.test.js` — update source file reference
296
- - Modify: `test/commands/update.test.js` — update source file reference
297
-
298
- **Context:** `init.js` copies `framework/CLAUDE_runtime.md` → `.claude/CLAUDE.md` in user projects. After this task, it copies `framework/CLAUDE.md` instead. The merged file has all content from both plus MCP deferred mode note.
299
-
300
- **Step 1: Find the CLAUDE_runtime.md copy line in init.js**
301
-
302
- ```bash
303
- grep -n "CLAUDE_runtime" src/commands/project/init.js
304
- grep -n "CLAUDE_runtime" src/commands/project/update.js
305
- ```
306
-
307
- Note the exact line numbers.
308
-
309
- **Step 2: Write new merged framework/CLAUDE.md**
310
-
311
- Replace the entire content of `framework/CLAUDE.md`:
312
-
313
- ```markdown
314
- # MORPH-SPEC Runtime Instructions
315
-
316
- > by Polymorphism Tech — Spec-driven development for .NET/Blazor/Next.js/Azure
317
-
318
- ---
319
-
320
- ## Project Context
321
-
322
- @.morph/context/README.md
323
-
324
- ---
325
-
326
- ## Critical Rules
327
-
328
- **NEVER:**
329
- - Skip to code without a specification
330
- - Implement without design approval
331
- - Ignore standards in `.morph/framework/standards/`
332
- - Create infrastructure manually
333
- - Generate code without defined contracts
334
-
335
- **ALWAYS:**
336
- - Follow the mandatory phases
337
- - Generate outputs in `.morph/features/{feature}/`
338
- - Document decisions in `decisions.md`
339
- - Checkpoint every 3 implemented tasks
340
- - Use Infrastructure as Code
341
-
342
- ---
343
-
344
- ## Quick Reference
345
-
346
- | Command | Purpose |
347
- |---------|---------|
348
- | `/morph-proposal {feature}` | Full spec pipeline (phases 1–4, pauses for approval) |
349
- | `/morph-apply {feature}` | Implement feature (phase 5) |
350
- | `/morph-status` | Feature status dashboard |
351
- | `/morph-preflight` | Pre-implementation validation |
352
-
353
- ---
354
-
355
- ## State & Outputs
356
-
357
- | Path | Notes |
358
- |------|-------|
359
- | `.morph/state.json` | **READ-ONLY** — use `morph-spec` CLI to update |
360
- | `.morph/features/{feature}/{phase}/` | Feature outputs organized by phase |
361
- | `.morph/framework/` | **READ-ONLY** — framework files managed by morph-spec |
362
- | `.morph/config/config.json` | Project configuration (editable) |
363
-
364
- ---
365
-
366
- ## Phase Sequence
367
-
368
- ```
369
- proposal → setup → [uiux] → design → clarify → tasks → implement → [sync]
370
- ```
371
-
372
- Use `morph-spec state show {feature}` to see current phase and pending approval gates.
373
-
374
- ---
375
-
376
- ## Agents
377
-
378
- Tier-1 and tier-2 MORPH agents are available as native subagents in `.claude/agents/`.
379
- They can be invoked directly by Claude Code during multi-agent workflows.
380
-
381
- ---
382
-
383
- ## Context Window Tip
384
-
385
- When using 3+ MCPs, add `"experimental": { "mcpCliMode": true }` to `.claude/settings.json`.
386
- MCP tools load on-demand instead of all at startup — keeps context clean for actual work.
387
-
388
- ---
389
-
390
- *MORPH-SPEC v4.5.0 by Polymorphism Tech*
391
- ```
392
-
393
- **Step 3: Update init.js source reference**
394
-
395
- Find the line that copies CLAUDE_runtime.md and change it to CLAUDE.md. It will look something like:
396
-
397
- ```javascript
398
- // BEFORE (find exact line with grep result from Step 1):
399
- await copyFile(join(frameworkDir, 'CLAUDE_runtime.md'), join(projectDir, '.claude', 'CLAUDE.md'));
400
-
401
- // AFTER:
402
- await copyFile(join(frameworkDir, 'CLAUDE.md'), join(projectDir, '.claude', 'CLAUDE.md'));
403
- ```
404
-
405
- **Step 4: Update update.js source reference**
406
-
407
- Same change in `src/commands/project/update.js`.
408
-
409
- **Step 5: Run test files to find failing assertions**
410
-
411
- ```bash
412
- node --test test/commands/init.test.js
413
- node --test test/commands/update.test.js
414
- ```
415
-
416
- Find any assertions referencing `CLAUDE_runtime.md` and change them to `CLAUDE.md`.
417
-
418
- **Step 6: Delete framework/CLAUDE_runtime.md**
419
-
420
- ```bash
421
- git rm framework/CLAUDE_runtime.md
422
- ```
423
-
424
- **Step 7: Run full suite**
425
-
426
- ```bash
427
- npm test
428
- ```
429
-
430
- Expected: same pass count, 0 fail.
431
-
432
- **Step 8: Commit**
433
-
434
- ```bash
435
- git add framework/CLAUDE.md src/commands/project/init.js src/commands/project/update.js test/commands/init.test.js test/commands/update.test.js
436
- git commit -m "feat(claude-md): merge CLAUDE.md + CLAUDE_runtime.md into single unified file"
437
- ```
438
-
439
- ---
440
-
441
- ## Task 4: Test File Policy in morph-workflow.md
442
-
443
- **Files:**
444
- - Modify: `framework/rules/morph-workflow.md`
445
-
446
- **Context:** No hook — behavioral guidance only. Add "Test File Policy" section at the end of the file (before the final `*MORPH-SPEC by Polymorphism Tech*` footer). No tests needed.
447
-
448
- **Step 1: Add section to framework/rules/morph-workflow.md**
449
-
450
- Append before the final footer line:
451
-
452
- ```markdown
453
- ---
454
-
455
- ## Test File Policy
456
-
457
- When a test fails, always follow this order:
458
-
459
- 1. **Analyze first** — determine if the IMPLEMENTATION is wrong or the TEST SPEC is wrong
460
- 2. **Fix implementation first** — the test is the spec; trust it by default
461
- 3. **Only modify a test file if the test expectation itself is incorrect** — wrong expected value, wrong behavior modeled
462
- 4. **Before modifying any test file, explain WHY the test spec is wrong** — what the correct behavior is and why the test doesn't model it
463
-
464
- Do not modify test files to make a failing test pass when the implementation is the actual problem.
465
-
466
- ---
467
- ```
468
-
469
- **Step 2: Run full suite**
470
-
471
- ```bash
472
- npm test
473
- ```
474
-
475
- Expected: same pass count.
476
-
477
- **Step 3: Commit**
478
-
479
- ```bash
480
- git add framework/rules/morph-workflow.md
481
- git commit -m "feat(rules): add Test File Policy to morph-workflow rule"
482
- ```
483
-
484
- ---
485
-
486
- ## Task 5: Standards and Templates Additions
487
-
488
- **Files:**
489
- - Modify: `framework/rules/frontend-standards.md` — note: already updated in Task 2 with @-imports, so also update the source standard file
490
- - Create: `framework/templates/docs/user-stories.md`
491
-
492
- **Context:** TypeScript strict mode guidance goes into the actual standards file (which the rule now @-imports). User stories template is a new Handlebars template.
493
-
494
- **Step 1: Check if TypeScript strict is already in frontend standards**
495
-
496
- ```bash
497
- grep -r "strict" framework/standards/frontend/
498
- ```
499
-
500
- If not present, add to the appropriate standards file (e.g., `framework/standards/frontend/typescript.md` or whatever file exists):
501
-
502
- ```markdown
503
- ## TypeScript Strict Mode (Required)
504
-
505
- Always enable `"strict": true` in `tsconfig.json`. This is non-negotiable for
506
- agent-assisted development because agents rely on compiler errors to self-correct.
507
- Without strict mode, type/null errors only surface at runtime where agents cannot observe them.
508
-
509
- ```json
510
- {
511
- "compilerOptions": {
512
- "strict": true,
513
- "noUncheckedIndexedAccess": true
514
- }
515
- }
516
- ```
517
-
518
- Strict mode enables: `strictNullChecks`, `noImplicitAny`, `strictFunctionTypes`, `strictPropertyInitialization`.
519
- ```
520
-
521
- **Step 2: Create framework/templates/docs/user-stories.md**
522
-
523
- ```markdown
524
- # User Stories: {{featureName}}
525
-
526
- > Generated: {{date}} | Feature: {{featureName}} | Status: Draft
527
-
528
- ---
529
-
530
- ## Story 1: [Actor] [Action]
531
-
532
- **Feature:** {{featureName}}
533
- **Priority:** High / Medium / Low
534
-
535
- ### Optimal Path
536
- 1. [Step 1]
537
- 2. [Step 2]
538
- 3. [Step 3 — success state]
539
-
540
- ### Edge Cases
541
- - [Edge case] → [Expected behavior]
542
- - [Error condition] → [Expected error handling]
543
-
544
- ### Acceptance Criteria
545
- - [ ] [Measurable criterion 1]
546
- - [ ] [Measurable criterion 2]
547
- - [ ] Edge: [edge case criterion]
548
-
549
- ---
550
-
551
- ## Story 2: [Actor] [Action]
552
-
553
- [Repeat structure above]
554
-
555
- ---
556
-
557
- *Generated by MORPH-SPEC — complete before implementation begins*
558
- ```
559
-
560
- **Step 3: Run full suite**
561
-
562
- ```bash
563
- npm test
564
- ```
565
-
566
- Expected: same pass count.
567
-
568
- **Step 4: Commit**
569
-
570
- ```bash
571
- git add framework/standards/ framework/templates/docs/user-stories.md
572
- git commit -m "feat(standards): add TypeScript strict mode requirement; add user-stories template"
573
- ```
574
-
575
- ---
576
-
577
- ## Task 6: Remove `outputs` and `phase` from state-manager.js
578
-
579
- **Files:**
580
- - Modify: `src/core/state/state-manager.js`
581
- - Modify: `test/lib/state-manager.test.js`
582
-
583
- **Context:** `ensureFeature()` at line 191 sets `outputs: getAllOutputPaths(featureName)` and `phase: "proposal"`. Remove both fields. Add two new exported helper functions: `derivePhase(featurePath)` and `deriveOutputs(featureName)`. Add v4→v5 migration in `loadState()`.
584
-
585
- **Step 1: Add helper functions to state-manager.js**
586
-
587
- Add these two new exported functions after the existing `getStatePath()` function (around line 25):
588
-
589
- ```javascript
590
- /**
591
- * Derive current phase from filesystem — checks for phase folders in descending order.
592
- * Returns the phase corresponding to the highest-numbered folder present.
593
- *
594
- * @param {string} featurePath - Absolute path to .morph/features/{feature}/
595
- * @returns {string} Phase name: 'implement' | 'tasks' | 'uiux' | 'design' | 'proposal' | 'setup'
596
- */
597
- export function derivePhase(featurePath) {
598
- const phaseMap = [
599
- ['4-implement', 'implement'],
600
- ['3-tasks', 'tasks'],
601
- ['2-ui', 'uiux'],
602
- ['1-design', 'design'],
603
- ['0-proposal', 'proposal'],
604
- ];
605
- for (const [folder, phase] of phaseMap) {
606
- if (existsSync(join(featurePath, folder))) return phase;
607
- }
608
- return 'setup';
609
- }
610
-
611
- /**
612
- * Derive output existence from filesystem — checks if each output file exists at its expected path.
613
- * Returns an object matching the old outputs shape for backwards-compatible display.
614
- *
615
- * @param {string} featureName - Feature name
616
- * @param {string} [baseDir] - Project base dir (defaults to cwd)
617
- * @returns {Object} Map of outputType → { created: boolean, path: string }
618
- */
619
- export function deriveOutputs(featureName, baseDir = process.cwd()) {
620
- const outputPaths = getAllOutputPaths(featureName);
621
- const result = {};
622
- for (const [type, { path: relPath }] of Object.entries(outputPaths)) {
623
- const absPath = join(baseDir, relPath);
624
- result[type] = { created: existsSync(absPath), path: relPath };
625
- }
626
- return result;
627
- }
628
- ```
629
-
630
- **Step 2: Remove `outputs` and `phase` from ensureFeature()**
631
-
632
- In `ensureFeature()` (around line 191), change:
633
-
634
- ```javascript
635
- // REMOVE these two lines:
636
- phase: "proposal",
637
- outputs: getAllOutputPaths(featureName),
638
- ```
639
-
640
- The feature object after the change should NOT have `phase` or `outputs` keys.
641
-
642
- **Step 3: Add v4→v5 migration in loadState()**
643
-
644
- In `loadState()`, after the existing v3→v4 migration block (around line 65), add:
645
-
646
- ```javascript
647
- // Migrate v4.x → v5.0.0: remove outputs and phase (now derived from filesystem)
648
- if (state.version && state.version.startsWith('4.')) {
649
- state.version = '5.0.0';
650
- for (const feature of Object.values(state.features || {})) {
651
- delete feature.outputs;
652
- delete feature.phase;
653
- }
654
- writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
655
- }
656
- ```
657
-
658
- **Step 4: Update initState() version string**
659
-
660
- Change `version: "4.0.0"` to `version: "5.0.0"` in `initState()`.
661
-
662
- **Step 5: Run state-manager tests to find failures**
663
-
664
- ```bash
665
- node --test test/lib/state-manager.test.js
666
- ```
667
-
668
- Find tests that assert on `feature.phase` or `feature.outputs` and update them:
669
- - Tests asserting `feature.phase === 'design'` → remove or replace with `derivePhase()` call
670
- - Tests asserting `feature.outputs.spec.created` → remove or replace with `deriveOutputs()` call
671
- - Tests asserting `state.version === '4.0.0'` → change to `'5.0.0'`
672
-
673
- **Step 6: Add new tests for derivePhase and deriveOutputs**
674
-
675
- In `test/lib/state-manager.test.js`, add:
676
-
677
- ```javascript
678
- import { derivePhase, deriveOutputs } from '../../src/core/state/state-manager.js';
679
- import { mkdirSync } from 'fs';
680
-
681
- describe('derivePhase', () => {
682
- test('returns setup when no phase folders exist', () => {
683
- const dir = createTempDir();
684
- assert.strictEqual(derivePhase(join(dir, '.morph', 'features', 'f')), 'setup');
685
- cleanupTempDir(dir);
686
- });
687
-
688
- test('returns proposal when 0-proposal/ exists', () => {
689
- const dir = createTempDir();
690
- const featurePath = join(dir, '.morph', 'features', 'f');
691
- mkdirSync(join(featurePath, '0-proposal'), { recursive: true });
692
- assert.strictEqual(derivePhase(featurePath), 'proposal');
693
- cleanupTempDir(dir);
694
- });
695
-
696
- test('returns tasks when 3-tasks/ is highest folder', () => {
697
- const dir = createTempDir();
698
- const featurePath = join(dir, '.morph', 'features', 'f');
699
- mkdirSync(join(featurePath, '0-proposal'), { recursive: true });
700
- mkdirSync(join(featurePath, '1-design'), { recursive: true });
701
- mkdirSync(join(featurePath, '3-tasks'), { recursive: true });
702
- assert.strictEqual(derivePhase(featurePath), 'tasks');
703
- cleanupTempDir(dir);
704
- });
705
-
706
- test('returns implement when 4-implement/ exists', () => {
707
- const dir = createTempDir();
708
- const featurePath = join(dir, '.morph', 'features', 'f');
709
- mkdirSync(join(featurePath, '4-implement'), { recursive: true });
710
- assert.strictEqual(derivePhase(featurePath), 'implement');
711
- cleanupTempDir(dir);
712
- });
713
- });
714
-
715
- describe('state migration v4 → v5', () => {
716
- test('removes outputs and phase from v4 state on load', () => {
717
- const dir = createTempDir();
718
- process.chdir(dir);
719
- // Write a v4.0.0 state with outputs and phase
720
- const v4State = {
721
- version: '4.0.0',
722
- project: { name: 'Test' },
723
- features: {
724
- 'my-feature': {
725
- phase: 'design',
726
- outputs: { spec: { created: true, path: 'some/path' } },
727
- workflow: 'standard'
728
- }
729
- }
730
- };
731
- mkdirSync(join(dir, '.morph'), { recursive: true });
732
- writeFileSync(join(dir, '.morph', 'state.json'), JSON.stringify(v4State), 'utf8');
733
-
734
- const state = loadState();
735
- assert.strictEqual(state.version, '5.0.0');
736
- assert.strictEqual(state.features['my-feature'].phase, undefined);
737
- assert.strictEqual(state.features['my-feature'].outputs, undefined);
738
- assert.strictEqual(state.features['my-feature'].workflow, 'standard'); // kept
739
-
740
- process.chdir(originalCwd);
741
- cleanupTempDir(dir);
742
- });
743
- });
744
- ```
745
-
746
- **Step 7: Run full suite**
747
-
748
- ```bash
749
- npm test
750
- ```
751
-
752
- Expected: 614+ tests pass, 0 fail.
753
-
754
- **Step 8: Commit**
755
-
756
- ```bash
757
- git add src/core/state/state-manager.js test/lib/state-manager.test.js
758
- git commit -m "feat(state): remove outputs+phase fields; derive from filesystem at runtime"
759
- ```
760
-
761
- ---
762
-
763
- ## Task 7: Delete track-output-creation hook + unregister it
764
-
765
- **Files:**
766
- - Delete: `framework/hooks/claude-code/post-tool-use/track-output-creation.js`
767
- - Modify: `src/utils/hooks-installer.js` — remove PostToolUse/Write entry for track-output-creation
768
- - Delete: `test/hooks/track-output-creation.test.js`
769
- - Modify: `test/hooks/hooks-installer.test.js` — verify hook no longer registered
770
-
771
- **Context:** `track-output-creation.js` existed solely to mark `outputs.{type}.created = true` in state.json. With `outputs` removed from state.json (Task 6), this hook has no purpose.
772
-
773
- **Step 1: Remove the PostToolUse/Write hook entry from hooks-installer.js**
774
-
775
- In `src/utils/hooks-installer.js`, find and delete the entire block (around lines 92-100):
776
-
777
- ```javascript
778
- // DELETE this entire block:
779
- // === PostToolUse: Write ===
780
- {
781
- event: 'PostToolUse',
782
- matcher: 'Write',
783
- hooks: [{
784
- type: 'command',
785
- command: 'node framework/hooks/claude-code/post-tool-use/track-output-creation.js'
786
- }]
787
- },
788
- ```
789
-
790
- **Step 2: Delete the hook file**
791
-
792
- ```bash
793
- git rm framework/hooks/claude-code/post-tool-use/track-output-creation.js
794
- git rm test/hooks/track-output-creation.test.js
795
- ```
796
-
797
- **Step 3: Update hooks-installer.test.js**
798
-
799
- Find any test that asserts `PostToolUse` is installed with `track-output-creation` and either remove it or change it to assert the hook is NOT registered. Add:
800
-
801
- ```javascript
802
- test('does NOT register track-output-creation hook (removed in v5)', async () => {
803
- await installClaudeHooks(tempDir);
804
- const settings = readSettings();
805
- const postToolUseHooks = settings.hooks?.PostToolUse ?? [];
806
- const commands = postToolUseHooks.flatMap(h => h.hooks ?? []).map(h => h.command ?? '');
807
- assert.ok(
808
- !commands.some(c => c.includes('track-output-creation')),
809
- 'track-output-creation should not be registered'
810
- );
811
- });
812
- ```
813
-
814
- **Step 4: Run hooks tests**
815
-
816
- ```bash
817
- node --test test/hooks/hooks-installer.test.js
818
- ```
819
-
820
- Expected: all pass.
821
-
822
- **Step 5: Run full suite**
823
-
824
- ```bash
825
- npm test
826
- ```
827
-
828
- Expected: test count slightly lower (track-output-creation.test.js removed), 0 fail.
829
-
830
- **Step 6: Commit**
831
-
832
- ```bash
833
- git add src/utils/hooks-installer.js test/hooks/hooks-installer.test.js
834
- git commit -m "feat(hooks): remove track-output-creation — outputs now derived from filesystem"
835
- ```
836
-
837
- ---
838
-
839
- ## Task 8: Update status command + version bump
840
-
841
- **Files:**
842
- - Modify: `src/commands/project/status.js` — use derivePhase/deriveOutputs
843
- - Modify: `test/commands/status.test.js` — update assertions
844
- - Modify: `package.json` — version 4.4.0 → 4.5.0
845
-
846
- **Context:** `morph-spec status <feature>` currently reads `feature.phase` and `feature.outputs` from state.json. After Task 6 these fields don't exist. Update status to call `derivePhase()` and `deriveOutputs()` instead.
847
-
848
- **Step 1: Find where status.js reads phase and outputs**
849
-
850
- ```bash
851
- grep -n "\.phase\|\.outputs" src/commands/project/status.js
852
- ```
853
-
854
- **Step 2: Update status.js to use derive helpers**
855
-
856
- Import and use the new helpers:
857
-
858
- ```javascript
859
- import { loadState, derivePhase, deriveOutputs } from '../../core/state/state-manager.js';
860
- import { join } from 'path';
861
-
862
- // Where phase was read:
863
- // BEFORE: const phase = feature.phase;
864
- // AFTER:
865
- const featurePath = join(process.cwd(), '.morph', 'features', featureName);
866
- const phase = derivePhase(featurePath);
867
-
868
- // Where outputs were read:
869
- // BEFORE: const outputs = feature.outputs;
870
- // AFTER:
871
- const outputs = deriveOutputs(featureName);
872
- ```
873
-
874
- **Step 3: Run status tests**
875
-
876
- ```bash
877
- node --test test/commands/status.test.js
878
- ```
879
-
880
- Fix any failing assertions — they'll be about phase values or outputs shape. The shape of `deriveOutputs()` return value matches the old `feature.outputs` shape (`{ type: { created: boolean, path: string } }`), so display logic should need minimal changes.
881
-
882
- **Step 4: Bump version in package.json**
883
-
884
- Change `"version": "4.4.0"` to `"version": "4.5.0"`.
885
-
886
- **Step 5: Update version reference in framework/CLAUDE.md footer**
887
-
888
- Change `*MORPH-SPEC v4.4.0 by Polymorphism Tech*` to `*MORPH-SPEC v4.5.0 by Polymorphism Tech*`.
889
- (This file was already rewritten in Task 3 with v4.5.0, so just verify it's correct.)
890
-
891
- **Step 6: Run full suite — final verification**
892
-
893
- ```bash
894
- npm test
895
- ```
896
-
897
- Expected: all tests pass, 0 fail. Count may differ from original (some tests removed, some added).
898
-
899
- **Step 7: Final commit**
900
-
901
- ```bash
902
- git add src/commands/project/status.js test/commands/status.test.js package.json framework/CLAUDE.md
903
- git commit -m "feat(status): derive phase+outputs from filesystem; bump version to 4.5.0"
904
- ```
905
-
906
- ---
907
-
908
- ## Final Verification
909
-
910
- After all 8 tasks:
911
-
912
- ```bash
913
- npm test
914
- git log --oneline -8
915
- ```
916
-
917
- Expected log:
918
- ```
919
- feat(status): derive phase+outputs from filesystem; bump version to 4.5.0
920
- feat(hooks): remove track-output-creation — outputs now derived from filesystem
921
- feat(state): remove outputs+phase fields; derive from filesystem at runtime
922
- feat(standards): add TypeScript strict mode requirement; add user-stories template
923
- feat(rules): add Test File Policy to morph-workflow rule
924
- feat(claude-md): merge CLAUDE.md + CLAUDE_runtime.md into single unified file
925
- feat(rules): replace static content with @-imports from framework/standards
926
- feat(skills): install as subdirectory/SKILL.md — native Claude Code format
927
- ```