@polymorphism-tech/morph-spec 7.9.3 → 8.0.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 (213) hide show
  1. package/CLAUDE.md +2 -2
  2. package/README.md +31 -2
  3. package/bin/morph-spec.js +50 -2
  4. package/bin/validate.js +379 -379
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +38 -1
  7. package/docs/QUICKSTART.md +60 -1
  8. package/framework/CLAUDE.md +13 -2
  9. package/framework/MORPH.md +38 -2
  10. package/framework/agents.json +22 -3
  11. package/framework/commands/commit.md +166 -166
  12. package/framework/commands/morph-hotfix.md +39 -0
  13. package/framework/commands/morph-infra.md +215 -215
  14. package/framework/commands/morph-preflight.md +205 -205
  15. package/framework/commands/morph-troubleshoot.md +124 -124
  16. package/framework/hooks/README.md +25 -10
  17. package/framework/hooks/claude-code/core/gate-guard.js +4 -2
  18. package/framework/hooks/claude-code/core/loop-detect.js +2 -1
  19. package/framework/hooks/claude-code/core/post-edit-typecheck.js +14 -5
  20. package/framework/hooks/claude-code/core/tasks-json-guard.js +4 -2
  21. package/framework/hooks/claude-code/core/validate-completion.js +1 -1
  22. package/framework/hooks/claude-code/core/worktree-context.js +160 -0
  23. package/framework/hooks/claude-code/statusline.py +679 -679
  24. package/framework/hooks/claude-code/statusline.sh +11 -11
  25. package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
  26. package/framework/hooks/git/pre-commit/agents.sh +25 -25
  27. package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
  28. package/framework/hooks/git/pre-commit/specs.sh +50 -50
  29. package/framework/hooks/git/pre-push/run-tests.sh +44 -44
  30. package/framework/hooks/shared/activity-logger.js +160 -160
  31. package/framework/hooks/shared/build-check.js +2 -2
  32. package/framework/hooks/shared/hook-response.js +17 -5
  33. package/framework/hooks/shared/payload-utils.js +42 -0
  34. package/framework/hooks/shared/state-reader.js +22 -2
  35. package/framework/hooks/shared/stdin-reader.js +26 -26
  36. package/framework/rubrics/backend.json +41 -41
  37. package/framework/rules/ai-agents-standards.md +45 -45
  38. package/framework/rules/dotnet-templates.md +86 -86
  39. package/framework/rules/frontend-standards.md +13 -13
  40. package/framework/rules/infrastructure-standards.md +27 -27
  41. package/framework/rules/neon-standards.md +25 -25
  42. package/framework/rules/nextjs-standards.md +20 -20
  43. package/framework/rules/nextjs-templates.md +40 -40
  44. package/framework/schemas/feature.schema.json +95 -1
  45. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  46. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  47. package/framework/skills/core/morph-apply/SKILL.md +39 -8
  48. package/framework/skills/core/morph-brainstorming/SKILL.md +9 -1
  49. package/framework/skills/core/morph-eval/SKILL.md +6 -1
  50. package/framework/skills/core/morph-hotfix/SKILL.md +136 -0
  51. package/framework/skills/core/morph-implement/SKILL.md +8 -4
  52. package/framework/skills/core/morph-plan/SKILL.md +5 -1
  53. package/framework/skills/core/morph-proposal/SKILL.md +24 -1
  54. package/framework/skills/core/morph-review/SKILL.md +3 -2
  55. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  56. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  57. package/framework/standards/STANDARDS.json +7 -2
  58. package/framework/standards/ai-agents/agent-session.md +160 -160
  59. package/framework/standards/ai-agents/batching.md +193 -193
  60. package/framework/standards/ai-agents/context-providers.md +188 -188
  61. package/framework/standards/ai-agents/mcp-server.md +250 -250
  62. package/framework/standards/ai-agents/mcp-tools.md +197 -197
  63. package/framework/standards/ai-agents/middleware-patterns.md +207 -207
  64. package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
  65. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
  66. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
  67. package/framework/standards/ai-agents/modalities-vision.md +201 -201
  68. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
  69. package/framework/standards/ai-agents/production.md +275 -275
  70. package/framework/standards/ai-agents/prompts-in-db.md +214 -214
  71. package/framework/standards/ai-agents/providers/model-registry.md +158 -158
  72. package/framework/standards/ai-agents/service-tier-flex.md +135 -135
  73. package/framework/standards/ai-agents/structured-output.md +249 -249
  74. package/framework/standards/ai-agents/sweet-spot.md +109 -109
  75. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +45 -3
  76. package/framework/templates/.idea/morph-templates.xml +92 -0
  77. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  78. package/framework/templates/IDE-SNIPPETS.md +262 -262
  79. package/framework/templates/REGISTRY.json +0 -62
  80. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  81. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
  82. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
  83. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
  84. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
  85. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
  86. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
  87. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
  88. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
  89. package/framework/templates/code/dotnet/database/migration.cs +83 -83
  90. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  91. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  92. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  93. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
  94. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
  95. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  96. package/framework/templates/code/typescript/contracts.ts +167 -167
  97. package/framework/templates/docs/clarifications.md +253 -253
  98. package/framework/templates/docs/schema-analysis.md +119 -119
  99. package/framework/templates/docs/ui-components.md +52 -52
  100. package/framework/templates/docs/ui-design-system.md +76 -76
  101. package/framework/templates/docs/ui-flows.md +167 -167
  102. package/framework/templates/docs/ui-mockups.md +98 -98
  103. package/framework/templates/feature/decisions.md +122 -122
  104. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
  105. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  106. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
  107. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  108. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
  109. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  110. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
  111. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  112. package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -50
  113. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  114. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  115. package/framework/templates/infrastructure/github/README.md +56 -56
  116. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  117. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  118. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  119. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  120. package/framework/templates/integrations/asaas-client.cs +387 -387
  121. package/framework/templates/integrations/asaas-webhook.cs +351 -351
  122. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  123. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  124. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  125. package/framework/templates/project/validate.js +120 -120
  126. package/framework/templates/state.template.json +17 -17
  127. package/framework/templates/ui/design-system.css +226 -226
  128. package/package.json +1 -1
  129. package/src/commands/dashboard/dashboard.js +53 -53
  130. package/src/commands/index.js +13 -13
  131. package/src/commands/mcp/mcp-setup.js +519 -519
  132. package/src/commands/project/checkpoint.js +209 -209
  133. package/src/commands/project/doctor.js +4 -2
  134. package/src/commands/project/init.js +46 -2
  135. package/src/commands/project/update.js +5 -3
  136. package/src/commands/project/worktree.js +1005 -46
  137. package/src/commands/state/advance.js +17 -2
  138. package/src/commands/state/approve.js +18 -1
  139. package/src/commands/state/create.js +158 -14
  140. package/src/commands/state/retype.js +126 -0
  141. package/src/commands/state/score.js +4 -0
  142. package/src/commands/tasks/dag.js +32 -0
  143. package/src/commands/validation/index.js +1 -0
  144. package/src/commands/validation/validate-feature.js +33 -5
  145. package/src/commands/validation/verify.js +190 -0
  146. package/src/core/state/phase-state-machine.js +197 -197
  147. package/src/core/state/state-manager.js +35 -10
  148. package/src/core/templates/template-renderer.js +477 -477
  149. package/src/core/work-types.js +79 -0
  150. package/src/dashboard/app/components/DetailPanel.jsx +11 -11
  151. package/src/dashboard/app/components/FlowGraph.jsx +17 -17
  152. package/src/dashboard/app/components/SearchFilter.jsx +22 -22
  153. package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
  154. package/src/dashboard/app/main.jsx +13 -13
  155. package/src/dashboard/app/pages/AgentMap.jsx +370 -370
  156. package/src/dashboard/app/pages/HookInspector.jsx +418 -418
  157. package/src/dashboard/app/pages/LiveState.jsx +11 -1
  158. package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
  159. package/src/dashboard/app/pages/RulesMap.jsx +273 -273
  160. package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
  161. package/src/dashboard/app/pages/Standards.jsx +332 -332
  162. package/src/dashboard/app/styles/index.css +47 -47
  163. package/src/dashboard/index.html +12 -12
  164. package/src/dashboard/package.json +24 -24
  165. package/src/dashboard/scanner/hooks-scanner.js +41 -41
  166. package/src/dashboard/scanner/phases-scanner.js +15 -15
  167. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  168. package/src/dashboard/scanner/rules-scanner.js +37 -37
  169. package/src/dashboard/scanner/skills-scanner.js +43 -43
  170. package/src/dashboard/scanner/squads-scanner.js +13 -13
  171. package/src/dashboard/scanner/standards-scanner.js +26 -26
  172. package/src/dashboard/scanner/state-scanner.js +42 -0
  173. package/src/dashboard/vite-plugin-morph-data.js +17 -17
  174. package/src/dashboard/vite.config.js +24 -24
  175. package/src/lib/agents/metrics-aggregator.js +56 -56
  176. package/src/lib/agents/metrics-calculator.js +125 -125
  177. package/src/lib/agents/output-collector.js +73 -73
  178. package/src/lib/detectors/claude-config-detector.js +152 -152
  179. package/src/lib/detectors/design-system-detector.js +231 -231
  180. package/src/lib/detectors/index.js +217 -217
  181. package/src/lib/generators/architecture-scanner.js +639 -639
  182. package/src/lib/generators/design-system-generator.js +508 -508
  183. package/src/lib/generators/index.js +13 -13
  184. package/src/lib/generators/settings-generator.js +209 -209
  185. package/src/lib/hooks/hook-executor.js +167 -167
  186. package/src/lib/installers/mcp-installer.js +719 -719
  187. package/src/lib/installers/memory-installer.js +1 -1
  188. package/src/lib/risk-detector.js +16 -0
  189. package/src/lib/tasks/dispatch-planner.js +122 -4
  190. package/src/lib/tasks/mermaid-renderer.js +132 -0
  191. package/src/lib/tasks/task-classifier.js +30 -30
  192. package/src/lib/tasks/task-parser.js +209 -209
  193. package/src/lib/tasks/test-runner.js +27 -6
  194. package/src/lib/validators/architecture/architecture-validator.js +259 -259
  195. package/src/lib/validators/checkpoint-persistence.js +39 -39
  196. package/src/lib/validators/contracts/contract-compliance-validator.js +274 -274
  197. package/src/lib/validators/css/css-validator.js +340 -340
  198. package/src/lib/validators/packages/package-validator.js +347 -347
  199. package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
  200. package/src/lib/validators/shared/issue-counter.js +14 -14
  201. package/src/lib/validators/validation-runner.js +349 -349
  202. package/src/lib/validators/verify-runner.js +322 -0
  203. package/src/lib/work-type-detector.js +143 -0
  204. package/src/scripts/setup-infra.js +32 -5
  205. package/src/utils/agent-id-mapper.js +11 -11
  206. package/src/utils/agents-installer.js +10 -6
  207. package/src/utils/claude-settings-manager.js +300 -300
  208. package/src/utils/file-copier.js +43 -0
  209. package/src/utils/hooks-installer.js +41 -11
  210. package/src/utils/skills-installer.js +174 -174
  211. package/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +0 -113
  212. package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +0 -80
  213. package/src/lib/tasks/wave-resolver.js +0 -353
