@polymorphism-tech/morph-spec 4.8.19 → 4.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/CLAUDE.md +21 -0
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +44 -55
  4. package/bin/task-manager.js +133 -20
  5. package/bin/validate.js +67 -33
  6. package/claude-plugin.json +1 -1
  7. package/docs/CHEATSHEET.md +201 -203
  8. package/docs/QUICKSTART.md +2 -2
  9. package/framework/CLAUDE.md +99 -77
  10. package/framework/agents.json +734 -182
  11. package/framework/commands/commit.md +166 -0
  12. package/framework/commands/morph-apply.md +13 -2
  13. package/framework/commands/morph-archive.md +8 -2
  14. package/framework/commands/morph-infra.md +6 -0
  15. package/framework/commands/morph-preflight.md +6 -0
  16. package/framework/commands/morph-proposal.md +56 -7
  17. package/framework/commands/morph-status.md +6 -0
  18. package/framework/commands/morph-troubleshoot.md +6 -0
  19. package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
  20. package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
  21. package/framework/hooks/claude-code/post-tool-use/dispatch.js +155 -32
  22. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +78 -0
  23. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +8 -17
  24. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +16 -3
  25. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +4 -3
  26. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +4 -3
  27. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +60 -0
  28. package/framework/hooks/claude-code/session-start/inject-morph-context.js +124 -2
  29. package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
  30. package/framework/hooks/claude-code/statusline.py +76 -30
  31. package/framework/hooks/claude-code/stop/validate-completion.js +2 -15
  32. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +23 -5
  33. package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
  34. package/framework/hooks/shared/activity-logger.js +0 -24
  35. package/framework/hooks/shared/compact-restore.js +100 -0
  36. package/framework/hooks/shared/dispatch-helpers.js +116 -0
  37. package/framework/hooks/shared/phase-utils.js +12 -5
  38. package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
  39. package/framework/hooks/shared/stale-task-reset.js +57 -0
  40. package/framework/hooks/shared/state-reader.js +29 -5
  41. package/framework/hooks/shared/worktree-helpers.js +53 -0
  42. package/framework/phases.json +69 -14
  43. package/framework/rules/morph-workflow.md +88 -86
  44. package/framework/skills/level-0-meta/mcp-registry.json +86 -51
  45. package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/SKILL.md +14 -17
  46. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
  47. package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +2 -2
  48. package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +163 -163
  49. package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/SKILL.md +9 -9
  50. package/framework/skills/level-0-meta/morph-init/SKILL.md +77 -12
  51. package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/SKILL.md +62 -15
  52. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +5 -5
  53. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
  54. package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +1 -1
  55. package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/SKILL.md +2 -2
  56. package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +3 -4
  57. package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +7 -7
  58. package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +2 -2
  59. package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/scripts/check-phase-outputs.mjs +2 -2
  60. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +238 -0
  61. package/framework/skills/level-1-workflows/{phase-codebase-analysis → morph-phase-codebase-analysis}/SKILL.md +3 -3
  62. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +507 -0
  63. package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/SKILL.md +168 -27
  64. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +50 -0
  65. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +45 -0
  66. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +47 -0
  67. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +254 -0
  68. package/framework/skills/level-1-workflows/{phase-setup → morph-phase-setup}/SKILL.md +50 -3
  69. package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/SKILL.md +48 -11
  70. package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
  71. package/framework/skills/level-1-workflows/{phase-uiux → morph-phase-uiux}/SKILL.md +46 -11
  72. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +97 -0
  73. package/framework/standards/STANDARDS.json +640 -88
  74. package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
  75. package/framework/standards/integration/mcp/mcp-tools.md +25 -7
  76. package/framework/templates/REGISTRY.json +1825 -1909
  77. package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
  78. package/framework/templates/docs/onboarding.md +3 -7
  79. package/package.json +2 -7
  80. package/src/commands/agents/dispatch-agents.js +104 -6
  81. package/src/commands/mcp/mcp-setup.js +39 -2
  82. package/src/commands/phase/phase-reset.js +74 -0
  83. package/src/commands/project/doctor.js +34 -51
  84. package/src/commands/project/init.js +1 -1
  85. package/src/commands/project/status.js +2 -2
  86. package/src/commands/project/update.js +381 -365
  87. package/src/commands/project/worktree.js +154 -0
  88. package/src/commands/scope/escalate.js +215 -0
  89. package/src/commands/state/advance-phase.js +132 -68
  90. package/src/commands/state/approve.js +2 -2
  91. package/src/commands/state/index.js +7 -8
  92. package/src/commands/state/phase-runner.js +1 -1
  93. package/src/commands/state/state.js +61 -6
  94. package/src/commands/task/expand.js +100 -0
  95. package/src/commands/tasks/task.js +78 -99
  96. package/src/commands/templates/template-render.js +93 -173
  97. package/src/commands/trust/trust.js +26 -21
  98. package/src/core/paths/output-schema.js +19 -3
  99. package/src/core/state/phase-state-machine.js +7 -4
  100. package/src/core/state/state-manager.js +32 -57
  101. package/src/core/workflows/workflow-detector.js +9 -87
  102. package/src/lib/detectors/claude-config-detector.js +93 -347
  103. package/src/lib/detectors/design-system-detector.js +189 -189
  104. package/src/lib/detectors/index.js +155 -57
  105. package/src/lib/generators/context-generator.js +2 -2
  106. package/src/lib/installers/mcp-installer.js +37 -5
  107. package/src/lib/phase-chain/phase-validator.js +336 -0
  108. package/src/lib/scope/impact-analyzer.js +106 -0
  109. package/src/lib/stack/stack-profile.js +88 -0
  110. package/src/lib/tasks/task-classifier.js +16 -0
  111. package/src/lib/tasks/task-parser.js +1 -1
  112. package/src/lib/tasks/test-runner.js +77 -0
  113. package/src/lib/trust/trust-manager.js +32 -144
  114. package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
  115. package/src/lib/validators/spec-validator.js +58 -4
  116. package/src/lib/validators/validation-runner.js +23 -11
  117. package/src/scripts/setup-infra.js +255 -224
  118. package/src/utils/agents-installer.js +34 -14
  119. package/src/utils/banner.js +1 -1
  120. package/src/utils/claude-settings-manager.js +1 -1
  121. package/src/utils/file-copier.js +1 -1
  122. package/src/utils/hooks-installer.js +272 -8
  123. package/framework/hooks/dev/check-sync-health.js +0 -117
  124. package/framework/hooks/dev/guard-version-numbers.js +0 -57
  125. package/framework/hooks/dev/sync-standards-registry.js +0 -60
  126. package/framework/hooks/dev/sync-template-registry.js +0 -60
  127. package/framework/hooks/dev/validate-skill-format.js +0 -70
  128. package/framework/hooks/dev/validate-standard-format.js +0 -73
  129. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +0 -190
  130. package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -366
  131. package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
  132. package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
  133. package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
  134. package/framework/workflows/configs/design-impl.json +0 -49
  135. package/framework/workflows/configs/express.json +0 -45
  136. package/framework/workflows/configs/fast-track.json +0 -42
  137. package/framework/workflows/configs/full-morph.json +0 -79
  138. package/framework/workflows/configs/fusion.json +0 -39
  139. package/framework/workflows/configs/long-running.json +0 -33
  140. package/framework/workflows/configs/spec-only.json +0 -43
  141. package/framework/workflows/configs/ui-refresh.json +0 -49
  142. package/framework/workflows/configs/zero-touch.json +0 -82
  143. package/src/commands/project/index.js +0 -8
  144. package/src/commands/project/monitor.js +0 -295
  145. package/src/commands/project/tutorial.js +0 -115
  146. package/src/commands/state/validate-phase.js +0 -238
  147. package/src/commands/templates/generate-contracts.js +0 -445
  148. package/src/core/index.js +0 -10
  149. package/src/core/orchestrator.js +0 -171
  150. package/src/core/registry/command-registry.js +0 -28
  151. package/src/core/registry/index.js +0 -8
  152. package/src/core/registry/validator-registry.js +0 -204
  153. package/src/core/state/index.js +0 -8
  154. package/src/core/templates/index.js +0 -9
  155. package/src/core/templates/template-data-sources.js +0 -325
  156. package/src/core/templates/template-validator.js +0 -296
  157. package/src/core/workflows/index.js +0 -7
  158. package/src/generator/config-generator.js +0 -206
  159. package/src/generator/templates/config.json.template +0 -40
  160. package/src/generator/templates/project.md.template +0 -67
  161. package/src/lib/agents/micro-agent-factory.js +0 -161
  162. package/src/lib/analysis/complexity-analyzer.js +0 -441
  163. package/src/lib/analysis/index.js +0 -7
  164. package/src/lib/analytics/analytics-engine.js +0 -345
  165. package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
  166. package/src/lib/checkpoints/index.js +0 -7
  167. package/src/lib/context/context-bundler.js +0 -241
  168. package/src/lib/context/context-optimizer.js +0 -212
  169. package/src/lib/context/context-tracker.js +0 -273
  170. package/src/lib/context/core-four-tracker.js +0 -201
  171. package/src/lib/context/mcp-optimizer.js +0 -200
  172. package/src/lib/detectors/config-detector.js +0 -223
  173. package/src/lib/detectors/standards-generator.js +0 -335
  174. package/src/lib/detectors/structure-detector.js +0 -275
  175. package/src/lib/execution/fusion-executor.js +0 -304
  176. package/src/lib/execution/parallel-executor.js +0 -270
  177. package/src/lib/hooks/stop-hook-executor.js +0 -286
  178. package/src/lib/hops/hop-composer.js +0 -221
  179. package/src/lib/monitor/agent-resolver.js +0 -144
  180. package/src/lib/monitor/renderer.js +0 -230
  181. package/src/lib/orchestration/index.js +0 -7
  182. package/src/lib/orchestration/team-orchestrator.js +0 -404
  183. package/src/lib/phase-chain/eligibility-checker.js +0 -243
  184. package/src/lib/threads/thread-coordinator.js +0 -238
  185. package/src/lib/threads/thread-manager.js +0 -317
  186. package/src/lib/tracking/artifact-trail.js +0 -202
  187. package/src/sanitizer/context-sanitizer.js +0 -221
  188. package/src/sanitizer/patterns.js +0 -163
  189. package/src/scanner/project-scanner.js +0 -242
  190. package/src/ui/diff-display.js +0 -91
  191. package/src/ui/interactive-wizard.js +0 -96
  192. package/src/ui/user-review.js +0 -211
  193. package/src/ui/wizard-questions.js +0 -188
  194. package/src/utils/color-utils.js +0 -70
  195. package/src/utils/process-handler.js +0 -97
  196. package/src/writer/file-writer.js +0 -86
  197. /package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/references/proposal-example.md +0 -0
  198. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-example.md +0 -0
  199. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-guidelines.md +0 -0
  200. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/scripts/scan-csharp.mjs +0 -0
  201. /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/references/review-example-nextjs.md +0 -0
  202. /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/scripts/scan-nextjs.mjs +0 -0
  203. /package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/scripts/scan-accessibility.mjs +0 -0
  204. /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-dev-server.mjs +0 -0
  205. /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-stack.mjs +0 -0
  206. /package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/scripts/set_title.sh +0 -0
  207. /package/framework/skills/level-1-workflows/{phase-clarify → morph-phase-clarify}/references/clarifications-example.md +0 -0
  208. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/architecture-analysis-guide.md +0 -0
  209. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-authoring-guide.md +0 -0
  210. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-example.md +0 -0
  211. /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/recap-example.md +0 -0
  212. /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/vsa-implementation-guide.md +0 -0
  213. /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/task-planning-patterns.md +0 -0
  214. /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/tasks-example.md +0 -0
