@polymorphism-tech/morph-spec 4.8.12 → 4.8.15

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 (76) hide show
  1. package/README.md +379 -379
  2. package/bin/morph-spec.js +23 -2
  3. package/bin/{task-manager.cjs → task-manager.js} +249 -172
  4. package/claude-plugin.json +14 -14
  5. package/docs/CHEATSHEET.md +203 -203
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/agents.json +224 -140
  8. package/framework/hooks/README.md +202 -202
  9. package/framework/hooks/claude-code/post-tool-use/dispatch.js +48 -2
  10. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +151 -0
  11. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +12 -0
  12. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +6 -0
  13. package/framework/hooks/claude-code/session-start/inject-morph-context.js +34 -0
  14. package/framework/hooks/claude-code/statusline.py +6 -0
  15. package/framework/hooks/claude-code/stop/validate-completion.js +38 -4
  16. package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +87 -0
  17. package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +58 -0
  18. package/framework/hooks/shared/phase-utils.js +4 -1
  19. package/framework/hooks/shared/state-reader.js +1 -0
  20. package/framework/skills/README.md +1 -0
  21. package/framework/skills/level-0-meta/brainstorming/SKILL.md +2 -0
  22. package/framework/skills/level-0-meta/code-review/SKILL.md +16 -0
  23. package/framework/skills/level-0-meta/code-review/references/review-guidelines.md +100 -0
  24. package/framework/skills/level-0-meta/code-review/scripts/scan-csharp.mjs +36 -6
  25. package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +16 -0
  26. package/framework/skills/level-0-meta/code-review-nextjs/scripts/scan-nextjs.mjs +189 -0
  27. package/framework/skills/level-0-meta/frontend-review/SKILL.md +359 -0
  28. package/framework/skills/level-0-meta/frontend-review/scripts/scan-accessibility.mjs +376 -0
  29. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +1 -1
  30. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +10 -8
  31. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +70 -0
  32. package/framework/skills/level-0-meta/post-implementation/SKILL.md +315 -0
  33. package/framework/skills/level-0-meta/post-implementation/scripts/detect-dev-server.mjs +153 -0
  34. package/framework/skills/level-0-meta/post-implementation/scripts/detect-stack.mjs +234 -0
  35. package/framework/skills/level-0-meta/terminal-title/SKILL.md +61 -0
  36. package/framework/skills/level-0-meta/terminal-title/scripts/set_title.sh +65 -0
  37. package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +50 -188
  38. package/framework/skills/level-0-meta/tool-usage-guide/references/tools-per-phase.md +213 -0
  39. package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +2 -0
  40. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +4 -7
  41. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +1 -1
  42. package/framework/skills/level-1-workflows/phase-design/SKILL.md +71 -109
  43. package/framework/skills/level-1-workflows/phase-design/references/architecture-analysis-guide.md +89 -0
  44. package/framework/skills/level-1-workflows/phase-design/references/spec-authoring-guide.md +55 -0
  45. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +171 -114
  46. package/framework/skills/level-1-workflows/phase-implement/references/vsa-implementation-guide.md +92 -0
  47. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +1 -2
  48. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +35 -159
  49. package/framework/skills/level-1-workflows/phase-tasks/references/task-planning-patterns.md +172 -0
  50. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +42 -3
  51. package/framework/squad-templates/backend-only.json +14 -1
  52. package/framework/squad-templates/frontend-only.json +14 -1
  53. package/framework/squad-templates/full-stack.json +25 -8
  54. package/framework/standards/STANDARDS.json +631 -86
  55. package/framework/standards/frontend/design-system/aesthetic-direction.md +213 -0
  56. package/framework/templates/project/validate.js +122 -0
  57. package/framework/workflows/configs/zero-touch.json +7 -0
  58. package/package.json +87 -87
  59. package/src/commands/agents/dispatch-agents.js +53 -10
  60. package/src/commands/state/advance-phase.js +88 -13
  61. package/src/commands/state/index.js +2 -1
  62. package/src/commands/state/phase-runner.js +215 -0
  63. package/src/commands/tasks/task.js +25 -4
  64. package/src/core/paths/output-schema.js +2 -1
  65. package/src/lib/detectors/design-system-detector.js +5 -4
  66. package/src/lib/generators/recap-generator.js +16 -0
  67. package/src/lib/orchestration/team-orchestrator.js +171 -89
  68. package/src/lib/phase-chain/eligibility-checker.js +243 -0
  69. package/src/lib/standards/digest-builder.js +231 -0
  70. package/src/lib/tasks/task-parser.js +94 -0
  71. package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +39 -0
  72. package/src/lib/validators/content/content-validator.js +34 -106
  73. package/src/lib/validators/nextjs/next-component-validator.js +2 -0
  74. package/src/lib/validators/validation-runner.js +2 -2
  75. package/src/utils/file-copier.js +1 -0
  76. package/src/utils/hooks-installer.js +31 -7
