Skip to content

Commit af14ec9

Browse files
sdsrssclaude
andcommitted
fix(plugin): restore fresh-install bootstrap via cache hooks.json (v0.8.3)
Fresh `/plugin install code-graph-mcp` users got no hooks firing because claude-plugin/hooks/hooks.json was emptied in v0.7.18 (to prevent double- fire with settings.json) and nothing else ever populated settings.json without that hook first running. Result: no auto-index, no StatusLine, no project-map injection, no impact analysis, no auto-update — only MCP tools/commands/agents worked. Migration users kept working because their settings.json still had the legacy registrations. Invert the authority: cache/<ver>/hooks/hooks.json is now the source of truth (same pattern superpowers uses). install/update strip any legacy code-graph entries from settings.json instead of writing them. StatusLine stays in settings.json — it's the only channel Claude Code exposes for it. Secondary fixes: - `lifecycle.js uninstall` CLI prints a reminder to also run `/plugin uninstall code-graph-mcp` inside Claude Code to sync the UI. - README gains an "Invited-memory mode" section documenting the `code-graph-mcp adopt` + CODE_GRAPH_QUIET_HOOKS env var path. - Doctor's "Plugin cache" diagnostic is inverted to "Legacy hooks" — flags leftover code-graph entries in settings.json with auto-fix. Removed from lifecycle.js as obsolete: registerHooksToSettings, getHookDefinitions, scanPluginHooksJsonCopies, findStalePluginHooksJson, clearStalePluginCacheHooks, EMPTY_HOOKS_STUB, isOurPluginMarketplace. session-init.js loses healStaleCacheHooks. Verification: - Fresh-install sandbox: cache hooks.json populated → session-init fires → statusLine composite installed → manifest written → no code-graph entries leak into settings.json. - Migration sandbox (v0.8.2 legacy hooks + other plugin hook present): legacy entries stripped, other plugin's hook preserved, manifest bumped 0.8.2 → 0.8.3. - 115/115 Node tests, 206/206 cargo tests, doctor runs clean. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cc2067a commit af14ec9

File tree

17 files changed

+265
-319
lines changed

17 files changed

+265
-319
lines changed