@@ -8,14 +8,14 @@
8
8
  * Called by `morph-spec init` and `morph-spec update`.
9
9
  */
10
10
 
11
- import { join } from 'path';
11
+ import { join, dirname } from 'path';
12
12
  import { readFile, writeFile, mkdir, copyFile } from 'fs/promises';
13
13
  import { existsSync, chmodSync, readdirSync } from 'fs';
14
14
  import { homedir } from 'os';
15
15
  import { execSync } from 'child_process';
16
16
 
17
17
  /** Current hooks schema version — bump when hook definitions change */
18
- const HOOKS_VERSION = '2.8.0';
18
+ const HOOKS_VERSION = '2.12.0';
19
19
 
20
20
  /** Marker for old dispatch.js (v1) */
21
21
  const OLD_DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
@@ -63,6 +63,16 @@ const MORPH_HOOKS = [
63
63
  }]
64
64
  },
65
65
 
66
+ // === SessionStart: compact only — post-compact context restore ===
67
+ {
68
+ event: 'SessionStart',
69
+ matcher: 'compact',
70
+ hooks: [{
71
+ type: 'command',
72
+ command: 'node framework/hooks/claude-code/session-start/post-compact-restore.js'
73
+ }]
74
+ },
75
+
66
76
  // === UserPromptSubmit ===
