@polymorphism-tech/morph-spec 7.9.4 → 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.
- package/CLAUDE.md +2 -2
- package/README.md +31 -2
- package/bin/morph-spec.js +50 -2
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +38 -1
- package/docs/QUICKSTART.md +60 -1
- package/framework/CLAUDE.md +13 -2
- package/framework/MORPH.md +38 -2
- package/framework/agents.json +22 -3
- package/framework/commands/commit.md +166 -166
- package/framework/commands/morph-hotfix.md +39 -0
- package/framework/commands/morph-infra.md +215 -215
- package/framework/commands/morph-preflight.md +205 -205
- package/framework/commands/morph-troubleshoot.md +124 -124
- package/framework/hooks/README.md +25 -10
- package/framework/hooks/claude-code/core/gate-guard.js +4 -2
- package/framework/hooks/claude-code/core/loop-detect.js +2 -1
- package/framework/hooks/claude-code/core/post-edit-typecheck.js +14 -5
- package/framework/hooks/claude-code/core/tasks-json-guard.js +4 -2
- package/framework/hooks/claude-code/core/validate-completion.js +1 -1
- package/framework/hooks/claude-code/core/worktree-context.js +160 -0
- package/framework/hooks/claude-code/statusline.py +679 -679
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
- package/framework/hooks/git/pre-commit/agents.sh +25 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
- package/framework/hooks/git/pre-commit/specs.sh +50 -50
- package/framework/hooks/git/pre-push/run-tests.sh +44 -44
- package/framework/hooks/shared/activity-logger.js +160 -160
- package/framework/hooks/shared/build-check.js +2 -2
- package/framework/hooks/shared/hook-response.js +17 -5
- package/framework/hooks/shared/payload-utils.js +42 -0
- package/framework/hooks/shared/state-reader.js +22 -2
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/rubrics/backend.json +41 -41
- package/framework/rules/ai-agents-standards.md +45 -45
- package/framework/rules/dotnet-templates.md +86 -86
- package/framework/rules/frontend-standards.md +13 -13
- package/framework/rules/infrastructure-standards.md +27 -27
- package/framework/rules/neon-standards.md +25 -25
- package/framework/rules/nextjs-standards.md +20 -20
- package/framework/rules/nextjs-templates.md +40 -40
- package/framework/schemas/feature.schema.json +95 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +39 -8
- package/framework/skills/core/morph-brainstorming/SKILL.md +9 -1
- package/framework/skills/core/morph-eval/SKILL.md +6 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +136 -0
- package/framework/skills/core/morph-implement/SKILL.md +8 -4
- package/framework/skills/core/morph-plan/SKILL.md +5 -1
- package/framework/skills/core/morph-proposal/SKILL.md +24 -1
- package/framework/skills/core/morph-review/SKILL.md +3 -2
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/standards/ai-agents/agent-session.md +160 -160
- package/framework/standards/ai-agents/batching.md +193 -193
- package/framework/standards/ai-agents/context-providers.md +188 -188
- package/framework/standards/ai-agents/mcp-server.md +250 -250
- package/framework/standards/ai-agents/mcp-tools.md +197 -197
- package/framework/standards/ai-agents/middleware-patterns.md +207 -207
- package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
- package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
- package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
- package/framework/standards/ai-agents/modalities-vision.md +201 -201
- package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
- package/framework/standards/ai-agents/production.md +275 -275
- package/framework/standards/ai-agents/prompts-in-db.md +214 -214
- package/framework/standards/ai-agents/providers/model-registry.md +158 -158
- package/framework/standards/ai-agents/service-tier-flex.md +135 -135
- package/framework/standards/ai-agents/structured-output.md +249 -249
- package/framework/standards/ai-agents/sweet-spot.md +109 -109
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +0 -62
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
- package/framework/templates/code/dotnet/database/migration.cs +83 -83
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/code/typescript/contracts.ts +167 -167
- package/framework/templates/docs/clarifications.md +253 -253
- package/framework/templates/docs/schema-analysis.md +119 -119
- package/framework/templates/docs/ui-components.md +52 -52
- package/framework/templates/docs/ui-design-system.md +76 -76
- package/framework/templates/docs/ui-flows.md +167 -167
- package/framework/templates/docs/ui-mockups.md +98 -98
- package/framework/templates/feature/decisions.md +122 -122
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -50
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/README.md +56 -56
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/integrations/asaas-client.cs +387 -387
- package/framework/templates/integrations/asaas-webhook.cs +351 -351
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
- package/framework/templates/project/validate.js +120 -120
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/dashboard/dashboard.js +53 -53
- package/src/commands/index.js +13 -13
- package/src/commands/mcp/mcp-setup.js +519 -519
- package/src/commands/project/checkpoint.js +209 -209
- package/src/commands/project/doctor.js +4 -2
- package/src/commands/project/init.js +46 -2
- package/src/commands/project/update.js +5 -3
- package/src/commands/project/worktree.js +1005 -46
- package/src/commands/state/advance.js +17 -2
- package/src/commands/state/approve.js +18 -1
- package/src/commands/state/create.js +158 -14
- package/src/commands/state/retype.js +126 -0
- package/src/commands/state/score.js +4 -0
- package/src/commands/tasks/dag.js +32 -0
- package/src/commands/validation/index.js +1 -0
- package/src/commands/validation/validate-feature.js +33 -5
- package/src/commands/validation/verify.js +190 -0
- package/src/core/state/phase-state-machine.js +197 -197
- package/src/core/state/state-manager.js +35 -10
- package/src/core/templates/template-renderer.js +477 -477
- package/src/core/work-types.js +79 -0
- package/src/dashboard/app/components/DetailPanel.jsx +11 -11
- package/src/dashboard/app/components/FlowGraph.jsx +17 -17
- package/src/dashboard/app/components/SearchFilter.jsx +22 -22
- package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +370 -370
- package/src/dashboard/app/pages/HookInspector.jsx +418 -418
- package/src/dashboard/app/pages/LiveState.jsx +11 -1
- package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
- package/src/dashboard/app/pages/RulesMap.jsx +273 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
- package/src/dashboard/app/pages/Standards.jsx +332 -332
- package/src/dashboard/app/styles/index.css +47 -47
- package/src/dashboard/index.html +12 -12
- package/src/dashboard/package.json +24 -24
- package/src/dashboard/scanner/hooks-scanner.js +41 -41
- package/src/dashboard/scanner/phases-scanner.js +15 -15
- package/src/dashboard/scanner/rubrics-scanner.js +12 -12
- package/src/dashboard/scanner/rules-scanner.js +37 -37
- package/src/dashboard/scanner/skills-scanner.js +43 -43
- package/src/dashboard/scanner/squads-scanner.js +13 -13
- package/src/dashboard/scanner/standards-scanner.js +26 -26
- package/src/dashboard/scanner/state-scanner.js +42 -0
- package/src/dashboard/vite-plugin-morph-data.js +17 -17
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/agents/metrics-aggregator.js +56 -56
- package/src/lib/agents/metrics-calculator.js +125 -125
- package/src/lib/agents/output-collector.js +73 -73
- package/src/lib/detectors/claude-config-detector.js +152 -152
- package/src/lib/detectors/design-system-detector.js +231 -231
- package/src/lib/detectors/index.js +217 -217
- package/src/lib/generators/architecture-scanner.js +639 -639
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/index.js +13 -13
- package/src/lib/generators/settings-generator.js +209 -209
- package/src/lib/hooks/hook-executor.js +167 -167
- package/src/lib/installers/mcp-installer.js +719 -719
- package/src/lib/installers/memory-installer.js +1 -1
- package/src/lib/risk-detector.js +16 -0
- package/src/lib/tasks/dispatch-planner.js +122 -4
- package/src/lib/tasks/mermaid-renderer.js +132 -0
- package/src/lib/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- package/src/lib/tasks/test-runner.js +27 -6
- package/src/lib/validators/architecture/architecture-validator.js +259 -259
- package/src/lib/validators/checkpoint-persistence.js +39 -39
- package/src/lib/validators/contracts/contract-compliance-validator.js +274 -274
- package/src/lib/validators/css/css-validator.js +340 -340
- package/src/lib/validators/packages/package-validator.js +347 -347
- package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
- package/src/lib/validators/shared/issue-counter.js +14 -14
- package/src/lib/validators/validation-runner.js +349 -349
- package/src/lib/validators/verify-runner.js +322 -0
- package/src/lib/work-type-detector.js +143 -0
- package/src/scripts/setup-infra.js +32 -5
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/agents-installer.js +10 -6
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/file-copier.js +43 -0
- package/src/utils/hooks-installer.js +41 -11
- package/src/utils/skills-installer.js +174 -174
- package/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +0 -113
- package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +0 -80
- 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
|
-
│ ├──
|
|
11
|
-
│ │ └──
|
|
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
|
-
|
|
151
|
-
├── No state.json → silent exit
|
|
152
|
-
├──
|
|
153
|
-
│
|
|
154
|
-
│
|
|
155
|
-
│
|
|
156
|
-
│
|
|
157
|
-
|
|
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
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
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());
|