@@ -7,8 +7,8 @@ Comprehensive hooks system for enforcing spec-driven development at the Claude C
7
7
  ```
8
8
  framework/hooks/
9
9
  ├── claude-code/ # Claude Code native hooks
10
- │ ├── session-start/
11
- │ │ └── inject-morph-context.js # Inject state summary on session start
10
+ │ ├── core/
11
+ │ │ └── worktree-context.js # SessionStart: worktree feature identity / active-feature summary
12
12
  │ ├── user-prompt/
13
13
  │ │ └── enrich-prompt.js # Context-aware prompt enrichment
14
14
  │ ├── pre-tool-use/
@@ -41,6 +41,7 @@ framework/hooks/
41
41
 
42
42
  | Event | Hook | Type | Purpose |
43
43
  |-------|------|------|---------|
44
+ | **SessionStart** | worktree-context.js | Context inject | Secondary worktree → feature identity (name/branch/phase) + missing-junction warning; primary root → active-feature summary (phase, task stats, pending gates), silent if none |
44
45
  | **PreToolUse** (Write\|Edit) | _(native permissions.deny)_ | Block | Blocks edits to state.json and .morph/framework/ |
45
46
  | **PreToolUse** (Write\|Edit) | protect-spec-files.js | Block | Blocks edits to spec files after approval; for tasks.json validates the mutable-field diff, the status enum, and requires a `notes` when `outputs` diverge from the plan |
46
47
  | **PreToolUse** (Write\|Edit) | enforce-phase-writes.js | Block | Ensures writes go to current phase directory |
@@ -113,6 +114,8 @@ All Claude Code hooks import from `framework/hooks/shared/`:
113
114
  | `hook-response.js` | `block(reason)`, `approve(context)`, `injectContext(text)`, `pass()` |
114
115
  | `stdin-reader.js` | `readStdin()` — Promise-based stdin JSON reader |
115
116
 
117
+ `injectContext(text)` is how `worktree-context.js` (and any other SessionStart hook) delivers context: it emits `{ "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": text } }` on stdout. Claude Code only honors additional context nested under `hookSpecificOutput` — a bare top-level field is ignored.
118
+
116
119
  ## How Hooks Work
117
120
 
118
121
  ### PreToolUse (Write|Edit) Flow
@@ -147,16 +150,28 @@ enforce-phase-writes.js
147
150
  ```