@@ -1,202 +1,202 @@
1
- # MORPH-SPEC Hooks Architecture (v2)
2
-
3
- Comprehensive hooks system for enforcing spec-driven development at the Claude Code level.
4
-
5
- ## Architecture
6
-
7
- ```
8
- framework/hooks/
9
- ├── claude-code/ # Claude Code native hooks
10
- │ ├── session-start/
11
- │ │ └── inject-morph-context.js # Inject state summary on session start
12
- │ ├── user-prompt/
13
- │ │ └── enrich-prompt.js # Context-aware prompt enrichment
14
- │ ├── pre-tool-use/
15
- │ │ ├── protect-spec-files.js # Block edits to approved spec artifacts
16
- │ │ └── enforce-phase-writes.js # Enforce writes to correct phase dir
17
- │ ├── post-tool-use/
18
- │ │ └── dispatch.js # Dispatch on CLI commands (auto-checkpoint)
19
- │ ├── stop/
20
- │ │ └── validate-completion.js # Advisory: warn about incomplete work
21
- │ ├── pre-compact/
22
- │ │ └── save-morph-context.js # Snapshot state before compaction
23
- │ └── notification/
24
- │ └── approval-reminder.js # Remind about pending approvals
25
- ├── shared/ # Reusable utilities for all hooks
26
- │ ├── state-reader.js # Read-only state.json accessor
27
- │ ├── phase-utils.js # Phase constants and path utilities
28
- │ ├── hook-response.js # JSON response builders
29
- │ └── stdin-reader.js # Stdin JSON reader
30
- ├── git/ # Git hooks (Bash)
31
- │ ├── pre-commit/
32
- │ │ ├── orchestrator.sh # Master hook dispatcher
33
- │ │ ├── agents.sh # Validates agents.json schema
34
- │ │ └── specs.sh # Validates spec.md sections
35
- │ ├── commit-msg/
36
- │ │ └── conventional-commits.sh # Enforces conventional commits
37
- │ └── pre-push/
38
- │ └── run-tests.sh # Runs test suite before push
39
- └── README.md # This file
40
- ```
41
-
42
- ## Hook Events
43
-
44
- | Event | Hook | Type | Purpose |
45
- |-------|------|------|---------|
46
- | **SessionStart** | inject-morph-context.js | Inject context | Shows active feature, phase, pending approvals |
47
- | **UserPromptSubmit** | enrich-prompt.js | Inject context | Warns about wrong-phase work, injects commands |
48
- | **PreToolUse** (Write\|Edit) | _(native permissions.deny)_ | Block | Blocks edits to state.json and .morph/framework/ |
49
- | **PreToolUse** (Write\|Edit) | protect-spec-files.js | Block | Blocks edits to spec files after approval |
50
- | **PreToolUse** (Write\|Edit) | enforce-phase-writes.js | Block | Ensures writes go to current phase directory |
51
- | **PreToolUse** (Bash) | _(prompt-type inline guard)_ | Block | Blocks `rm -rf .morph/` and direct state edits via Claude's reasoning |
52
- | **PostToolUse** (Bash) | dispatch.js | Dispatch | Triggers checkpoints on task completion |
53
- | **PostToolUseFailure** | handle-tool-failure.js | Logging | Appends structured JSON to .morph/logs/tool-failures.log |
54
- | **Stop** | validate-completion.js | Advisory | Warns about incomplete tasks/missing outputs/pending gates |
55
- | **PreCompact** | save-morph-context.js | Snapshot | Saves state to .morph/memory/ before compaction |
56
- | **Notification** | approval-reminder.js | Advisory | Reminds about pending approval gates |
57
-
58
- ## Design Principles
59
-
60
- 1. **Fail-open**: All hooks catch exceptions and `exit 0` — never accidentally block legitimate work
61
- 2. **Non-morph projects**: Every hook checks for `.morph/state.json` first and exits silently if missing
62
- 3. **Performance**: PreToolUse hooks use synchronous state reads for <100ms execution
63
- 4. **Cross-platform**: All hooks use `path.join()`/`path.resolve()`, no hardcoded path separators
64
- 5. **Node.js only**: All hooks use `node` as executor (no PowerShell/bash dependency)
65
-
66
- ## Installation
67
-
68
- Hooks are automatically installed by `morph-spec init` and updated by `morph-spec update`.
69
-
70
- During init/update, the entire `framework/hooks/` directory is copied to `.morph/framework/hooks/`.
71
- Hook commands in `.claude/settings.local.json` reference `$CLAUDE_PROJECT_DIR/.morph/framework/hooks/`
72
- so they work correctly in any project regardless of how morph-spec was installed.
73
-
74
- The installer writes to `.claude/settings.local.json`:
75
-
76
- ```json
77
- {
78
- "hooks": {
79
- "SessionStart": [{ "matcher": "startup|resume|compact", "hooks": [...] }],
80
- "UserPromptSubmit": [{ "hooks": [...] }],
81
- "PreToolUse": [
82
- { "matcher": "Write|Edit", "hooks": [...] },
83
- { "matcher": "Bash", "hooks": [...] }
84
- ],
85
- "PostToolUse": [
86
- { "matcher": "Bash", "hooks": [...] }
87
- ],
88
- "Stop": [{ "hooks": [...] }],
89
- "PreCompact": [{ "hooks": [...] }],
90
- "Notification": [{ "matcher": "idle_prompt", "hooks": [...] }]
91
- }
92
- }
93
- ```
94
-
95
- ### Git Hooks
96
-
97
- ```bash
98
- # In your project root
99
- cd .git/hooks
100
- ln -sf ../../framework/hooks/git/pre-commit/orchestrator.sh pre-commit
101
- ln -sf ../../framework/hooks/git/commit-msg/conventional-commits.sh commit-msg
102
- ln -sf ../../framework/hooks/git/pre-push/run-tests.sh pre-push
103
- chmod +x pre-commit commit-msg pre-push
104
- ```
105
-
106
- ## Shared Utilities
107
-
108
- All Claude Code hooks import from `framework/hooks/shared/`:
109
-
110
- | Module | Purpose |
111
- |--------|---------|
112
- | `state-reader.js` | `loadState()`, `getActiveFeature()`, `getFeaturePhase()`, `isGateApproved()`, `getPendingGates()`, `getMissingOutputs()` |
113
- | `phase-utils.js` | Phase constants, path extraction, file classification |
114
- | `hook-response.js` | `block(reason)`, `approve(context)`, `injectContext(text)`, `pass()` |
115
- | `stdin-reader.js` | `readStdin()` — Promise-based stdin JSON reader |
116
-
117
- ## How Hooks Work
118
-
119
- ### PreToolUse (Write|Edit) Flow
120
-
121
- ```
122
- Claude calls Write/Edit tool
123
-
124
- Claude Code sends JSON to stdin: { tool_input: { file_path: "..." } }
125
-
126
- [native permissions.deny]
127
- ├── Is .morph/state.json? → BLOCK (use CLI)
128
- ├── Is .morph/framework/**? → BLOCK (read-only)
129
- └── Other → continue
130
-
131
- protect-spec-files.js
132
- ├── Is in .morph/features/{feature}/?
133
- │ ├── Is spec.md and design gate approved? → BLOCK
134
- │ ├── Is tasks.md and tasks gate approved? → BLOCK
135
- │ └── Gate not approved → pass
136
- └── Not a feature file → pass
137
-
138
- enforce-phase-writes.js
139
- ├── Is in .morph/features/{feature}/?
140
- │ ├── Phase is implement → pass (unrestricted)
141
- │ ├── Target dir matches phase dir → pass
142
- │ └── Target dir doesn't match → BLOCK
143
- └── Not a feature file → pass
144
- ```
145
-
146
- ### SessionStart Flow
147
-
148
- ```
149
- Session starts/resumes/compacts
150
-
151
- inject-morph-context.js
152
- ├── No state.json → silent exit
153
- ├── Has active feature → inject summary:
154
- │ "MORPH-SPEC Status:
155
- │ - Active feature: my-feature (phase: implement)
156
- │ - Tasks: 3/10 completed
157
- │ - Pending approvals: design, tasks"
158
- └── No active feature → inject feature list
159
- ```
160
-
161
- ## Testing
162
-
163
- ```bash
164
- # Run hook tests
165
- npm test -- test/hooks/
166
-
167
- # Run shared utilities tests
168
- npm test -- test/hooks/shared-utils.test.js
169
-
170
- # Run installer tests
171
- npm test -- test/hooks/hooks-installer.test.js
172
- ```
173
-
174
- ## Troubleshooting
175
-
176
- ### Hooks Not Running
177
-
178
- ```bash
179
- # Check if hooks are installed
180
- cat .claude/settings.local.json | jq '.hooks'
181
-
182
- # Reinstall hooks
183
- morph-spec update
184
- ```
185
-
186
- ### Hook Blocking Legitimate Work
187
-
188
- All hooks are fail-open. If a hook is incorrectly blocking:
189
-
190
- 1. The hook catches its own errors and exits 0
191
- 2. If truly stuck, remove the specific hook from `.claude/settings.local.json`
192
- 3. Report the issue so the hook logic can be fixed
193
-
194
- ### Resetting All Morph Hooks
195
-
196
- ```bash
197
- morph-spec doctor --reset
198
- ```
199
-
200
- ---
201
-
202
- *MORPH-SPEC by Polymorphism Tech — Hooks Architecture v2.5*
1
+ # MORPH-SPEC Hooks Architecture (v2)
2
+
3
+ Comprehensive hooks system for enforcing spec-driven development at the Claude Code level.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ framework/hooks/
9
+ ├── claude-code/ # Claude Code native hooks
10
+ │ ├── session-start/
11
+ │ │ └── inject-morph-context.js # Inject state summary on session start
12
+ │ ├── user-prompt/
13
+ │ │ └── enrich-prompt.js # Context-aware prompt enrichment
14
+ │ ├── pre-tool-use/
15
+ │ │ ├── protect-spec-files.js # Block edits to approved spec artifacts
16
+ │ │ └── enforce-phase-writes.js # Enforce writes to correct phase dir
17
+ │ ├── post-tool-use/
18
+ │ │ └── dispatch.js # Dispatch on CLI commands (auto-checkpoint)
19
+ │ ├── stop/
20
+ │ │ └── validate-completion.js # Advisory: warn about incomplete work
21
+ │ ├── pre-compact/
22
+ │ │ └── save-morph-context.js # Snapshot state before compaction
23
+ │ └── notification/
24
+ │ └── approval-reminder.js # Remind about pending approvals
25
+ ├── shared/ # Reusable utilities for all hooks
26
+ │ ├── state-reader.js # Read-only state.json accessor
27
+ │ ├── phase-utils.js # Phase constants and path utilities
28
+ │ ├── hook-response.js # JSON response builders
29
+ │ └── stdin-reader.js # Stdin JSON reader
30
+ ├── git/ # Git hooks (Bash)
31
+ │ ├── pre-commit/
32
+ │ │ ├── orchestrator.sh # Master hook dispatcher
33
+ │ │ ├── agents.sh # Validates agents.json schema
34
+ │ │ └── specs.sh # Validates spec.md sections
35
+ │ ├── commit-msg/
36
+ │ │ └── conventional-commits.sh # Enforces conventional commits
37
+ │ └── pre-push/
38
+ │ └── run-tests.sh # Runs test suite before push
39
+ └── README.md # This file
40
+ ```
41
+
42
+ ## Hook Events
43
+
44
+ | Event | Hook | Type | Purpose |
45
+ |-------|------|------|---------|
46
+ | **SessionStart** | inject-morph-context.js | Inject context | Shows active feature, phase, pending approvals |
47
+ | **UserPromptSubmit** | enrich-prompt.js | Inject context | Warns about wrong-phase work, injects commands |
48
+ | **PreToolUse** (Write\|Edit) | _(native permissions.deny)_ | Block | Blocks edits to state.json and .morph/framework/ |
49
+ | **PreToolUse** (Write\|Edit) | protect-spec-files.js | Block | Blocks edits to spec files after approval |
50
+ | **PreToolUse** (Write\|Edit) | enforce-phase-writes.js | Block | Ensures writes go to current phase directory |
51
+ | **PreToolUse** (Bash) | _(prompt-type inline guard)_ | Block | Blocks `rm -rf .morph/` and direct state edits via Claude's reasoning |
52
+ | **PostToolUse** (Bash) | dispatch.js | Dispatch | Triggers checkpoints on task completion |
53
+ | **PostToolUseFailure** | handle-tool-failure.js | Logging | Appends structured JSON to .morph/logs/tool-failures.log |
54
+ | **Stop** | validate-completion.js | Advisory | Warns about incomplete tasks/missing outputs/pending gates |
55
+ | **PreCompact** | save-morph-context.js | Snapshot | Saves state to .morph/memory/ before compaction |
56
+ | **Notification** | approval-reminder.js | Advisory | Reminds about pending approval gates |
57
+
58
+ ## Design Principles
59
+
60
+ 1. **Fail-open**: All hooks catch exceptions and `exit 0` — never accidentally block legitimate work
61
+ 2. **Non-morph projects**: Every hook checks for `.morph/state.json` first and exits silently if missing
62
+ 3. **Performance**: PreToolUse hooks use synchronous state reads for <100ms execution
63
+ 4. **Cross-platform**: All hooks use `path.join()`/`path.resolve()`, no hardcoded path separators
64
+ 5. **Node.js only**: All hooks use `node` as executor (no PowerShell/bash dependency)
65
+
66
+ ## Installation
67
+
68
+ Hooks are automatically installed by `morph-spec init` and updated by `morph-spec update`.
69
+
70
+ During init/update, the entire `framework/hooks/` directory is copied to `.morph/framework/hooks/`.
71
+ Hook commands in `.claude/settings.local.json` reference `$CLAUDE_PROJECT_DIR/.morph/framework/hooks/`
72
+ so they work correctly in any project regardless of how morph-spec was installed.
73
+
74
+ The installer writes to `.claude/settings.local.json`:
75
+
76
+ ```json
77
+ {
78
+ "hooks": {
79
+ "SessionStart": [{ "matcher": "startup|resume|compact", "hooks": [...] }],
80
+ "UserPromptSubmit": [{ "hooks": [...] }],
81
+ "PreToolUse": [
82
+ { "matcher": "Write|Edit", "hooks": [...] },
83
+ { "matcher": "Bash", "hooks": [...] }
84
+ ],
85
+ "PostToolUse": [
86
+ { "matcher": "Bash", "hooks": [...] }
87
+ ],
88
+ "Stop": [{ "hooks": [...] }],
89
+ "PreCompact": [{ "hooks": [...] }],
90
+ "Notification": [{ "matcher": "idle_prompt", "hooks": [...] }]
91
+ }
92
+ }
93
+ ```
94
+
95
+ ### Git Hooks
96
+
97
+ ```bash
98
+ # In your project root
99
+ cd .git/hooks
100
+ ln -sf ../../framework/hooks/git/pre-commit/orchestrator.sh pre-commit
101
+ ln -sf ../../framework/hooks/git/commit-msg/conventional-commits.sh commit-msg
102
+ ln -sf ../../framework/hooks/git/pre-push/run-tests.sh pre-push
103
+ chmod +x pre-commit commit-msg pre-push
104
+ ```
105
+
106
+ ## Shared Utilities
107
+
108
+ All Claude Code hooks import from `framework/hooks/shared/`:
109
+
110
+ | Module | Purpose |
111
+ |--------|---------|
112
+ | `state-reader.js` | `loadState()`, `getActiveFeature()`, `getFeaturePhase()`, `isGateApproved()`, `getPendingGates()`, `getMissingOutputs()` |
113
+ | `phase-utils.js` | Phase constants, path extraction, file classification |
114
+ | `hook-response.js` | `block(reason)`, `approve(context)`, `injectContext(text)`, `pass()` |
115
+ | `stdin-reader.js` | `readStdin()` — Promise-based stdin JSON reader |
116
+
117
+ ## How Hooks Work
118
+
119
+ ### PreToolUse (Write|Edit) Flow
120
+
121
+ ```
122
+ Claude calls Write/Edit tool
123
+
124
+ Claude Code sends JSON to stdin: { tool_input: { file_path: "..." } }
125
+
126
+ [native permissions.deny]
127
+ ├── Is .morph/state.json? → BLOCK (use CLI)
128
+ ├── Is .morph/framework/**? → BLOCK (read-only)
129
+ └── Other → continue
130
+
131
+ protect-spec-files.js
132
+ ├── Is in .morph/features/{feature}/?
133
+ │ ├── Is spec.md and design gate approved? → BLOCK
134
+ │ ├── Is tasks.md and tasks gate approved? → BLOCK
135
+ │ └── Gate not approved → pass
136
+ └── Not a feature file → pass
137
+
138
+ enforce-phase-writes.js
139
+ ├── Is in .morph/features/{feature}/?
140
+ │ ├── Phase is implement → pass (unrestricted)
141
+ │ ├── Target dir matches phase dir → pass
142
+ │ └── Target dir doesn't match → BLOCK
143
+ └── Not a feature file → pass
144
+ ```
145
+
146
+ ### SessionStart Flow
147
+
148
+ ```
149
+ Session starts/resumes/compacts
150
+
151
+ inject-morph-context.js
152
+ ├── No state.json → silent exit
153
+ ├── Has active feature → inject summary:
154
+ │ "MORPH-SPEC Status:
155
+ │ - Active feature: my-feature (phase: implement)
156
+ │ - Tasks: 3/10 completed
157
+ │ - Pending approvals: design, tasks"
158
+ └── No active feature → inject feature list
159
+ ```
160
+
161
+ ## Testing
162
+
163
+ ```bash
164
+ # Run hook tests
165
+ npm test -- test/hooks/
166
+
167
+ # Run shared utilities tests
168
+ npm test -- test/hooks/shared-utils.test.js
169
+
170
+ # Run installer tests
171
+ npm test -- test/hooks/hooks-installer.test.js
172
+ ```
173
+
174
+ ## Troubleshooting
175
+
176
+ ### Hooks Not Running
177
+
178
+ ```bash
179
+ # Check if hooks are installed
180
+ cat .claude/settings.local.json | jq '.hooks'
181
+
182
+ # Reinstall hooks
183
+ morph-spec update
184
+ ```
185
+
186
+ ### Hook Blocking Legitimate Work
187
+
188
+ All hooks are fail-open. If a hook is incorrectly blocking:
189
+
190
+ 1. The hook catches its own errors and exits 0
191
+ 2. If truly stuck, remove the specific hook from `.claude/settings.local.json`
192
+ 3. Report the issue so the hook logic can be fixed
193
+
194
+ ### Resetting All Morph Hooks
195
+
196
+ ```bash
197
+ morph-spec doctor --reset
198
+ ```
199
+
200
+ ---
201
+
202
+ *MORPH-SPEC by Polymorphism Tech — Hooks Architecture v2.5*
@@ -14,9 +14,11 @@
14
14
  */