67
77
  {
68
78
  event: 'UserPromptSubmit',
@@ -93,6 +103,10 @@ const MORPH_HOOKS = [
93
103
  {
94
104
  type: 'command',
95
105
  command: 'node framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js'
106
+ },
107
+ {
108
+ type: 'command',
109
+ command: 'node framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js'
96
110
  }
97
111
  ]
98
112
  },
@@ -103,11 +117,11 @@ const MORPH_HOOKS = [
103
117
  matcher: 'Bash',
104
118
  hooks: [{
105
119
  type: 'prompt',
106
- prompt: `You are a guard for a morph-spec project. Read the bash command from the hook input and check:
107
- 1. Does the command delete .morph/ (e.g. contains "rm" with "-r" or "-rf" and ".morph")? If yes, BLOCK.
108
- 2. Does the command edit state.json directly via sed, jq, awk, echo, cat, or printf with a shell redirect (>)? If yes, BLOCK.
109
- 3. Does the command write to .morph/framework/ via a shell redirect (>)? If yes, BLOCK.
110
- If any condition is true, respond: {"ok": false, "reason": "MORPH-SPEC: <brief explanation and suggested alternative using morph-spec CLI>"}
120
+ prompt: `You are a guard for a morph-spec project. Read the bash command from the hook input and check EXACTLY these 3 conditions. IMPORTANT: reading files (cat, head, tail, grep, less, bat) is NEVER blocked regardless of path.
121
+ 1. Does the command DELETE .morph/ or .morph/state.json or .morph/framework/ using rm -r, rm -rf, or rmdir? BLOCK only these exact paths. NOTE: deleting .morph/features/ subdirectories is NOT blocked.
122
+ 2. Does the command OVERWRITE .morph/state.json using a shell redirect (>) combined with sed, jq, awk, echo, cat, or printf? If yes, BLOCK.
123
+ 3. Does the command WRITE TO .morph/framework/ via a shell redirect (>) or Node.js fs.writeFileSync/appendFileSync on a path under .morph/framework/? READING .morph/framework/ is always allowed. Writing to .morph/features/, .morph/config/, .morph/context/ is allowed.
124
+ If ANY of these 3 specific conditions matches, respond: {"ok": false, "reason": "MORPH-SPEC: [explain what was blocked]. To read agents/state use: morph-spec state get <feature> | morph-spec dispatch-agents <feature> <phase> --table | cat .morph/framework/agents.json (reading is always allowed). To update state use: morph-spec state set <feature> <key> <value>."}
111
125
  Otherwise respond: {"ok": true}`
112
126
  }]
113
127
  },
@@ -124,6 +138,14 @@ Otherwise respond: {"ok": true}`
124
138
  {
125
139
  type: 'command',
126
140
  command: 'node framework/hooks/claude-code/post-tool-use/context-refresh.js'
141
+ },
142
+ {
143
+ type: 'command',
144
+ command: 'node framework/hooks/claude-code/post-tool-use/skill-reminder.js'
145
+ },
146
+ {
147
+ type: 'command',
148
+ command: 'node framework/hooks/claude-code/post-tool-use/validator-feedback.js'
127
149
  }
128
150
  ]
129
151
  },
@@ -302,7 +324,6 @@ export async function installClaudeHooks(targetPath) {
302
324
  return { installed, updated: !alreadyCurrent };
303
325
  }
304
326
 
305
- /**
306
327
  /**
307
328
  * Resolve Python executable suitable for the statusLine command.
308
329
  *
@@ -398,6 +419,249 @@ export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOve
398
419
  return { installed: true, globalDir };
399
420
  }
400
421
 
422
+ /**
423
+ * Install VS Code terminal settings to enable OSC title sequences.
424
+ *
425
+ * Sets `terminal.integrated.tabs.title` to include `${sequence}` so VS Code
426
+ * renders OSC 0 escape sequences written by the set-terminal-title hook.
427
+ *
428
+ * Idempotent — only writes if the setting is missing or doesn't already
429
+ * include `${sequence}`.
430
+ *
431
+ * @param {string} [homeDirOverride] - Override home directory (for testing)
432
+ * @returns {Promise<{ updated: boolean, path: string|null, alreadySet: boolean }>}
433
+ */
434
+ export async function installVSCodeTerminalSettings(homeDirOverride = null) {
435
+ const home = homeDirOverride || homedir();
436
+
437
+ let settingsPath;
438
+ if (process.platform === 'win32') {
439
+ settingsPath = join(home, 'AppData', 'Roaming', 'Code', 'User', 'settings.json');
440
+ } else if (process.platform === 'darwin') {
441
+ settingsPath = join(home, 'Library', 'Application Support', 'Code', 'User', 'settings.json');
442
+ } else {
443
+ settingsPath = join(home, '.config', 'Code', 'User', 'settings.json');
444
+ }
445
+
446
+ if (!existsSync(settingsPath)) {
447
+ return { updated: false, path: settingsPath, alreadySet: false };
448
+ }
449
+
450
+ let settings;
451
+ try {
452
+ settings = JSON.parse(await readFile(settingsPath, 'utf-8'));
453
+ } catch {
454
+ settings = {};
455
+ }
456
+
457
+ const KEY = 'terminal.integrated.tabs.title';
458
+ const current = settings[KEY] || '';
459
+ if (current.includes('${sequence}')) {
460
+ return { updated: false, path: settingsPath, alreadySet: true };
461
+ }
462
+
463
+ settings[KEY] = '${sequence}${separator}${local}';
464
+ await writeFile(settingsPath, JSON.stringify(settings, null, 4) + '\n', 'utf-8');
465
+ return { updated: true, path: settingsPath, alreadySet: false };
466
+ }
467
+
468
+ /**
469
+ * Install shell integration for Claude Code terminal title.
470
+ *
471
+ * Writes a wrapper function to the user's shell profile that:
472
+ * 1. Starts a background job watching ~/.claude/terminal_title
473
+ * 2. Updates the terminal tab title in real time via Console.Title (PS) / OSC 0 (bash)
474
+ * 3. Resets the title when the `claude` command exits
475
+ *
476
+ * On Windows: installs to PowerShell 5.1 and/or PowerShell 7 profiles.
477
+ * On Linux/macOS: installs to ~/.bashrc and/or ~/.zshrc (if they exist).
478
+ *
479
+ * Idempotent — skips if the integration block is already present.
480
+ *
481
+ * @param {string} [homeDirOverride] - Override home directory (for testing)
482
+ * @returns {Promise<{ installed: string[], skipped: string[], errors: string[] }>}
483
+ */
484
+ export async function installShellIntegration(homeDirOverride = null) {
485
+ const home = homeDirOverride || homedir();
486
+ const installed = [];
487
+ const skipped = [];
488
+ const errors = [];
489
+
490
+ if (process.platform === 'win32') {
491
+ const profiles = [
492
+ join(home, 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1'),
493
+ join(home, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1'),
494
+ ];
495
+ for (const profilePath of profiles) {
496
+ try {
497
+ const result = await _appendShellBlock(profilePath, _buildPowerShellBlock(), _psAlreadyInstalled);
498
+ if (result === 'installed') installed.push(profilePath);
499
+ else skipped.push(profilePath);
500
+ } catch (err) {
501
+ errors.push(profilePath + ': ' + err.message);
502
+ }
503
+ }
504
+ } else {
505
+ const bashBlock = _buildBashBlock();
506
+ for (const profilePath of [join(home, '.bashrc'), join(home, '.zshrc')]) {
507
+ if (!existsSync(profilePath)) continue;
508
+ try {
509
+ const result = await _appendShellBlock(profilePath, bashBlock, _bashAlreadyInstalled);
510
+ if (result === 'installed') installed.push(profilePath);
511
+ else skipped.push(profilePath);
512
+ } catch (err) {
513
+ errors.push(profilePath + ': ' + err.message);
514
+ }
515
+ }
516
+ }
517
+
518
+ return { installed, skipped, errors };
519
+ }
520
+
521
+ /** @returns {string} PowerShell integration block */
522
+ function _buildPowerShellBlock() {
523
+ return [
524
+ '',
525
+ '# === Claude Code terminal title integration (morph-spec) ===',
526
+ '# Per-session title file prevents cross-session title contamination.',
527
+ '$script:_claudeExe = Get-Command claude -CommandType Application -ErrorAction SilentlyContinue |',
528
+ ' Select-Object -First 1 -ExpandProperty Source',
529
+ '',
530
+ 'function _Start-ClaudeTitleWatcher {',
531
+ ' param([string]$TitleFile)',
532
+ ' $rs = [runspacefactory]::CreateRunspace()',
533
+ ' $rs.Open()',
534
+ ' $ps = [powershell]::Create()',
535
+ ' $ps.Runspace = $rs',
536
+ ' [void]$ps.AddScript({',
537
+ ' param($f)',
538
+ ' $last = ""',
539
+ ' while ($true) {',
540
+ ' if (Test-Path $f) {',
541
+ ' try {',
542
+ ' $t = (Get-Content $f -Raw -ErrorAction Stop).Trim()',
543
+ ' if ($t -and $t -ne $last) {',
544
+ ' [Console]::Title = $t',
545
+ ' $last = $t',
546
+ ' }',
547
+ ' } catch {}',
548
+ ' }',
549
+ ' Start-Sleep -Milliseconds 500',
550
+ ' }',
551
+ ' }).AddArgument($TitleFile)',
552
+ ' $ps.BeginInvoke() | Out-Null',
553
+ ' return $ps',
554
+ '}',
555
+ '',
556
+ 'function Invoke-Claude {',
557
+ ' [CmdletBinding()]',
558
+ ' param([Parameter(ValueFromRemainingArguments=$true)][string[]]$Arguments)',
559
+ ' if (-not $script:_claudeExe) { Write-Error "claude executable not found in PATH"; return }',
560
+ ' # Per-session file: each Invoke-Claude gets its own title file so multiple',
561
+ ' # open terminals don\'t overwrite each other\'s titles.',
562
+ ' $sessionTitleFile = Join-Path $HOME ".claude\\terminal_title_$(New-Guid)"',
563
+ ' $env:MORPH_TERMINAL_TITLE_FILE = $sessionTitleFile',
564
+ ' $watcher = _Start-ClaudeTitleWatcher -TitleFile $sessionTitleFile',
565
+ ' try {',
566
+ ' & $script:_claudeExe @Arguments',
567
+ ' } finally {',
568
+ ' try { $watcher.Stop(); $watcher.Dispose() } catch {}',
569
+ ' $env:MORPH_TERMINAL_TITLE_FILE = $null',
570
+ ' Remove-Item $sessionTitleFile -ErrorAction SilentlyContinue',
571
+ ' [Console]::Title = "PowerShell"',
572
+ ' }',
573
+ '}',
574
+ 'Set-Alias -Name claude -Value Invoke-Claude -Force',
575
+ '# === End Claude Code terminal title integration (morph-spec) ===',
576
+ ].join('\n');
577
+ }
578
+
579
+ /** @returns {string} Bash/Zsh integration block */
580
+ function _buildBashBlock() {
581
+ return [
582
+ '',
583
+ '# === Claude Code terminal title integration (morph-spec) ===',
584
+ '# Per-session title file prevents cross-session title contamination.',
585
+ '_claude_title_watcher() {',
586
+ ' local title_file="$1"',
587
+ ' local last_title=""',
588
+ ' while true; do',
589
+ ' if [ -f "$title_file" ]; then',
590
+ ' local current',
591
+ ' current=$(cat "$title_file" 2>/dev/null)',
592
+ ' if [ -n "$current" ] && [ "$current" != "$last_title" ]; then',
593
+ " printf '\\e]0;%s\\a' \"$current\"",
594
+ ' last_title="$current"',
595
+ ' fi',
596
+ ' fi',
597
+ ' sleep 0.5',
598
+ ' done',
599
+ '}',
600
+ '',
601
+ 'claude() {',
602
+ ' # Per-session file: each invocation gets its own title file so multiple',
603
+ ' # open terminals don\'t overwrite each other\'s titles.',
604
+ ' local SESSION_TITLE_FILE',
605
+ ' SESSION_TITLE_FILE="$HOME/.claude/terminal_title_$$_$RANDOM"',
606
+ ' export MORPH_TERMINAL_TITLE_FILE="$SESSION_TITLE_FILE"',
607
+ ' _claude_title_watcher "$SESSION_TITLE_FILE" &',
608
+ ' local WATCHER_PID=$!',
609
+ ' command claude "$@"',
610
+ ' local EXIT_CODE=$?',
611
+ ' kill "$WATCHER_PID" 2>/dev/null',
612
+ ' wait "$WATCHER_PID" 2>/dev/null',
613
+ ' unset MORPH_TERMINAL_TITLE_FILE',
614
+ ' rm -f "$SESSION_TITLE_FILE"',
615
+ " printf '\\e]0;claude\\a'",
616
+ ' return $EXIT_CODE',
617
+ '}',
618
+ '# === End Claude Code terminal title integration (morph-spec) ===',
619
+ ].join('\n');
620
+ }
621
+
622
+ /** Check if PowerShell profile already has the integration */
623
+ function _psAlreadyInstalled(content) {
624
+ return content.includes('Invoke-Claude') || content.includes('_Start-ClaudeTitleWatcher');
625
+ }
626
+
627
+ /** Check if bash/zsh profile already has the integration */
628
+ function _bashAlreadyInstalled(content) {
629
+ return content.includes('_claude_title_watcher') || content.includes('command claude "$@"');
630
+ }
631
+
632
+ /**
633
+ * Append a shell block to a profile file if not already present.
634
+ * Creates the file (and parent dirs) if it doesn't exist.
635
+ * @param {string} profilePath
636
+ * @param {string} block
637
+ * @param {(content: string) => boolean} isInstalled
638
+ * @returns {Promise<'installed'|'skipped'>}
639
+ */
640
+ async function _appendShellBlock(profilePath, block, isInstalled) {
641
+ let existing = '';
642
+ let isUtf16le = false;
643
+
644
+ if (existsSync(profilePath)) {
645
+ const raw = await readFile(profilePath);
646
+ // Detect UTF-16LE: starts with FF FE BOM or has null bytes in the first bytes
647
+ isUtf16le = (raw[0] === 0xFF && raw[1] === 0xFE) ||
648
+ (raw.length >= 4 && raw[1] === 0 && raw[3] === 0);
649
+ existing = isUtf16le ? raw.toString('utf16le').replace(/^\uFEFF/, '') : raw.toString('utf-8');
650
+ }
651
+
652
+ if (isInstalled(existing)) return 'skipped';
653
+
654
+ const dir = dirname(profilePath);
655
+ if (!existsSync(dir)) {
656
+ await mkdir(dir, { recursive: true });
657
+ }
658
+
659
+ // Always write as UTF-8; trim trailing whitespace from UTF-16LE-decoded content
660
+ const base = isUtf16le ? existing.trimEnd() + '\n' : existing;
661
+ await writeFile(profilePath, base + block + '\n', 'utf-8');
662
+ return 'installed';
663
+ }
664
+
401
665
  /**
402
666
  * Remove old v1 agent-teams/dispatch.js hook entries.
403
667
  * @param {Object} settings
@@ -1,117 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Dev Stop Hook: Sync Health Check (Advisory)
5
- *
6
- * Event: Stop
7
- * Scope: Framework codebase only (dev hook)
8
- *
9
- * When Claude stops, checks for out-of-sync conditions:
10
- * 1. Templates not in REGISTRY.json
11
- * 2. Standards not in STANDARDS.json
12
- *
13
- * Uses readdirSync (no git dependency, fast).
14
- * Returns advisory context if out-of-sync items found.
15
- *
16
- * Fail-open: exits 0 on any error.
17
- */
18
-
19
- import { readdirSync, readFileSync, existsSync } from 'fs';
20
- import { join } from 'path';
21
- import { injectContext, pass } from '../shared/hook-response.js';
22
-
23
- /**
24
- * Recursively collect .md files from a directory.
25
- * @param {string} dir - Directory to scan
26
- * @param {string} [base] - Base path for relative paths
27
- * @returns {string[]} Relative paths
28
- */
29
- function collectMdFiles(dir, base) {
30
- base = base || dir;
31
- const results = [];
32
- if (!existsSync(dir)) return results;
33
-
34
- try {
35
- const entries = readdirSync(dir, { withFileTypes: true });
36
- for (const entry of entries) {
37
- const fullPath = join(dir, entry.name);
38
- if (entry.isDirectory()) {
39
- if (entry.name === 'node_modules') continue;
40
- results.push(...collectMdFiles(fullPath, base));
41
- } else if (entry.name.endsWith('.md') && entry.name !== 'README.md') {
42
- const rel = fullPath.replace(base + '/', '').replace(base + '\\', '').replace(/\\/g, '/');
43
- results.push(rel);
44
- }
45
- }
46
- } catch {
47
- // Fail-open
48
- }
49
-
50
- return results;
51
- }
52
-
53
- try {
54
- // Prevent infinite loop
55
- if (process.env.MORPH_STOP_HOOK_ACTIVE === '1') pass();
56
-
57
- const warnings = [];
58
-
59
- // Check templates vs REGISTRY.json
60
- const registryPath = 'framework/templates/REGISTRY.json';
61
- if (existsSync(registryPath)) {
62
- try {
63
- const registry = JSON.parse(readFileSync(registryPath, 'utf-8'));
64
- const registeredPaths = new Set((registry.templates || []).map(t => t.path));
65
- const templateFiles = collectMdFiles('framework/templates');
66
-
67
- const unregistered = templateFiles.filter(f => !registeredPaths.has(f));
68
- if (unregistered.length > 0) {
69
- warnings.push(`Templates not in REGISTRY.json (${unregistered.length}):`);
70
- for (const f of unregistered.slice(0, 5)) {
71
- warnings.push(` - ${f}`);
72
- }
73
- if (unregistered.length > 5) {
74
- warnings.push(` ... and ${unregistered.length - 5} more`);
75
- }
76
- }
77
- } catch {
78
- // Skip on error
79
- }
80
- }
81
-
82
- // Check standards vs STANDARDS.json
83
- const standardsPath = 'framework/standards/STANDARDS.json';
84
- if (existsSync(standardsPath)) {
85
- try {
86
- const registry = JSON.parse(readFileSync(standardsPath, 'utf-8'));
87
- const registeredPaths = new Set((registry.standards || []).map(s => s.path));
88
- const standardFiles = collectMdFiles('framework/standards');
89
-
90
- const unregistered = standardFiles.filter(f => !registeredPaths.has(f));
91
- if (unregistered.length > 0) {
92
- warnings.push(`Standards not in STANDARDS.json (${unregistered.length}):`);
93
- for (const f of unregistered.slice(0, 5)) {
94
- warnings.push(` - ${f}`);
95
- }
96
- if (unregistered.length > 5) {
97
- warnings.push(` ... and ${unregistered.length - 5} more`);
98
- }
99
- warnings.push(' Run: node scripts/generate-standards-registry.js');
100
- }
101
- } catch {
102
- // Skip on error
103
- }
104
- }
105
-
106
- if (warnings.length === 0) pass();
107
-
108
- const message = [
109
- 'MORPH-SPEC Dev: Out-of-sync items detected:',
110
- ...warnings.map(w => ` ${w}`),
111
- ].join('\n');
112
-
113
- injectContext(message);
114
- } catch {
115
- // Fail-open
116
- process.exit(0);
117
- }
@@ -1,57 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Dev PreToolUse Hook: Guard Version Numbers
5
- *
6
- * Event: PreToolUse | Matcher: Write|Edit
7
- * Scope: Framework codebase only (dev hook)
8
- *
9
- * Blocks Write/Edit to files that contain hardcoded version patterns
10
- * like "MORPH-SPEC v4.8.12". Version should only be in package.json.
11
- *
12
- * Checked extensions: .md, .cs, .css, .js (covers templates + source)
13
- * Exceptions: CHANGELOG.md, node_modules/, test/, package.json, package-lock.json
14
- *
15
- * Fail-open: exits 0 on any error.
16
- */
17
-
18
- import { readStdin } from '../shared/stdin-reader.js';
19
- import { getFilePath, getContentToValidate } from '../shared/payload-utils.js';
20
- import { block, pass } from '../shared/hook-response.js';
21
-
22
- const VERSION_PATTERN = /MORPH-SPEC\s+v\d+\.\d+\.\d+/;
23
- const CHECKED_EXTENSIONS = ['.md', '.cs', '.css', '.js', '.html', '.txt'];
24
-
25
- try {
26
- const payload = await readStdin();
27
- if (!payload) pass();
28
-
29
- const filePath = getFilePath(payload);
30
- if (!filePath) pass();
31
-
32
- // Only check relevant file types
33
- if (!CHECKED_EXTENSIONS.some(ext => filePath.endsWith(ext))) pass();
34
-
35
- // Exceptions
36
- if (filePath.includes('node_modules/')) pass();
37
- if (filePath.endsWith('CHANGELOG.md')) pass();
38
- if (filePath.includes('test/')) pass();
39
- if (filePath.endsWith('package.json')) pass();
40
- if (filePath.endsWith('package-lock.json')) pass();
41
-
42
- const content = getContentToValidate(payload);
43
- if (!content) pass();
44
-
45
- if (VERSION_PATTERN.test(content)) {
46
- block(
47
- `MORPH-SPEC: Hardcoded version number detected in '${filePath}'.\n` +
48
- `Version should only be in package.json. Use 'MORPH-SPEC by Polymorphism Tech' without version.\n` +
49
- `Pattern found: ${content.match(VERSION_PATTERN)?.[0]}`
50
- );
51
- }
52
-
53
- pass();
54
- } catch {
55
- // Fail-open
56
- process.exit(0);
57
- }
@@ -1,60 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Dev PostToolUse Hook: Standards Registry Sync Advisory
5
- *
6
- * Event: PostToolUse | Matcher: Write
7
- * Scope: Framework codebase only (dev hook)
8
- *
9
- * After a Write to framework/standards/**/*.md, checks if the file
10
- * is registered in framework/standards/STANDARDS.json.
11
- * If not, injects an advisory to regenerate the registry.
12
- *
13
- * Fail-open: exits 0 on any error.
14
- */
15
-
16
- import { readFileSync, existsSync } from 'fs';
17
- import { readStdin } from '../shared/stdin-reader.js';
18
- import { getFilePath } from '../shared/payload-utils.js';
19
- import { approve, pass } from '../shared/hook-response.js';
20
-
21
- try {
22
- const payload = await readStdin();
23
- if (!payload) pass();
24
-
25
- const filePath = getFilePath(payload);
26
- if (!filePath) pass();
27
-
28
- // Only check framework/standards/**/*.md
29
- if (!filePath.includes('framework/standards/')) pass();
30
- if (!filePath.endsWith('.md')) pass();
31
-
32
- // Skip meta files
33
- const basename = filePath.split('/').pop();
34
- if (basename === 'README.md') pass();
35
- if (basename === 'STANDARDS.json') pass();
36
-
37
- // Extract relative path from framework/standards/
38
- const standardsIdx = filePath.indexOf('framework/standards/');
39
- if (standardsIdx === -1) pass();
40
- const relativePath = filePath.slice(standardsIdx + 'framework/standards/'.length);
41
-
42
- // Read STANDARDS.json
43
- const registryPath = 'framework/standards/STANDARDS.json';
44
- if (!existsSync(registryPath)) pass();
45
-
46
- const registry = JSON.parse(readFileSync(registryPath, 'utf-8'));
47
- const registeredPaths = (registry.standards || []).map(s => s.path);
48
-
49
- if (!registeredPaths.includes(relativePath)) {
50
- approve(
51
- `MORPH-SPEC: Standard '${relativePath}' is not in STANDARDS.json.\n` +
52
- `Run: node scripts/generate-standards-registry.js`
53
- );
54
- }
55
-
56
- pass();
57
- } catch {
58
- // Fail-open
59
- process.exit(0);
60
- }
@@ -1,60 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Dev PostToolUse Hook: Template Registry Sync Advisory
5
- *
6
- * Event: PostToolUse | Matcher: Write
7
- * Scope: Framework codebase only (dev hook)
8
- *
9
- * After a Write to framework/templates/**/*.md, checks if the file
10
- * is registered in framework/templates/REGISTRY.json.
11
- * If not, injects an advisory to add the entry.
12
- *
13
- * Fail-open: exits 0 on any error.
14
- */
15
-
16
- import { readFileSync, existsSync } from 'fs';
17
- import { readStdin } from '../shared/stdin-reader.js';
18
- import { getFilePath } from '../shared/payload-utils.js';
19
- import { approve, pass } from '../shared/hook-response.js';
20
-
21
- try {
22
- const payload = await readStdin();
23
- if (!payload) pass();
24
-
25
- const filePath = getFilePath(payload);
26
- if (!filePath) pass();
27
-
28
- // Only check framework/templates/**/*.md
29
- if (!filePath.includes('framework/templates/')) pass();
30
- if (!filePath.endsWith('.md')) pass();
31
-
32
- // Skip meta files
33
- const basename = filePath.split('/').pop();
34
- if (basename === 'README.md') pass();
35
- if (basename === 'REGISTRY.json') pass();
36
-
37
- // Extract relative path from framework/templates/
38
- const templateIdx = filePath.indexOf('framework/templates/');
39
- if (templateIdx === -1) pass();
40
- const relativePath = filePath.slice(templateIdx + 'framework/templates/'.length);
41
-
42
- // Read REGISTRY.json
43
- const registryPath = 'framework/templates/REGISTRY.json';
44
- if (!existsSync(registryPath)) pass();
45
-
46
- const registry = JSON.parse(readFileSync(registryPath, 'utf-8'));
47
- const registeredPaths = (registry.templates || []).map(t => t.path);
48
-
49
- if (!registeredPaths.includes(relativePath)) {
50
- approve(
51
- `MORPH-SPEC: Template '${relativePath}' is not in REGISTRY.json.\n` +
52
- `Add an entry to framework/templates/REGISTRY.json with id, path, phase, and outputName fields.`
53
- );
54
- }
55
-
56
- pass();
57
- } catch {
58
- // Fail-open
59
- process.exit(0);
60
- }