148
151
  Session starts/resumes/compacts
149
152
  ↓
150
- inject-morph-context.js
151
- ├── No state.json → silent exit
152
- ├── Has active feature → inject summary:
153
- │ "MORPH-SPEC Status:
154
- │ - Active feature: my-feature (phase: implement)
155
- │ - Tasks: 3/10 completed
156
- │ - Pending approvals: design, tasks"
157
- └── No active feature → inject feature list
153
+ worktree-context.js
154
+ ├── No morph project (no state.json AND no .morph/features/) → silent exit
155
+ ├── Secondary worktree (git-dir != git-common-dir)?
156
+ │ ├── Feature identified (branch morph/{f}, or the lone .morph/features/ dir)
157
+ │ │ → inject: "🌳 Worktree desta feature: {f} (branch morph/{f}, fase {phase}).
158
+ │ │ Trabalhe apenas nesta feature nesta sessão; o estado vive no .morph/
159
+ │ │ deste worktree." + a warning appended if node_modules/.claude
160
+ │ │ junctions are missing ("Rode: morph-spec worktree link --all")
161
+ │ └── No feature identified → silent exit
162
+ └── Primary root (not a worktree)
163
+ ├── Has active (in_progress/draft) feature → inject summary:
164
+ │ "MORPH-SPEC: feature ativa {name} (fase {phase}) · tasks {c}/{t}
165
+ │ · gates pendentes: {gate, gate}"
166
+ └── No active feature → silent exit (don't clutter idle sessions)
158
167
  ```
159
168
 
169
+ v8 note: this hook also fulfills the SessionStart injection role this README
170
+ used to describe as `inject-morph-context.js` — that file was documented but
171
+ never registered in `MORPH_HOOKS`; `worktree-context.js` is the hook that
172
+ actually ships (v3.2.0), and its primary-root branch covers the same
173
+ active-feature-summary ground.
174
+
160
175
  ## Testing
161
176
 
162
177
  ```bash
@@ -98,7 +98,8 @@ function checkAskUserQuestion(input, state, feature, cwd) {
98
98
  `(trust:${decision.trust ?? 'auto'}, nenhum sinal de risco). ` +
99
99
  `Sob trust:auto sem risco, este gate deveria auto-passar com um summary curto, sem interromper o humano. ` +
100
100
  `Se a pausa foi intencional (você detectou um risco que o registro não capturou), atualize gateDecisions.${gate}.signals antes de prosseguir; ` +
101
- `caso contrário, evite pausar gates auto-aprováveis.`
101
+ `caso contrário, evite pausar gates auto-aprováveis.`,
102
+ 'PostToolUse'
102
103
  );
103
104
  }
