@polymorphism-tech/morph-spec 4.22.0 → 5.0.1
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 +57 -71
- package/README.md +2 -2
- package/bin/morph-spec.js +224 -491
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +55 -38
- package/framework/MORPH.md +244 -0
- package/framework/agents.json +127 -1198
- package/framework/commands/morph-apply.md +56 -240
- package/framework/commands/morph-archive.md +48 -85
- package/framework/commands/morph-preflight.md +7 -7
- package/framework/commands/morph-proposal.md +35 -191
- package/framework/commands/morph-status.md +19 -50
- package/framework/commands/morph-troubleshoot.md +2 -2
- package/framework/evals/rubrics/README.md +44 -0
- package/framework/evals/rubrics/architecture.md +46 -0
- package/framework/evals/rubrics/code-quality.md +50 -0
- package/framework/evals/rubrics/contracts.md +47 -0
- package/framework/evals/rubrics/test-coverage.md +50 -0
- package/framework/evals/scenarios/dotnet-crud.yaml +42 -0
- package/framework/evals/scenarios/nextjs-auth.yaml +38 -0
- package/framework/evals/suites/standard.yaml +5 -0
- package/framework/evals/trace-schema.md +67 -0
- package/framework/hooks/claude-code/core/README.md +3 -0
- package/framework/hooks/claude-code/core/denylist.json +14 -0
- package/framework/hooks/claude-code/{pre-tool-use → core}/enforce-phase-writes.js +5 -5
- package/framework/hooks/claude-code/core/post-edit-typecheck.js +69 -0
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +29 -0
- package/framework/hooks/claude-code/{pre-tool-use → core}/protect-spec-files.js +4 -6
- package/framework/hooks/claude-code/{stop → core}/validate-completion.js +13 -82
- package/framework/hooks/shared/phase-utils.js +20 -31
- package/framework/hooks/shared/state-reader.js +4 -5
- package/framework/{skills/level-0-meta/mcp-registry.json → mcp/registry.json} +254 -243
- package/framework/phases.json +41 -115
- package/framework/rubrics/eval-rubric.json +66 -0
- package/framework/skills/README.md +39 -64
- package/framework/skills/core/README.md +3 -0
- package/framework/skills/core/morph-brainstorming/SKILL.md +102 -0
- package/framework/skills/core/morph-eval/SKILL.md +57 -0
- package/framework/skills/core/morph-implement/SKILL.md +83 -0
- package/framework/skills/core/morph-plan/SKILL.md +64 -0
- package/framework/skills/core/morph-review/SKILL.md +62 -0
- package/framework/skills/core/morph-stack-scan/SKILL.md +109 -0
- package/framework/skills/core/morph-standards/SKILL.md +49 -0
- package/framework/templates/CLAUDE.md.core.guide.md +29 -0
- package/framework/templates/CLAUDE.md.core.template +22 -0
- package/framework/templates/REGISTRY.json +4 -4
- package/framework/workflows/configs/fast-track.json +5 -10
- package/framework/workflows/configs/full.json +4 -8
- package/framework/workflows/configs/quick.json +32 -0
- package/framework/workflows/configs/standard.json +57 -64
- package/package.json +7 -6
- package/src/commands/mcp/mcp-setup.js +58 -31
- package/src/commands/project/doctor.js +69 -70
- package/src/commands/project/init.js +26 -5
- package/src/commands/project/status.js +18 -19
- package/src/commands/state/approve.js +1 -1
- package/src/commands/validation/validate-feature.js +18 -112
- package/src/core/paths/output-schema.js +133 -152
- package/src/core/state/phase-state-machine.js +10 -24
- package/src/core/state/state-manager.js +17 -17
- package/src/core/workflows/workflow-detector.js +214 -31
- package/src/core/workflows/workflow-router.js +204 -0
- package/src/dashboard/app/pages/AgentMap.jsx +32 -33
- package/src/dashboard/app/pages/LiveState.jsx +1 -1
- package/src/dashboard/app/pages/PhasePipeline.jsx +2 -4
- package/src/dashboard/app/pages/SkillsLibrary.jsx +2 -3
- package/src/dashboard/app/pages/Workflows.jsx +2 -2
- package/src/dashboard/scanner/agents-scanner.js +30 -35
- package/src/lib/agents/metrics-aggregator.js +1 -1
- package/src/lib/agents/output-collector.js +7 -9
- package/src/lib/context/context-builder.js +289 -0
- package/src/lib/detectors/claude-config-detector.js +19 -3
- package/src/lib/generators/context-generator.js +20 -42
- package/src/lib/generators/index.js +1 -0
- package/src/lib/generators/mandate-generator.js +512 -0
- package/src/lib/generators/metadata-extractor.js +1 -1
- package/src/lib/generators/recap-generator.js +3 -4
- package/src/lib/installers/mcp-installer.js +300 -92
- package/src/lib/phase-chain/phase-validator.js +36 -40
- package/src/lib/stack/stack-profile.js +74 -91
- package/src/lib/tasks/task-parser.js +1 -1
- package/src/lib/tasks/wave-resolver.js +353 -0
- package/src/lib/validators/checkpoint-persistence.js +2 -2
- package/src/lib/validators/content/cross-phase-validator.js +281 -0
- package/src/lib/validators/content/index.js +1 -0
- package/src/lib/validators/shared/emit-validator-dispatch.js +6 -56
- package/src/scripts/global-install.js +2 -27
- package/src/utils/agents-installer.js +224 -447
- package/src/utils/claude-settings-manager.js +57 -15
- package/src/utils/file-copier.js +1 -0
- package/src/utils/hooks-installer.js +15 -117
- package/src/utils/skills-installer.js +12 -18
- package/bin/detect-agents.js +0 -226
- package/bin/task-manager.js +0 -1036
- package/framework/agents/README.md +0 -13
- package/framework/agents/ai-agents/ai-system-architect.md +0 -194
- package/framework/agents/ai-agents/vector-search-expert.md +0 -247
- package/framework/agents/architecture/architecture-expert.md +0 -98
- package/framework/agents/architecture/morph-spec-architect.md +0 -66
- package/framework/agents/architecture/packages-validator.md +0 -89
- package/framework/agents/architecture/security-expert.md +0 -79
- package/framework/agents/architecture/seo-growth-hacker.md +0 -168
- package/framework/agents/architecture/standards-architect.md +0 -153
- package/framework/agents/backend/dotnet-senior.md +0 -163
- package/framework/agents/backend/ef-modeler.md +0 -119
- package/framework/agents/backend/hangfire-orchestrator.md +0 -133
- package/framework/agents/backend/ms-agent-expert.md +0 -55
- package/framework/agents/backend/neon-expert.md +0 -379
- package/framework/agents/frontend/css-specialist.md +0 -89
- package/framework/agents/frontend/design-system-validator.md +0 -100
- package/framework/agents/frontend/gsap-animator.md +0 -131
- package/framework/agents/frontend/nextjs-component-validator.md +0 -97
- package/framework/agents/frontend/nextjs-expert.md +0 -201
- package/framework/agents/frontend/ui-designer.md +0 -259
- package/framework/agents/infrastructure/infra-architect.md +0 -215
- package/framework/agents/integrations/asaas-financial.md +0 -138
- package/framework/agents/integrations/neon-auth.md +0 -151
- package/framework/agents/integrations/resend-email.md +0 -127
- package/framework/hooks/claude-code/post-tool-use/agent-complete.js +0 -122
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +0 -363
- package/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +0 -78
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +0 -194
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +0 -199
- package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +0 -55
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +0 -323
- package/framework/hooks/claude-code/session-start/post-compact-restore.js +0 -41
- package/framework/hooks/claude-code/session-start/resume-context.js +0 -77
- package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +0 -90
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +0 -114
- package/framework/hooks/shared/compact-restore.js +0 -123
- package/framework/hooks/shared/dispatch-helpers.js +0 -147
- package/framework/hooks/shared/skill-reminder-helpers.js +0 -79
- package/framework/hooks/shared/stale-task-reset.js +0 -57
- package/framework/hooks/shared/worktree-helpers.js +0 -75
- package/framework/skills/level-0-meta/README.md +0 -7
- package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +0 -472
- package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +0 -174
- package/framework/skills/level-0-meta/morph-brainstorming/references/proposal-example.md +0 -138
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +0 -145
- package/framework/skills/level-0-meta/morph-code-review/SKILL.md +0 -250
- package/framework/skills/level-0-meta/morph-code-review/references/review-example.md +0 -164
- package/framework/skills/level-0-meta/morph-code-review/references/review-guidelines.md +0 -100
- package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +0 -151
- package/framework/skills/level-0-meta/morph-code-review-nextjs/SKILL.md +0 -165
- package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +0 -254
- package/framework/skills/level-0-meta/morph-code-review-nextjs/scripts/scan-nextjs.mjs +0 -189
- package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +0 -344
- package/framework/skills/level-0-meta/morph-frontend-review/scripts/scan-accessibility.mjs +0 -376
- package/framework/skills/level-0-meta/morph-gemini-image/SKILL.md +0 -290
- package/framework/skills/level-0-meta/morph-init/SKILL.md +0 -537
- package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +0 -225
- package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +0 -401
- package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-dev-server.mjs +0 -153
- package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-stack.mjs +0 -234
- package/framework/skills/level-0-meta/morph-simulation-checklist/SKILL.md +0 -106
- package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +0 -243
- package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +0 -169
- package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +0 -148
- package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +0 -110
- package/framework/skills/level-1-workflows/README.md +0 -7
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +0 -269
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +0 -453
- package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +0 -44
- package/framework/skills/level-1-workflows/morph-phase-design/references/spec-authoring-guide.md +0 -55
- package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +0 -253
- package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +0 -38
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +0 -634
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +0 -50
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +0 -54
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +0 -47
- package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +0 -132
- package/framework/skills/level-1-workflows/morph-phase-implement/references/vsa-implementation-guide.md +0 -92
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +0 -257
- package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +0 -14
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +0 -291
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -143
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +0 -404
- package/framework/skills/level-1-workflows/morph-phase-tasks/scripts/validate-tasks.mjs +0 -112
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +0 -995
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +0 -147
- package/framework/squad-templates/backend-only.json +0 -47
- package/framework/squad-templates/frontend-only.json +0 -56
- package/framework/squad-templates/full-stack.json +0 -74
- package/src/commands/agents/agent-metrics.js +0 -174
- package/src/commands/agents/audit-dispatch.js +0 -82
- package/src/commands/agents/detect-agents.js +0 -275
- package/src/commands/agents/dispatch-agents.js +0 -461
- package/src/commands/agents/index.js +0 -13
- package/src/commands/agents/manage-agents.js +0 -352
- package/src/commands/agents/project-metrics.js +0 -74
- package/src/commands/phase/phase-reset.js +0 -74
- package/src/commands/scope/escalate.js +0 -217
- package/src/commands/state/advance-phase.js +0 -600
- package/src/commands/state/phase-runner.js +0 -215
- package/src/commands/state/state.js +0 -444
- package/src/commands/task/expand.js +0 -100
- package/src/commands/tasks/index.js +0 -4
- package/src/commands/tasks/task.js +0 -105
- package/src/commands/templates/index.js +0 -5
- package/src/commands/templates/template-render.js +0 -93
- package/src/commands/trust/trust.js +0 -118
- package/src/lib/agents/agents-manager.js +0 -349
- package/src/lib/build/build-runner.js +0 -99
- package/src/lib/checkpoints/checkpoint-hooks.js +0 -111
- package/src/lib/contracts/sprint-contract-generator.js +0 -137
- package/src/lib/scope/impact-analyzer.js +0 -106
- package/src/lib/trust/trust-manager.js +0 -157
- package/src/utils/tier-helpers.js +0 -24
- /package/framework/hooks/claude-code/{post-tool-use → core}/loop-detect.js +0 -0
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* PreCompact Hook: Inject MORPH Context into Compact Summary
|
|
5
|
-
*
|
|
6
|
-
* Event: PreCompact
|
|
7
|
-
*
|
|
8
|
-
* Injects a rich morph-spec state snapshot as additionalContext so that
|
|
9
|
-
* Claude's compacted summary includes feature state, task progress, outputs,
|
|
10
|
-
* and next steps. Ensures the session resumes with full morph awareness
|
|
11
|
-
* without having to re-read all the spec files.
|
|
12
|
-
*
|
|
13
|
-
* Also saves snapshot to .morph/memory/ for SessionStart to reference.
|
|
14
|
-
*
|
|
15
|
-
* Fail-open: exits 0 on any error.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { writeFileSync, mkdirSync, existsSync, readFileSync } from 'fs';
|
|
19
|
-
import { join } from 'path';
|
|
20
|
-
import {
|
|
21
|
-
stateExists, loadState, getActiveFeature,
|
|
22
|
-
derivePhaseForFeature, getPendingGates, getOutputs,
|
|
23
|
-
} from '../../shared/state-reader.js';
|
|
24
|
-
import { injectContext, pass } from '../../shared/hook-response.js';
|
|
25
|
-
import { logHookActivity } from '../../shared/activity-logger.js';
|
|
26
|
-
import { buildRichContext, DECISIONS_MAX_CHARS, MAX_PENDING_TASKS } from '../../shared/compact-restore.js';
|
|
27
|
-
|
|
28
|
-
const PHASE_POSITIONS = {
|
|
29
|
-
proposal: 1,
|
|
30
|
-
uiux: 2, design: 2,
|
|
31
|
-
plan: 3,
|
|
32
|
-
tasks: 4,
|
|
33
|
-
implement: 5, sync: 5,
|
|
34
|
-
};
|
|
35
|
-
const PIPELINE_TOTAL = 5;
|
|
36
|
-
|
|
37
|
-
const NEXT_STEP_HINT = {
|
|
38
|
-
proposal: (n) => `Continue spec pipeline: /morph-proposal ${n}`,
|
|
39
|
-
design: (n) => `Approve design gate: morph-spec approve ${n} design`,
|
|
40
|
-
uiux: (n) => `Approve UI gate: morph-spec approve ${n} uiux`,
|
|
41
|
-
tasks: (n) => `Approve tasks gate: morph-spec approve ${n} tasks`,
|
|
42
|
-
implement: (n) => `Continue implementation: morph-spec task start ${n} <next-task-id>`,
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
if (!stateExists()) pass();
|
|
47
|
-
|
|
48
|
-
const state = loadState();
|
|
49
|
-
if (!state) pass();
|
|
50
|
-
|
|
51
|
-
const active = getActiveFeature();
|
|
52
|
-
const cwd = process.cwd();
|
|
53
|
-
|
|
54
|
-
// ── Disk snapshot (for SessionStart restore) ──────────────────────────────
|
|
55
|
-
const snapshot = {
|
|
56
|
-
timestamp: new Date().toISOString(),
|
|
57
|
-
event: 'pre-compact',
|
|
58
|
-
activeFeature: active ? active.name : null,
|
|
59
|
-
features: {},
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
for (const [name, feature] of Object.entries(state.features || {})) {
|
|
63
|
-
snapshot.features[name] = {
|
|
64
|
-
phase: feature.phase || derivePhaseForFeature(name),
|
|
65
|
-
status: feature.status,
|
|
66
|
-
workflow: feature.workflow,
|
|
67
|
-
tasks: feature.tasks,
|
|
68
|
-
approvalGates: Object.fromEntries(
|
|
69
|
-
Object.entries(feature.approvalGates || {}).map(([gate, data]) => [gate, data.approved])
|
|
70
|
-
),
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ── Enrich snapshot with richContext (decisions + taskList) ───────────────
|
|
75
|
-
if (active) {
|
|
76
|
-
try {
|
|
77
|
-
const { name: activeName, feature: activeFeature } = active;
|
|
78
|
-
const activePhase = derivePhaseForFeature(activeName);
|
|
79
|
-
let decisionsContent = '';
|
|
80
|
-
const decisionsPath = join(cwd, `.morph/features/${activeName}/2-design/decisions.md`);
|
|
81
|
-
if (existsSync(decisionsPath)) {
|
|
82
|
-
try { decisionsContent = readFileSync(decisionsPath, 'utf-8'); } catch { /* ignore */ }
|
|
83
|
-
}
|
|
84
|
-
let specContent = '';
|
|
85
|
-
const specPath2 = join(cwd, `.morph/features/${activeName}/2-design/spec.md`);
|
|
86
|
-
if (existsSync(specPath2)) {
|
|
87
|
-
try { specContent = readFileSync(specPath2, 'utf-8'); } catch { /* ignore */ }
|
|
88
|
-
}
|
|
89
|
-
snapshot.richContext = buildRichContext(activeFeature, activePhase, decisionsContent, specContent);
|
|
90
|
-
} catch { /* fail-open: richContext is optional */ }
|
|
91
|
-
}
|
|
92
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
93
|
-
|
|
94
|
-
const memoryDir = join(cwd, '.morph', 'memory');
|
|
95
|
-
if (!existsSync(memoryDir)) mkdirSync(memoryDir, { recursive: true });
|
|
96
|
-
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
97
|
-
writeFileSync(join(memoryDir, `pre-compact-${ts}.json`), JSON.stringify(snapshot, null, 2), 'utf-8');
|
|
98
|
-
|
|
99
|
-
// ── Nothing more to inject if no active feature ───────────────────────────
|
|
100
|
-
if (!active) pass();
|
|
101
|
-
|
|
102
|
-
const { name, feature } = active;
|
|
103
|
-
const phase = derivePhaseForFeature(name);
|
|
104
|
-
const pos = PHASE_POSITIONS[phase];
|
|
105
|
-
const phaseLabel = pos ? `${phase} (${pos}/${PIPELINE_TOTAL})` : phase;
|
|
106
|
-
|
|
107
|
-
const lines = [
|
|
108
|
-
'=== MORPH-SPEC COMPACT CONTEXT ===',
|
|
109
|
-
`Preserve this section verbatim in the compact summary.`,
|
|
110
|
-
'',
|
|
111
|
-
`Feature : ${name}`,
|
|
112
|
-
`Phase : ${phaseLabel}`,
|
|
113
|
-
`Status : ${feature.status} | Workflow: ${feature.workflow || 'auto'}`,
|
|
114
|
-
];
|
|
115
|
-
|
|
116
|
-
// Task progress
|
|
117
|
-
const tasks = feature.tasks || {};
|
|
118
|
-
const done = tasks.completed || 0;
|
|
119
|
-
const total = tasks.total || 0;
|
|
120
|
-
if (total > 0) {
|
|
121
|
-
const pct = Math.round(done / total * 100);
|
|
122
|
-
lines.push(`Tasks : ${done}/${total} completed (${pct}%)`);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// In-progress + next pending tasks from taskList
|
|
126
|
-
const taskList = Array.isArray(feature.taskList) ? feature.taskList : [];
|
|
127
|
-
const inProg = taskList.filter(t => t.status === 'in_progress');
|
|
128
|
-
const pending = taskList.filter(t => t.status === 'pending').slice(0, MAX_PENDING_TASKS);
|
|
129
|
-
|
|
130
|
-
if (inProg.length > 0) {
|
|
131
|
-
lines.push('');
|
|
132
|
-
lines.push('In progress:');
|
|
133
|
-
for (const t of inProg) lines.push(` [${t.id}] ${t.title}`);
|
|
134
|
-
}
|
|
135
|
-
if (pending.length > 0) {
|
|
136
|
-
lines.push('');
|
|
137
|
-
lines.push('Next pending tasks:');
|
|
138
|
-
for (const t of pending) lines.push(` [${t.id}] ${t.title}`);
|
|
139
|
-
if (taskList.filter(t => t.status === 'pending').length > MAX_PENDING_TASKS) {
|
|
140
|
-
lines.push(` ... (see tasks.md for full list)`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Created outputs
|
|
145
|
-
const outputs = getOutputs(name) || {};
|
|
146
|
-
const created = Object.entries(outputs).filter(([, o]) => o.created);
|
|
147
|
-
const missing = Object.entries(outputs).filter(([, o]) => !o.created);
|
|
148
|
-
if (created.length > 0) {
|
|
149
|
-
lines.push('');
|
|
150
|
-
lines.push('Created outputs:');
|
|
151
|
-
for (const [type, o] of created) lines.push(` ✓ ${type}: ${o.path}`);
|
|
152
|
-
}
|
|
153
|
-
if (missing.length > 0) {
|
|
154
|
-
lines.push('Pending outputs:');
|
|
155
|
-
for (const [type, o] of missing) lines.push(` ✗ ${type}: ${o.path}`);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Active agents
|
|
159
|
-
const agents = feature.activeAgents || [];
|
|
160
|
-
if (agents.length > 0) {
|
|
161
|
-
lines.push('');
|
|
162
|
-
lines.push(`Active agents: ${agents.join(', ')}`);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Pending approval gates
|
|
166
|
-
const pendingGates = getPendingGates(name);
|
|
167
|
-
if (pendingGates.length > 0) {
|
|
168
|
-
lines.push(`Pending approvals: ${pendingGates.join(', ')}`);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Key decisions (truncated to stay within context budget)
|
|
172
|
-
const decisionsPath = join(cwd, `.morph/features/${name}/2-design/decisions.md`);
|
|
173
|
-
if (existsSync(decisionsPath)) {
|
|
174
|
-
try {
|
|
175
|
-
const raw = readFileSync(decisionsPath, 'utf-8');
|
|
176
|
-
const content = raw.length > DECISIONS_MAX_CHARS
|
|
177
|
-
? raw.slice(0, DECISIONS_MAX_CHARS) + '\n[... truncated — full file at decisions.md]'
|
|
178
|
-
: raw;
|
|
179
|
-
lines.push('');
|
|
180
|
-
lines.push('Key decisions (decisions.md):');
|
|
181
|
-
lines.push(content);
|
|
182
|
-
} catch { /* non-blocking */ }
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Next step
|
|
186
|
-
const hintFn = NEXT_STEP_HINT[phase];
|
|
187
|
-
if (hintFn) {
|
|
188
|
-
lines.push('');
|
|
189
|
-
lines.push(`Next step: ${hintFn(name)}`);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
lines.push('');
|
|
193
|
-
lines.push('=== END MORPH CONTEXT ===');
|
|
194
|
-
|
|
195
|
-
logHookActivity('save-morph-context', 'PreCompact', 'context_saved');
|
|
196
|
-
injectContext(lines.join('\n'));
|
|
197
|
-
} catch {
|
|
198
|
-
process.exit(0);
|
|
199
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* PreToolUse Hook: Advisory Task Tracking Guard
|
|
5
|
-
*
|
|
6
|
-
* Event: PreToolUse | Matcher: Write|Edit
|
|
7
|
-
*
|
|
8
|
-
* During implement phase, injects a warning if no task is currently in_progress.
|
|
9
|
-
* Advisory only (type: approve with context) — never blocks.
|
|
10
|
-
*
|
|
11
|
-
* Fail-open: exits 0 on any error.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { readStdin } from '../../shared/stdin-reader.js';
|
|
15
|
-
import { stateExists, loadState, getActiveFeature, derivePhaseForFeature } from '../../shared/state-reader.js';
|
|
16
|
-
import { approve, pass } from '../../shared/hook-response.js';
|
|
17
|
-
import { getFilePath } from '../../shared/payload-utils.js';
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
if (!stateExists()) pass();
|
|
21
|
-
|
|
22
|
-
const payload = await readStdin();
|
|
23
|
-
if (!payload) pass();
|
|
24
|
-
|
|
25
|
-
const filePath = getFilePath(payload);
|
|
26
|
-
if (!filePath) pass();
|
|
27
|
-
|
|
28
|
-
// Skip files inside .morph/ — internal framework writes don't need task tracking
|
|
29
|
-
if (filePath.includes('.morph/') || filePath.includes('.morph\\')) pass();
|
|
30
|
-
|
|
31
|
-
// Only check during implement phase
|
|
32
|
-
const active = getActiveFeature();
|
|
33
|
-
if (!active) pass();
|
|
34
|
-
|
|
35
|
-
const { name, feature } = active;
|
|
36
|
-
const phase = feature.phase || derivePhaseForFeature(name);
|
|
37
|
-
if (phase !== 'implement') pass();
|
|
38
|
-
|
|
39
|
-
// Check if any task is in_progress (read directly from taskList)
|
|
40
|
-
if (feature.taskList?.some(t => t.status === 'in_progress')) pass();
|
|
41
|
-
// Fallback: legacy counter check
|
|
42
|
-
if (feature.tasks?.inProgress > 0) pass();
|
|
43
|
-
|
|
44
|
-
// No task in_progress — inject advisory warning
|
|
45
|
-
approve(
|
|
46
|
-
`[morph-spec] WARNING: No active task tracking detected during implement phase.\n` +
|
|
47
|
-
`Before writing code, start a task:\n` +
|
|
48
|
-
` morph-spec task start ${name} <id> # mark it in progress\n` +
|
|
49
|
-
`Task tracking ensures validators run, checkpoints fire, and progress is recorded.\n` +
|
|
50
|
-
`If a task is more complex than expected, use: morph-spec task expand ${name} <id> --into "T00Xa: Sub" "T00Xb: Sub"`
|
|
51
|
-
);
|
|
52
|
-
} catch {
|
|
53
|
-
// Fail-open
|
|
54
|
-
process.exit(0);
|
|
55
|
-
}
|
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SessionStart Hook: Inject MORPH-SPEC Context
|
|
5
|
-
*
|
|
6
|
-
* Event: SessionStart | Matcher: startup|resume|compact
|
|
7
|
-
*
|
|
8
|
-
* Reads state.json and injects a summary as additionalContext so Claude
|
|
9
|
-
* knows the current morph-spec state at session start.
|
|
10
|
-
*
|
|
11
|
-
* Fail-open: exits 0 on any error.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { loadState, getActiveFeature, getPendingGates, getMissingOutputs, derivePhaseForFeature, getTaskStats } from '../../shared/state-reader.js';
|
|
15
|
-
import { loadSessionSnapshot, formatPreviousSession } from '../../shared/session-snapshot.js';
|
|
16
|
-
import { stateExists } from '../../shared/state-reader.js';
|
|
17
|
-
import { injectContext, pass } from '../../shared/hook-response.js';
|
|
18
|
-
import { resetActivity, logHookActivity, archiveSession, logEvent } from '../../shared/activity-logger.js';
|
|
19
|
-
import { readFileSync, existsSync, writeFileSync, renameSync, unlinkSync, statSync } from 'fs';
|
|
20
|
-
import { join } from 'path';
|
|
21
|
-
import { resetStaleTasks } from '../../shared/stale-task-reset.js';
|
|
22
|
-
import { execSync as _execSync } from 'child_process';
|
|
23
|
-
import { parseWorktreeResult, buildWorktreeContextLine, buildWorktreeResumeContext } from '../../shared/worktree-helpers.js';
|
|
24
|
-
|
|
25
|
-
const DEFAULT_SPEC_MAX_CHARS = 6000;
|
|
26
|
-
|
|
27
|
-
function getSpecMaxChars() {
|
|
28
|
-
try {
|
|
29
|
-
const configPath = join(process.cwd(), '.morph/config/config.json');
|
|
30
|
-
if (!existsSync(configPath)) return DEFAULT_SPEC_MAX_CHARS;
|
|
31
|
-
const cfg = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
32
|
-
return cfg?.hooks?.specInjectionLimit ?? DEFAULT_SPEC_MAX_CHARS;
|
|
33
|
-
} catch {
|
|
34
|
-
return DEFAULT_SPEC_MAX_CHARS;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function getProjectConfig() {
|
|
39
|
-
try {
|
|
40
|
-
const configPath = join(process.cwd(), '.morph/config/config.json');
|
|
41
|
-
if (!existsSync(configPath)) return null;
|
|
42
|
-
return JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
43
|
-
} catch {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const SPEC_MAX_CHARS = getSpecMaxChars();
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Atomic state save for hook context.
|
|
52
|
-
* Same approach as state-manager.js saveState — write to tmp, then rename.
|
|
53
|
-
* Fail-open: any error is silently ignored.
|
|
54
|
-
* @param {Object} state - Full state object
|
|
55
|
-
* @param {string} cwd - Project root path
|
|
56
|
-
*/
|
|
57
|
-
function saveStateSync(state, cwd) {
|
|
58
|
-
const statePath = join(cwd, '.morph/state.json');
|
|
59
|
-
const tmpPath = `${statePath}.tmp.hook.${process.pid}`;
|
|
60
|
-
try {
|
|
61
|
-
state.metadata = state.metadata || {};
|
|
62
|
-
state.metadata.lastUpdated = new Date().toISOString();
|
|
63
|
-
writeFileSync(tmpPath, JSON.stringify(state, null, 2), 'utf8');
|
|
64
|
-
renameSync(tmpPath, statePath);
|
|
65
|
-
} catch {
|
|
66
|
-
try { unlinkSync(tmpPath); } catch { /* ignore cleanup errors */ }
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
if (!stateExists()) pass();
|
|
72
|
-
|
|
73
|
-
const state = loadState();
|
|
74
|
-
if (!state?.features || Object.keys(state.features).length === 0) pass();
|
|
75
|
-
|
|
76
|
-
const active = getActiveFeature();
|
|
77
|
-
|
|
78
|
-
// Archive previous session log before resetting
|
|
79
|
-
const activeFeatureName = active?.name || '';
|
|
80
|
-
const activePhase = active ? derivePhaseForFeature(active.name) : '';
|
|
81
|
-
archiveSession();
|
|
82
|
-
|
|
83
|
-
// Reset activity log for new session
|
|
84
|
-
resetActivity(new Date().toISOString(), activeFeatureName, activePhase);
|
|
85
|
-
logEvent('session_start', { feature: activeFeatureName, phase: activePhase });
|
|
86
|
-
logHookActivity('inject-morph-context', 'SessionStart', 'ok');
|
|
87
|
-
|
|
88
|
-
// ── Stale task cleanup ──────────────────────────────────────────────────────
|
|
89
|
-
// Reset any in_progress tasks older than 1 hour — they are orphans from an
|
|
90
|
-
// interrupted session and are no longer being actively implemented.
|
|
91
|
-
const staleResetLog = resetStaleTasks(state);
|
|
92
|
-
if (staleResetLog.length > 0) {
|
|
93
|
-
saveStateSync(state, process.cwd());
|
|
94
|
-
}
|
|
95
|
-
// ────────────────────────────────────────────────────────────────────────────
|
|
96
|
-
|
|
97
|
-
const lines = ['MORPH-SPEC Status:'];
|
|
98
|
-
|
|
99
|
-
// ── Context freshness check ─────────────────────────────────────────────
|
|
100
|
-
// If .morph/context/README.md is missing or older than 24h, suggest refresh.
|
|
101
|
-
try {
|
|
102
|
-
const contextReadmePath = join(process.cwd(), '.morph/context/README.md');
|
|
103
|
-
let contextStale = false;
|
|
104
|
-
if (!existsSync(contextReadmePath)) {
|
|
105
|
-
contextStale = true;
|
|
106
|
-
} else {
|
|
107
|
-
const stat = statSync(contextReadmePath);
|
|
108
|
-
const ageMs = Date.now() - stat.mtimeMs;
|
|
109
|
-
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
|
|
110
|
-
contextStale = ageMs > TWENTY_FOUR_HOURS;
|
|
111
|
-
}
|
|
112
|
-
if (contextStale) {
|
|
113
|
-
lines.push('');
|
|
114
|
-
lines.push('⚠ Architecture context is stale or missing. Run: morph-spec context refresh');
|
|
115
|
-
}
|
|
116
|
-
} catch {
|
|
117
|
-
// Fail-open — context freshness check must never block the session
|
|
118
|
-
}
|
|
119
|
-
// ────────────────────────────────────────────────────────────────────────
|
|
120
|
-
|
|
121
|
-
if (staleResetLog.length > 0) {
|
|
122
|
-
lines.push(`⚠️ ${staleResetLog.length} task(s) auto-reset in_progress → pending (orphaned from previous session): ${staleResetLog.join(', ')}`);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (active) {
|
|
126
|
-
const { name, feature } = active;
|
|
127
|
-
const phase = derivePhaseForFeature(name);
|
|
128
|
-
lines.push(`- Active feature: ${name} (phase: ${phase}, workflow: ${feature.workflow || 'auto'})`);
|
|
129
|
-
lines.push(`- Status: ${feature.status}`);
|
|
130
|
-
|
|
131
|
-
// Task progress (read directly from taskList)
|
|
132
|
-
const stats = getTaskStats(feature);
|
|
133
|
-
if (stats.total > 0) {
|
|
134
|
-
lines.push(`- Tasks: ${stats.completed}/${stats.total} completed`);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Pending approvals
|
|
138
|
-
const pending = getPendingGates(name);
|
|
139
|
-
if (pending.length > 0) {
|
|
140
|
-
lines.push(`- Pending approvals: ${pending.join(', ')}`);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Next required output
|
|
144
|
-
const missing = getMissingOutputs(name);
|
|
145
|
-
if (missing.length > 0) {
|
|
146
|
-
const next = missing[0];
|
|
147
|
-
lines.push(`- Next required output: ${next.type} → ${next.path}`);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Checkpoints
|
|
151
|
-
if (feature.checkpoints?.length > 0) {
|
|
152
|
-
const lastCp = feature.checkpoints[feature.checkpoints.length - 1];
|
|
153
|
-
lines.push(`- Last checkpoint: #${lastCp.checkpointNum} (${lastCp.passed ? 'passed' : 'failed'})`);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Trust level visibility — warn when low trust may block approval gates
|
|
157
|
-
const APPROVAL_GATE_PHASES = new Set(['proposal', 'design', 'tasks']);
|
|
158
|
-
if (APPROVAL_GATE_PHASES.has(phase)) {
|
|
159
|
-
const config = getProjectConfig();
|
|
160
|
-
const trustLevel = config?.trust?.level || config?.trustLevel || 'low';
|
|
161
|
-
const workflow = feature.workflow || config?.workflow;
|
|
162
|
-
if (trustLevel !== 'high' && trustLevel !== 'maximum') {
|
|
163
|
-
lines.push('');
|
|
164
|
-
lines.push(`⚠ Trust level: ${trustLevel} — approval gates require manual approval`);
|
|
165
|
-
lines.push(` To auto-approve: morph-spec trust set ${name} high`);
|
|
166
|
-
}
|
|
167
|
-
if (!workflow && config?.workflowDetection?.confidence === 0) {
|
|
168
|
-
lines.push('');
|
|
169
|
-
lines.push('ℹ Workflow not detected (confidence: 0). Set manually in .morph/config/config.json → workflow');
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Active feature spec (truncated for context budget)
|
|
174
|
-
const specPath = join(process.cwd(), `.morph/features/${name}/2-design/spec.md`);
|
|
175
|
-
if (existsSync(specPath)) {
|
|
176
|
-
try {
|
|
177
|
-
const specContent = readFileSync(specPath, 'utf-8');
|
|
178
|
-
const truncated = specContent.length > SPEC_MAX_CHARS
|
|
179
|
-
? specContent.slice(0, SPEC_MAX_CHARS) + `\n\n[... spec truncated — full file at .morph/features/${name}/2-design/spec.md]`
|
|
180
|
-
: specContent;
|
|
181
|
-
lines.push('');
|
|
182
|
-
lines.push(`--- Active feature spec (${name}/2-design/spec.md) ---`);
|
|
183
|
-
lines.push(truncated);
|
|
184
|
-
lines.push('--- End spec ---');
|
|
185
|
-
} catch {
|
|
186
|
-
// Non-blocking: skip spec injection on read error
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// ── Worktree context injection ─────────────────────────────────────────────
|
|
191
|
-
// For the active feature, detect existing worktree from state metadata and
|
|
192
|
-
// inject MANDATORY instructions to work inside it.
|
|
193
|
-
// The worktree is created by `morph-proposal` Step 0 or `morph-apply` Step 0.
|
|
194
|
-
// This hook only injects the context — it does NOT create worktrees.
|
|
195
|
-
// Fail-open: any error is silently ignored.
|
|
196
|
-
try {
|
|
197
|
-
const worktreeMeta = feature.worktree;
|
|
198
|
-
if (worktreeMeta?.path) {
|
|
199
|
-
const contextLine = buildWorktreeResumeContext(worktreeMeta, name);
|
|
200
|
-
if (contextLine) {
|
|
201
|
-
lines.push('');
|
|
202
|
-
lines.push(contextLine);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
} catch {
|
|
206
|
-
// Fail-open — worktree context must never block the session
|
|
207
|
-
}
|
|
208
|
-
// ────────────────────────────────────────────────────────────────────────────
|
|
209
|
-
|
|
210
|
-
// ── Previous session context ──────────────────────────────────────────────
|
|
211
|
-
// Inject summary from previous session for "getting up to speed"
|
|
212
|
-
try {
|
|
213
|
-
const snapshot = loadSessionSnapshot(process.cwd());
|
|
214
|
-
const previousSessionText = formatPreviousSession(snapshot);
|
|
215
|
-
if (previousSessionText) {
|
|
216
|
-
lines.push('');
|
|
217
|
-
lines.push(previousSessionText);
|
|
218
|
-
}
|
|
219
|
-
} catch {
|
|
220
|
-
// Fail-open — previous session context must never block
|
|
221
|
-
}
|
|
222
|
-
// ────────────────────────────────────────────────────────────────────────
|
|
223
|
-
} else {
|
|
224
|
-
// Show summary of all features
|
|
225
|
-
const featureNames = Object.keys(state.features);
|
|
226
|
-
lines.push(`- Features: ${featureNames.length} (${featureNames.join(', ')})`);
|
|
227
|
-
|
|
228
|
-
for (const [fname, feat] of Object.entries(state.features)) {
|
|
229
|
-
const fPhase = derivePhaseForFeature(fname);
|
|
230
|
-
lines.push(` - ${fname}: phase=${fPhase}, status=${feat.status}`);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// ── MCP status injection ────────────────────────────────────────────────────
|
|
235
|
-
// Cross-reference configured MCPs with phase recommendations from mcp-registry.json.
|
|
236
|
-
// Fail-open: all inside try/catch, no crash on missing files.
|
|
237
|
-
try {
|
|
238
|
-
const currentPhase = active ? derivePhaseForFeature(active.name) : '';
|
|
239
|
-
if (currentPhase) {
|
|
240
|
-
// Read configured MCPs from settings
|
|
241
|
-
const configuredMcps = new Set();
|
|
242
|
-
const settingsFiles = [
|
|
243
|
-
join(process.cwd(), '.claude', 'settings.local.json'),
|
|
244
|
-
join(process.cwd(), '.claude', 'settings.json'),
|
|
245
|
-
];
|
|
246
|
-
for (const sf of settingsFiles) {
|
|
247
|
-
if (existsSync(sf)) {
|
|
248
|
-
try {
|
|
249
|
-
const s = JSON.parse(readFileSync(sf, 'utf8'));
|
|
250
|
-
for (const name of Object.keys(s.mcpServers || {})) {
|
|
251
|
-
configuredMcps.add(name.toLowerCase());
|
|
252
|
-
}
|
|
253
|
-
} catch { /* ignore */ }
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Read recommended MCPs from phases.json or mcp-registry.json
|
|
258
|
-
let recommended = [];
|
|
259
|
-
const registryPaths = [
|
|
260
|
-
join(process.cwd(), 'framework', 'skills', 'level-0-meta', 'mcp-registry.json'),
|
|
261
|
-
join(process.cwd(), '.morph', 'framework', 'skills', 'level-0-meta', 'mcp-registry.json'),
|
|
262
|
-
];
|
|
263
|
-
for (const rp of registryPaths) {
|
|
264
|
-
if (existsSync(rp)) {
|
|
265
|
-
try {
|
|
266
|
-
const registry = JSON.parse(readFileSync(rp, 'utf8'));
|
|
267
|
-
recommended = registry.phaseMatrix?.[currentPhase] || [];
|
|
268
|
-
break;
|
|
269
|
-
} catch { /* ignore */ }
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (recommended.length > 0) {
|
|
274
|
-
const mcpStatus = recommended.map(name => {
|
|
275
|
-
const isConfigured = configuredMcps.has(name.toLowerCase());
|
|
276
|
-
return `${name} ${isConfigured ? '✓' : '✗ (recommended)'}`;
|
|
277
|
-
});
|
|
278
|
-
lines.push('');
|
|
279
|
-
lines.push(`MCPs: ${mcpStatus.join(', ')}`);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
} catch {
|
|
283
|
-
// Fail-open — MCP status must never block the session
|
|
284
|
-
}
|
|
285
|
-
// ────────────────────────────────────────────────────────────────────────────
|
|
286
|
-
|
|
287
|
-
// ── CLI availability detection ─────────────────────────────────────────────
|
|
288
|
-
// Check which development CLIs are available for the current phase.
|
|
289
|
-
// Uses execSync with short timeout — fail-open on any error.
|
|
290
|
-
try {
|
|
291
|
-
const currentPhaseForCli = active ? derivePhaseForFeature(active.name) : '';
|
|
292
|
-
if (currentPhaseForCli) {
|
|
293
|
-
const cliChecks = [
|
|
294
|
-
{ name: 'docker', cmd: 'docker --version', phases: ['design', 'implement'] },
|
|
295
|
-
{ name: 'neonctl', cmd: 'neonctl --version', phases: ['design', 'implement'] },
|
|
296
|
-
{ name: 'psql', cmd: 'psql --version', phases: ['design', 'implement'] },
|
|
297
|
-
{ name: 'az', cmd: 'az version --output none', phases: ['implement'] },
|
|
298
|
-
{ name: 'gh', cmd: 'gh --version', phases: ['design', 'tasks', 'implement'] },
|
|
299
|
-
];
|
|
300
|
-
|
|
301
|
-
const relevant = cliChecks.filter(c => c.phases.includes(currentPhaseForCli));
|
|
302
|
-
if (relevant.length > 0) {
|
|
303
|
-
const cliStatus = relevant.map(c => {
|
|
304
|
-
try {
|
|
305
|
-
_execSync(c.cmd, { stdio: 'pipe', timeout: 5000 });
|
|
306
|
-
return `${c.name} ✓`;
|
|
307
|
-
} catch {
|
|
308
|
-
return `${c.name} ✗`;
|
|
309
|
-
}
|
|
310
|
-
});
|
|
311
|
-
lines.push(`CLIs: ${cliStatus.join(', ')}`);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
} catch {
|
|
315
|
-
// Fail-open — CLI detection must never block the session
|
|
316
|
-
}
|
|
317
|
-
// ────────────────────────────────────────────────────────────────────────────
|
|
318
|
-
|
|
319
|
-
injectContext(lines.join('\n'));
|
|
320
|
-
} catch {
|
|
321
|
-
// Fail-open
|
|
322
|
-
process.exit(0);
|
|
323
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SessionStart Hook: Post-Compact Context Restore
|
|
5
|
-
*
|
|
6
|
-
* Event: SessionStart | Matcher: compact
|
|
7
|
-
*
|
|
8
|
-
* Fires ONLY when a session starts after context compaction.
|
|
9
|
-
* Reads the latest pre-compact memory file and injects the richContext
|
|
10
|
-
* block (decisions.md snippet + task list) so Claude resumes with full
|
|
11
|
-
* morph awareness even if the compact summary was truncated.
|
|
12
|
-
*
|
|
13
|
-
* Fail-open: exits 0 on any error.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { existsSync, readdirSync, readFileSync } from 'fs';
|
|
17
|
-
import { join } from 'path';
|
|
18
|
-
import { injectContext, pass } from '../../shared/hook-response.js';
|
|
19
|
-
import { stateExists } from '../../shared/state-reader.js';
|
|
20
|
-
import { buildRestoreBlock, findLatestMemoryFile } from '../../shared/compact-restore.js';
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
if (!stateExists()) pass();
|
|
24
|
-
|
|
25
|
-
const memoryDir = join(process.cwd(), '.morph', 'memory');
|
|
26
|
-
if (!existsSync(memoryDir)) pass();
|
|
27
|
-
|
|
28
|
-
const files = readdirSync(memoryDir);
|
|
29
|
-
const latest = findLatestMemoryFile(files);
|
|
30
|
-
if (!latest) pass();
|
|
31
|
-
|
|
32
|
-
const raw = readFileSync(join(memoryDir, latest), 'utf-8');
|
|
33
|
-
const snapshot = JSON.parse(raw);
|
|
34
|
-
|
|
35
|
-
const block = buildRestoreBlock(snapshot);
|
|
36
|
-
if (!block) pass();
|
|
37
|
-
|
|
38
|
-
injectContext(block);
|
|
39
|
-
} catch {
|
|
40
|
-
process.exit(0);
|
|
41
|
-
}
|