.claude-plugin/marketplace.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
},
66
"metadata": {
77
"description": "AST knowledge graph plugin for Claude Code — semantic search, call graph, HTTP tracing, impact analysis",
8-
"version": "0.8.2"
8+
"version": "0.8.3"
99
},
1010
"plugins": [
1111
{
1212
"name": "code-graph-mcp",
1313
"source": "./claude-plugin",
1414
"description": "AST knowledge graph for intelligent code navigation — auto-indexes your codebase and provides semantic search, call graph traversal, HTTP route tracing, and impact analysis via MCP tools",
15-
"version": "0.8.2",
15+
"version": "0.8.3",
1616
"author": {
1717
"name": "sdsrs"
1818
},

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "code-graph-mcp"
3-
version = "0.8.2"
3+
version = "0.8.3"
44
edition = "2021"
55

66
[features]

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,23 @@ Then reconnect the MCP server in Claude Code with `/mcp`.
146146

147147
> **Note:** Auto-update is disabled in the source repo directory (dev mode). Use manual update when developing the plugin itself.
148148
149+
#### Invited-memory mode (quieter prompts)
150+
151+
By default, every user prompt the plugin deems code-related gets a small context injection from `code-graph` CLI output. If you'd rather rely on MEMORY.md + explicit tool calls, opt into invited-memory mode:
152+
153+
1. Adopt the plugin contract into your project's memory index (idempotent, self-heals):
154+
```bash
155+
code-graph-mcp adopt
156+
```
157+
This writes `plugin_code_graph_mcp.md` (decision rules) into `~/.claude/projects/<slug>/memory/` and links it from `MEMORY.md` inside a sentinel block. Run `code-graph-mcp unadopt` to remove.
158+
2. Set the activation env var in `~/.claude/settings.json`:
159+
```json
160+
{
161+
"env": { "CODE_GRAPH_QUIET_HOOKS": "1" }
162+
}
163+
```
164+
3. Restart Claude Code. Session startup skips the project-map injection, UserPromptSubmit stops auto-injecting context, and the MCP `instructions` become a short pointer to the MEMORY.md file.
165+
149166
### Option 2: Claude Code MCP Server Only
150167

151168
Register as an MCP server without the plugin features:

claude-plugin/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"author": {
55
"name": "sdsrs"
66
},
7-
"version": "0.8.2",
7+
"version": "0.8.3",
88
"keywords": [
99
"code-graph",
1010
"ast",

claude-plugin/hooks/hooks.json

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,54 @@
11
{
2-
"description": "code-graph-mcp hooks",
3-
"_note": "Hooks are registered to ~/.claude/settings.json by scripts/lifecycle.js. This file is intentionally empty to prevent double-firing — Claude Code would otherwise load hooks from both the plugin cache copy AND settings.json, causing each hook to run twice per event.",
4-
"hooks": {}
2+
"description": "code-graph-mcp hooks — loaded directly by Claude Code from the plugin cache.",
3+
"_note": "Authoritative source. settings.json is no longer used for hook registration as of v0.8.3 — session-init.js actively removes any legacy code-graph entries it finds there. Paths use ${CLAUDE_PLUGIN_ROOT} so they follow version directory updates automatically.",
4+
"hooks": {
5+
"SessionStart": [
6+
{
7+
"matcher": "startup|clear|compact",
8+
"hooks": [
9+
{
10+
"type": "command",
11+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/session-init.js\"",
12+
"timeout": 5
13+
}
14+
]
15+
}
16+
],
17+
"PreToolUse": [
18+
{
19+
"matcher": "tool == \"Edit\"",
20+
"hooks": [
21+
{
22+
"type": "command",
23+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/pre-edit-guide.js\"",
24+
"timeout": 4
25+
}
26+
]
27+
}
28+
],
29+
"PostToolUse": [
30+
{
31+
"matcher": "tool == \"Write\" || tool == \"Edit\"",
32+
"hooks": [
33+
{
34+
"type": "command",
35+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/incremental-index.js\"",
36+
"timeout": 10
37+
}
38+
]
39+
}
40+
],
41+
"UserPromptSubmit": [
42+
{
43+
"matcher": "",
44+
"hooks": [
45+
{
46+
"type": "command",
47+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/user-prompt-context.js\"",
48+
"timeout": 5
49+
}
50+
]
51+
}
52+
]
53+
}
554
}

claude-plugin/scripts/doctor.js

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const os = require('os');
77
const { readBinaryVersion, isDevMode, getNewestMtime } = require('./version-utils');
88
const {
99
getPluginVersion, readJson, healthCheck, CACHE_DIR,
10-
findStalePluginHooksJson, clearStalePluginCacheHooks,
10+
removeHooksFromSettings, isOurHookEntry, writeJsonAtomic,
1111
} = require('./lifecycle');
1212
const { findBinary, clearCache: clearBinaryCache } = require('./find-binary');
1313

@@ -166,24 +166,40 @@ function runDiagnostics() {
166166
});
167167
}
168168

169-
// 7. Plugin cache hooks.json sanity — non-empty copies cause every hook to fire twice
169+
// 7. Legacy hooks in settings.json — v0.8.2 and earlier wrote hooks there;
170+
// cache/<ver>/hooks/hooks.json is now authoritative. Duplicates cause
171+
// every hook to fire twice until settings.json is cleaned.
170172
try {
171-
const stale = findStalePluginHooksJson();
172-
if (stale.length === 0) {
173-
results.push({ name: 'Plugin cache', status: 'ok', detail: 'no stale hooks.json' });
173+
const SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
174+
const settings = readJson(SETTINGS_PATH) || {};
175+
const legacyCount = countLegacyHookEntries(settings);
176+
if (legacyCount === 0) {
177+
results.push({ name: 'Legacy hooks', status: 'ok', detail: 'settings.json is clean' });
174178
} else {
175179
results.push({
176-
name: 'Plugin cache',
180+
name: 'Legacy hooks',
177181
status: 'warn',
178-
detail: `${stale.length} stale hooks.json (hooks fire twice per event)`,
179-
fixId: 'hooks-cache-stale',
182+
detail: `${legacyCount} entries in settings.json (fire twice per event)`,
183+
fixId: 'legacy-hooks-in-settings',
180184
});
181185
}
182-
} catch { /* lifecycle probe failed — skip */ }
186+
} catch { /* probe failed — skip */ }
183187

184188
return results;
185189
}
186190

191+
function countLegacyHookEntries(settings) {
192+
if (!settings || !settings.hooks) return 0;
193+
let count = 0;
194+
for (const entries of Object.values(settings.hooks)) {
195+
if (!Array.isArray(entries)) continue;
196+
for (const entry of entries) {
197+
if (isOurHookEntry(entry)) count++;
198+
}
199+
}
200+
return count;
201+
}
202+
187203
// ── Report Formatting ─────────────────────────────────────
188204

189205
const STATUS_ICONS = { ok: '\u2705', warn: '\u26a0\ufe0f', error: '\u274c', skip: '\u2796' };
@@ -339,12 +355,17 @@ function runRepairs(results) {
339355
break;
340356
}
341357

342-
case 'hooks-cache-stale': {
343-
console.log('\n Clearing stale plugin cache hooks.json...');
344-
const cleared = clearStalePluginCacheHooks();
345-
console.log(` \u2705 Cleared ${cleared.length} file(s) — restart Claude Code to apply`);
346-
for (const p of cleared) console.log(` - ${p}`);
347-
fixed++;
358+
case 'legacy-hooks-in-settings': {
359+
console.log('\n Removing legacy code-graph hooks from settings.json...');
360+
const SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
361+
const settings = readJson(SETTINGS_PATH) || {};
362+
if (removeHooksFromSettings(settings)) {
363+
writeJsonAtomic(SETTINGS_PATH, settings);
364+
console.log(' \u2705 settings.json cleaned — restart Claude Code to apply');
365+
fixed++;
366+
} else {
367+
console.log(' \u2796 No legacy entries found');
368+
}
348369
break;
349370
}
350371

0 commit comments

Comments
 (0)