104
105
  // The decision said pause:true and the LLM did pause — coherent.
@@ -138,7 +139,8 @@ function checkStateEdit(input, state, feature, cwd) {
138
139
  `mas a decisão registrada (gateDecisions.${gate}) era pause:true ` +
139
140
  `(sinal: ${decision.reason || 'risco detectado'}). ` +
140
141
  `Um sinal de risco exigia pausa humana via AskUserQuestion — uma auto-aprovação aqui ignora o trust mode. ` +
141
- `Reabra o gate com AskUserQuestion (label "aprovado, prosseguir") ou corrija o sinal de risco antes de avançar.`
142
+ `Reabra o gate com AskUserQuestion (label "aprovado, prosseguir") ou corrija o sinal de risco antes de avançar.`,
143
+ 'PostToolUse'
142
144
  );
143
145
  }
144
146
  checked = true;
@@ -95,7 +95,8 @@ try {
95
95
  ` 2. Try a completely different fix strategy\n` +
96
96
  ` 3. If the test keeps failing, check if the test itself is wrong\n` +
97
97
  ` 4. If blocked, ask the user for guidance\n\n` +
98
- `Do NOT continue the same pattern.`
98
+ `Do NOT continue the same pattern.`,
99
+ 'PostToolUse'
99
100
  );