15
15
 
16
16
  import { execSync } from 'child_process';
17
+ import { readFileSync, existsSync } from 'fs';
18
+ import { resolve } from 'path';
17
19
  import { readStdin } from '../../shared/stdin-reader.js';
18
20
  import { stateExists, getFeature } from '../../shared/state-reader.js';
19
- import { pass } from '../../shared/hook-response.js';
21
+ import { pass, injectContext } from '../../shared/hook-response.js';
20
22
 
21
23
  try {
22
24
  if (!stateExists()) pass();
@@ -59,7 +61,8 @@ function dispatch(command) {
59
61
  // morph-spec phase advance <feature>
60
62
  const phaseAdvanceMatch = command.match(/morph-spec\s+phase\s+advance\s+(\S+)/);
61
63
  if (phaseAdvanceMatch) {
62
- // Phase advancement hooks are handled internally by advance-phase.js
64
+ const [, featureName] = phaseAdvanceMatch;
65
+ evaluatePhaseChain(featureName);
63
66
  pass();
64
67
  }
65
68
 
@@ -74,6 +77,49 @@ function dispatch(command) {
74
77
  pass();
75
78
  }
76
79
 
80
+ /**
81
+ * Evaluate whether the phase chain should auto-continue after a phase advance.
82
+ * Injects a `morph-spec phase run` instruction when the workflow has phaseChain.enabled.
83
+ * Silently passes when workflow doesn't support chaining or feature is not found.
84
+ *
85
+ * @param {string} featureName
86
+ */
87
+ function evaluatePhaseChain(featureName) {
88
+ try {
89
+ const feature = getFeature(featureName);
90
+ if (!feature) return;
91
+
92
+ const workflowId = feature.workflow;
93
+ if (!workflowId || workflowId === 'auto') return;
94
+
95
+ // Load workflow config to check phaseChain.enabled
96
+ const configPath = resolve(
97
+ process.cwd(),
98
+ `.morph/framework/workflows/configs/${workflowId}.json`
99
+ );
100
+ if (!existsSync(configPath)) return;
101
+
102
+ let workflowConfig;
103
+ try {
104
+ workflowConfig = JSON.parse(readFileSync(configPath, 'utf8'));
105
+ } catch {
106
+ return;
107
+ }
108
+
109
+ if (!workflowConfig?.phaseChain?.enabled) return;
110
+
111
+ // Inject instruction to continue the phase chain
112
+ injectContext(
113
+ `Phase advance complete. Continue the phase chain:\n` +
114
+ ` morph-spec phase run ${featureName}\n\n` +
115
+ `The phase runner will check eligibility and auto-advance if all gates pass. ` +
116
+ `It will pause automatically on blocked tasks, low pass rate, or trust gates.`
117
+ );
118
+ } catch {
119
+ // Non-blocking — fail silently
120
+ }
121
+ }
122
+
77
123
  function run(cmd) {
78
124
  try {
79
125
  execSync(cmd, { stdio: 'pipe', cwd: process.cwd(), timeout: 30000 });
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PostToolUse Hook: Validator Feedback
5
+ *
6
+ * Event: PostToolUse | Matcher: Bash
7
+ *
8
+ * Fires after `morph-spec task done` completes.
9
+ * Reads validationHistory for the completed task and injects
10
+ * remediation context when validation failed.
11
+ *
12
+ * This closes the Builder-Validator loop:
13
+ * task done → runValidation → validationHistory → hook injects context
14
+ * → LLM sees remediation prompt before next step → auto-fix attempt
15
+ *
16
+ * Fail-open: exits 0 on any error.
17
+ */
18
+
19
+ import { readFileSync, existsSync } from 'fs';
20
+ import { join } from 'path';
21
+ import { readStdin } from '../../shared/stdin-reader.js';
22
+ import { stateExists } from '../../shared/state-reader.js';
23
+ import { injectContext, pass } from '../../shared/hook-response.js';
24
+
25
+ // Standard IDs referenced in remediation messages
26
+ const STANDARD_REFS = {
27
+ 'di': 'backend/dotnet/core.md#dependency-injection',
28
+ 'async': 'backend/dotnet/async.md#cancellation-token-pattern',
29
+ 'security': 'core/architecture.md#security-patterns',
30
+ 'packages': 'backend/dotnet/program-cs-checklist.md#nuget-packages',
31
+ 'design-system': 'frontend/design-system/naming.md#css-class-conventions',
32
+ 'blazor': 'frontend/blazor/pitfalls.md#dbcontext-in-blazor',
33
+ };
34
+
35
+ try {
36
+ if (!stateExists()) pass();
37
+
38
+ const payload = await readStdin();
39
+ if (!payload) pass();
40
+
41
+ const command = payload?.tool_input?.command || '';
42
+ if (!command) pass();
43
+
44
+ // Match: morph-spec task done <feature> <taskId>
45
+ const taskDoneMatch = command.match(/morph-spec\s+task\s+done\s+(\S+)\s+(\S+)/);
46
+ if (!taskDoneMatch) pass();
47
+
48
+ const [, featureName, taskId] = taskDoneMatch;
49
+
50
+ // Load state and find validationHistory for this task
51
+ const statePath = join(process.cwd(), '.morph', 'state.json');
52
+ if (!existsSync(statePath)) pass();
53
+
54
+ let state;
55
+ try {
56
+ state = JSON.parse(readFileSync(statePath, 'utf8'));
57
+ } catch {
58
+ pass();
59
+ }
60
+
61
+ const feature = state?.features?.[featureName];
62
+ if (!feature) pass();
63
+
64
+ const taskHistory = feature.validationHistory?.[taskId];
65
+ if (!taskHistory) pass();
66
+
67
+ // Only act on failed validations (passed = no feedback needed)
68
+ if (taskHistory.status === 'passed') pass();
69
+
70
+ // Build remediation context based on validation issues
71
+ buildRemediationContext(featureName, taskId, taskHistory);
72
+ } catch {
73
+ // Fail-open
74
+ process.exit(0);
75
+ }
76
+
77
+ /**
78
+ * Build and inject remediation context for a failed validation.
79
+ *
80
+ * @param {string} featureName
81
+ * @param {string} taskId
82
+ * @param {Object} taskHistory - validationHistory[taskId]
83
+ */
84
+ function buildRemediationContext(featureName, taskId, taskHistory) {
85
+ const attempt = taskHistory.attempt || 1;
86
+ const validators = taskHistory.validators || {};
87
+
88
+ // Collect all issues across all validators
89
+ const allIssues = [];
90
+ for (const [validatorName, result] of Object.entries(validators)) {
91
+ if (result.passed) continue;
92
+ const issues = result.issues || [];
93
+ for (const issue of issues) {
94
+ allIssues.push({ validator: validatorName, ...issue });
95
+ }
96
+ }
97
+
98
+ if (allIssues.length === 0 && taskHistory.status !== 'blocked') pass();
99
+
100
+ const lines = [];
101
+
102
+ if (taskHistory.status === 'blocked') {
103
+ // Max attempts reached — escalation message
104
+ lines.push(`⛔ ESCALATION REQUIRED — Task ${taskId} in feature '${featureName}'`);
105
+ lines.push(` Validation failed ${attempt} times (max attempts reached).`);
106
+ lines.push(` Human review required before proceeding.`);
107
+ lines.push(` Last failures:`);
108
+ for (const issue of allIssues.slice(0, 5)) {
109
+ lines.push(` • [${issue.validator}] ${issue.message} ${issue.file ? `(${issue.file}${issue.line ? ':' + issue.line : ''})` : ''}`);
110
+ }
111
+ } else {
112
+ // Active remediation — attempt N of 3
113
+ lines.push(`🔄 REMEDIATION REQUIRED — Task ${taskId} (attempt ${attempt}/3)`);
114
+ lines.push(` Validation failed. Fix the following issues and re-run:`);
115
+ lines.push(` morph-spec task done ${featureName} ${taskId}`);
116
+ lines.push(``);
117
+
118
+ for (const issue of allIssues) {
119
+ const refKey = getRefKey(issue.rule || issue.validator);
120
+ const ref = refKey ? ` — Ref: ${STANDARD_REFS[refKey] || refKey}` : '';
121
+ const location = issue.file
122
+ ? ` (${issue.file}${issue.line ? ':' + issue.line : ''})`
123
+ : '';
124
+ lines.push(` • [${issue.validator}] ${issue.message}${location}${ref}`);
125
+ }
126
+
127
+ if (attempt >= 2) {
128
+ lines.push(``);
129
+ lines.push(` ⚠️ If this fails again, it will be escalated (attempt ${attempt + 1} = final).`);
130
+ }
131
+ }
132
+
133
+ injectContext(lines.join('\n'));
134
+ }
135
+
136
+ /**
137
+ * Map a rule/validator name to a STANDARD_REFS key.
138
+ * @param {string} ruleOrValidator
139
+ * @returns {string|null}
140
+ */
141
+ function getRefKey(ruleOrValidator) {
142
+ if (!ruleOrValidator) return null;
143
+ const lower = ruleOrValidator.toLowerCase();
144
+ if (lower.includes('di') || lower.includes('dependency') || lower.includes('injection')) return 'di';
145
+ if (lower.includes('async') || lower.includes('await') || lower.includes('cancellation')) return 'async';
146
+ if (lower.includes('security') || lower.includes('sql') || lower.includes('xss')) return 'security';
147
+ if (lower.includes('package') || lower.includes('nuget')) return 'packages';
148
+ if (lower.includes('design') || lower.includes('css') || lower.includes('naming')) return 'design-system';
149
+ if (lower.includes('blazor') || lower.includes('dbcontext')) return 'blazor';
150
+ return null;
151
+ }
@@ -17,6 +17,8 @@
17
17
  * Fail-open: exits 0 on any error.
18
18
  */
19
19
 
20
+ import { existsSync } from 'fs';
21
+ import { resolve } from 'path';
20
22
  import { readStdin } from '../../shared/stdin-reader.js';
21
23
  import { stateExists, getFeaturePhase } from '../../shared/state-reader.js';
22
24
  import {
@@ -28,6 +30,12 @@ import {
28
30
  import { block, pass } from '../../shared/hook-response.js';
29
31
 
30
32
  try {
33
+ // Amend mode: bypass phase write enforcement for legitimate corrections
34
+ if (process.env.MORPH_AMEND_PHASE) {
35
+ console.error(`[morph-spec] AMEND MODE: bypassing phase protection for phase '${process.env.MORPH_AMEND_PHASE}'`);
36
+ pass();
37
+ }
38
+
31
39
  if (!stateExists()) pass();
32
40
 
33
41
  const payload = await readStdin();
@@ -55,6 +63,10 @@ try {
55
63
  if (!allowedDir) pass(); // Unknown phase — allow
56
64
 
57
65
  if (targetDir !== allowedDir) {
66
+ // Allow editing already-existing files from previous phases
67
+ const absolutePath = resolve(process.cwd(), filePath);
68
+ if (existsSync(absolutePath)) pass(); // editing existing file — allow
69
+
58
70
  const phaseLabel = phase.toUpperCase();
59
71
  block(
60
72
  `MORPH-SPEC: Writing to '${targetDir}/' is not allowed during ${phaseLabel} phase.\n` +
@@ -24,6 +24,12 @@ import {
24
24
  import { block, pass } from '../../shared/hook-response.js';
25
25
 
26
26
  try {
27
+ // Amend mode: bypass spec protection for legitimate in-implementation corrections
28
+ if (process.env.MORPH_AMEND_PHASE) {
29
+ console.error(`[morph-spec] AMEND MODE: bypassing spec protection for phase '${process.env.MORPH_AMEND_PHASE}'`);
30
+ pass();
31
+ }
32
+
27
33
  if (!stateExists()) pass();
28
34
 
29
35
  const payload = await readStdin();