100
101
  } catch {
101
102
  // Fail-open
@@ -20,6 +20,7 @@ import { join } from 'path';
20
20
  import { readStdin } from '../../shared/stdin-reader.js';
21
21
  import { pass, injectContext } from '../../shared/hook-response.js';
22
22
  import { stateExists } from '../../shared/state-reader.js';
23
+ import { resolveRootForFile } from '../../shared/payload-utils.js';
23
24
 
24
25
  const DEBOUNCE_MS = 3000;
25
26
 
@@ -33,9 +34,17 @@ try {
33
34
  if (!['Write', 'Edit'].includes(toolName)) pass();
34
35
  if (!filePath.match(/\.tsx?$/)) pass();
35
36
 
36
- // Debounce: skip if a typecheck ran within the last 3 seconds
37
- const cwd = process.cwd();
38
- const memDir = join(cwd, '.morph', 'memory');
37
+ // Resolve the root that OWNS the edited file — when a subagent edits inside
38
+ // its own git worktree (isolation: "worktree", §3b-w), tsc must run there,
39
+ // not in the main working tree. Falls back to cwd for the normal case.
40
+ const root = resolveRootForFile(filePath, process.cwd());
41
+
42
+ // No node_modules under the resolved root → tsc can't run; pass silently
43
+ // (a fresh worktree before `morph-spec worktree link` has no node_modules).
44
+ if (!existsSync(join(root, 'node_modules'))) pass();
45
+
46
+ // Debounce (keyed by root): skip if a typecheck ran within the last 3s.
47
+ const memDir = join(root, '.morph', 'memory');
39
48
  const debouncePath = join(memDir, 'typecheck-debounce.json');
40
49
 
41
50
  try {
@@ -51,7 +60,7 @@ try {
51
60
  } catch { /* non-blocking */ }
52
61
 
53
62
  try {
54
- execSync('npx tsc --noEmit', { cwd, stdio: 'pipe', timeout: 30000 });
63
+ execSync('npx tsc --noEmit', { cwd: root, stdio: 'pipe', timeout: 30000 });
55
64
  pass();
56
65
  } catch (err) {
57
66
  const stderr = err.stderr?.toString() || '';
@@ -62,7 +71,7 @@ try {
62
71
  .filter(l => l.includes(fileName) || l.includes('error TS'))
63
72
  .slice(0, 10)
64
73
  .join('\n');
65
- injectContext(`TypeScript errors after editing ${fileName}:\n${relevant || output.split('\n').slice(0, 10).join('\n')}`);
74
+ injectContext(`TypeScript errors after editing ${fileName}:\n${relevant || output.split('\n').slice(0, 10).join('\n')}`, 'PostToolUse');
66
75
  }
67
76
  } catch {
68
77
  pass();
@@ -59,7 +59,8 @@ try {
59
59
  logHookActivity('tasks-json-guard', 'PostToolUse', 'invalid', cwd);
60
60
  injectContext(
61
61
  '⚠️ [morph-spec] tasks.json INVÁLIDO — o arquivo gravado não parseia como JSON. ' +
62
- 'Corrija o arquivo antes de continuar.'
62
+ 'Corrija o arquivo antes de continuar.',
63
+ 'PostToolUse'
63
64
  );
64
65
  }
65
66
 
@@ -69,7 +70,8 @@ try {
69
70
  injectContext(
70
71
  '⚠️ [morph-spec] tasks.json com schema inválido após o write:\n' +
71
72
  errors.map(e => ` • ${e}`).join('\n') +
72
- `\nStatus válidos: ${VALID_TASK_STATUS.join(', ')}. Corrija antes de marcar a task como done.`
73
+ `\nStatus válidos: ${VALID_TASK_STATUS.join(', ')}. Corrija antes de marcar a task como done.`,
74
+ 'PostToolUse'
73
75
  );
74
76
  }
75
77
 
@@ -235,7 +235,7 @@ try {
235
235
 
236
236
  logHookActivity('validate-completion', 'Stop', `warnings(${warnings.length})`);
237
237
  logEvent('session_end', { feature: name, phase: currentPhase, warnings: warnings.length });
238
- injectContext(message);
238
+ injectContext(message, 'Stop');
239
239
  } catch {
240
240
  process.exit(0);
241
241
  }
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * worktree-context.js — SessionStart hook
4
+ *
5
+ * Event: SessionStart
6
+ *
7
+ * v8 runs each feature in its own git worktree (`.worktrees/{feature}/` on
8
+ * branch `morph/{feature}`), each with its own Claude Code instance. When a
9
+ * session starts inside one of those worktrees, Claude has no built-in way to
10
+ * know which feature it belongs to — this hook injects that identity so the
11
+ * agent doesn't have to guess (or worse, wander into another feature's
12
+ * `.morph/`).
13
+ *
14
+ * This also fulfills the SessionStart injection role long documented in
15
+ * `framework/hooks/README.md` as `inject-morph-context.js` — a hook that was
16
+ * described but never registered in MORPH_HOOKS. In the primary root (not a
17
+ * worktree) this hook covers that same ground: a compact active-feature
18
+ * summary so a fresh session doesn't have to re-read state.json by hand.
19
+ *
20
+ * Two branches:
21
+ * 1. Secondary worktree → feature identity (name, branch, phase) +
22
+ * a warning if shared infra junctions (node_modules/.claude) are
23
+ * missing (they're created by `morph-spec worktree link --all`).
24
+ * 2. Primary root → active-feature summary (phase, task stats, pending
25
+ * gates), or silent if there's no feature in progress.
26
+ *
27
+ * Fail-open: any error, or nothing to report, → pass() silently. This hook
28
+ * never blocks a session from starting.
29
+ */
30
+
31
+ import { readFileSync, existsSync, readdirSync } from 'fs';
32
+ import { join } from 'path';
33
+ import { execSync } from 'child_process';
34
+ import {
35
+ stateExists,
36
+ getActiveFeature,
37
+ getFeaturePhase,
38
+ getPendingGates,
39
+ getTaskStats,
40
+ } from '../../shared/state-reader.js';
41
+ import { injectContext, pass } from '../../shared/hook-response.js';
42
+
43
+ function readStdin() {
44
+ try {
45
+ return JSON.parse(readFileSync(0, 'utf8') || '{}');
46
+ } catch {
47
+ return {};
48
+ }
49
+ }
50
+
51
+ function projectDir() {
52
+ return process.env.CLAUDE_PROJECT_DIR || process.cwd();
53
+ }
54
+
55
+ function gitRevParse(arg, dir) {
56
+ return execSync(`git rev-parse ${arg}`, { cwd: dir, stdio: ['pipe', 'pipe', 'pipe'] })
57
+ .toString()
58
+ .trim();
59
+ }
60
+
61
+ /**
62
+ * A secondary git worktree has a `--git-dir` (worktree-private, under
63
+ * `.../.git/worktrees/{name}`) distinct from its `--git-common-dir` (the
64
+ * primary repo's shared `.git`). In the primary root both resolve to the
65
+ * same path. Any git error (not a repo at all) is treated as "not a
66
+ * worktree" — fail-open.
67
+ */
68
+ function isSecondaryWorktree(dir) {
69
+ try {
70
+ const gitDir = gitRevParse('--git-dir', dir);
71
+ const commonDir = gitRevParse('--git-common-dir', dir);
72
+ return gitDir !== commonDir;
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+
78
+ /** Feature name from the current branch, if it matches `morph/{feature}`. */
79
+ function featureFromBranch(dir) {
80
+ try {
81
+ const branch = gitRevParse('--abbrev-ref HEAD', dir);
82
+ const match = branch.match(/^morph\/(.+)$/);
83
+ return match ? match[1] : null;
84
+ } catch {
85
+ return null;
86
+ }
87
+ }
88
+
89
+ /** Fallback: if exactly one feature directory exists, assume that's the one. */
90
+ function featureFromSingleDir(dir) {
91
+ try {
92
+ const entries = readdirSync(join(dir, '.morph', 'features'), { withFileTypes: true })
93
+ .filter((e) => e.isDirectory());
94
+ return entries.length === 1 ? entries[0].name : null;
95
+ } catch {
96
+ return null;
97
+ }
98
+ }
99
+
100
+ function buildWorktreeContext(dir, feature) {
101
+ const phase = getFeaturePhase(feature, dir);
102
+ let text = `🌳 Worktree desta feature: ${feature} (branch morph/${feature}, fase ${phase}). ` +
103
+ `Trabalhe apenas nesta feature nesta sessão; o estado vive no .morph/ deste worktree.`;
104
+
105
+ const missingJunctions = !existsSync(join(dir, 'node_modules')) || !existsSync(join(dir, '.claude'));
106
+ if (missingJunctions) {
107
+ text += `\n⚠ Infra compartilhada ausente (node_modules/.claude). Rode: morph-spec worktree link --all`;
108
+ }
109
+
110
+ return text;
111
+ }
112
+
113
+ function buildActiveFeatureSummary(dir) {
114
+ const active = getActiveFeature(dir);
115
+ if (!active) return null;
116
+
117
+ const phase = getFeaturePhase(active.name, dir);
118
+ const stats = getTaskStats(active.feature);
119
+ const pending = getPendingGates(active.name, dir);
120
+
121
+ let text = `MORPH-SPEC: feature ativa ${active.name} (fase ${phase}) · tasks ${stats.completed}/${stats.total}`;
122
+ if (pending.length) {
123
+ text += ` · gates pendentes: ${pending.join(', ')}`;
124
+ }
125
+ return text;
126
+ }
127
+
128
+ async function main() {
129
+ try {
130
+ const dir = projectDir();
131
+ readStdin(); // consume the SessionStart payload (source/session_id) — unused here
132
+
133
+ if (!stateExists(dir)) {
134
+ pass();
135
+ return;
136
+ }
137
+
138
+ if (isSecondaryWorktree(dir)) {
139
+ const feature = featureFromBranch(dir) || featureFromSingleDir(dir);
140
+ if (!feature) {
141
+ pass();
142
+ return;
143
+ }
144
+ injectContext(buildWorktreeContext(dir, feature), 'SessionStart');
145
+ return;
146
+ }
147
+
148
+ // Primary root — active-feature summary (or silent if nothing's in progress)
149
+ const summary = buildActiveFeatureSummary(dir);
150
+ if (!summary) {
151
+ pass();
152
+ return;
153
+ }
154
+ injectContext(summary, 'SessionStart');
155
+ } catch {
156
+ pass();
157
+ }
158
+ }
159
+
160
+ main().catch(() => pass());