User Story
As a developer using engram install-hook --auto-reindex (landing in #13), I want my knowledge graph to stay in sync when an agent uses Bash for file operations — not just Edit/Write/MultiEdit — so that rm src/foo.ts, mv old.ts new.ts, and git rm don't leave stale nodes in the graph until the next explicit edit.
Context
#13 wires engram reindex-hook into a PostToolUse entry with matcher Edit|Write|MultiEdit. That covers the native Claude Code editing tools but not file ops the agent routinely runs through Bash — rm, mv, cp, git rm, git mv, output redirection (cat ... > foo.ts). Those produce a PostToolUse event with tool_name: "Bash" and tool_input.command as a string, which the current matcher explicitly excludes.
Hit this live while testing #13: removed the probe file via rm, then had to manually run engram reindex <deleted-file> to get the prune. engram watch (long-running fs.watch) catches this case natively via the rename event handler added in #9/#12 — but the hook-based model loses it.
Proposal
Widen the auto-reindex PostToolUse matcher to also include Bash, and parse tool_input.command to extract affected paths. For each path found, call syncFile(absPath, projectRoot) — same primitive engram reindex and reindex-hook already use, same silent-skip behavior for non-code / missing-unindexed / ignored-dir cases.
Minimum viable coverage:
rm [-rf] <paths...> — each path becomes a prune candidate
mv <src> <dst> — prune src, reindex dst
cp <src> <dst> — reindex dst only
git rm [-r] <paths...> — prune candidates
git mv <src> <dst> — prune + reindex
> <path> / >> <path> redirections — reindex target
Defer or decline:
touch <path> — creates empty files, graph would be empty anyway
find ... | xargs rm / pipe-through-xargs — best left to engram watch or explicit engram reindex
- Directory-level prune (
rm -rf src/) — already called out in the current CHANGELOG under v2.2 as needing per-file directory-prefix pruning before it's safe
Precedent & primitives
src/intercept/handlers/bash.ts already parses Read-like bash (cat, head, less, etc.) for the PreToolUse interception path. Extending that module (or mirroring its lexer in a new parseWriteLikeBashCommand()) is the natural home — same quoting/glob/sudo edge cases already handled there.
The hook contract remains always exit 0 — any parser ambiguity resolves to "skip this path" rather than error.
Acceptance criteria
engram install-hook --auto-reindex registers a PostToolUse entry whose matcher includes Bash (proposed: Edit|Write|MultiEdit|Bash).
engram reindex-hook handles Bash payloads: detects the command verbs above, resolves each path against cwd, calls syncFile.
- Unrecognized commands (git log, npm install, tests, etc.) are silent no-ops.
- Unparseable commands (heavy piping, unusual quoting) are silent no-ops.
- Tests:
parseWriteLikeBashCommand("rm src/foo.ts") → [{ action: "prune", path: "src/foo.ts" }]
parseWriteLikeBashCommand("mv a.ts b.ts") → [{ action: "prune", path: "a.ts" }, { action: "reindex", path: "b.ts" }]
- E2E:
runReindexHook with a Bash payload re-indexes / prunes as expected.
- Unknown commands leave the graph untouched.
Notes
Investigated with AI assistance and human review.
User Story
As a developer using
engram install-hook --auto-reindex(landing in #13), I want my knowledge graph to stay in sync when an agent uses Bash for file operations — not just Edit/Write/MultiEdit — so thatrm src/foo.ts,mv old.ts new.ts, andgit rmdon't leave stale nodes in the graph until the next explicit edit.Context
#13 wires
engram reindex-hookinto a PostToolUse entry with matcherEdit|Write|MultiEdit. That covers the native Claude Code editing tools but not file ops the agent routinely runs through Bash —rm,mv,cp,git rm,git mv, output redirection (cat ... > foo.ts). Those produce a PostToolUse event withtool_name: "Bash"andtool_input.commandas a string, which the current matcher explicitly excludes.Hit this live while testing #13: removed the probe file via
rm, then had to manually runengram reindex <deleted-file>to get the prune.engram watch(long-runningfs.watch) catches this case natively via the rename event handler added in #9/#12 — but the hook-based model loses it.Proposal
Widen the auto-reindex PostToolUse matcher to also include
Bash, and parsetool_input.commandto extract affected paths. For each path found, callsyncFile(absPath, projectRoot)— same primitiveengram reindexandreindex-hookalready use, same silent-skip behavior for non-code / missing-unindexed / ignored-dir cases.Minimum viable coverage:
rm [-rf] <paths...>— each path becomes a prune candidatemv <src> <dst>— prunesrc, reindexdstcp <src> <dst>— reindexdstonlygit rm [-r] <paths...>— prune candidatesgit mv <src> <dst>— prune + reindex> <path>/>> <path>redirections — reindex targetDefer or decline:
touch <path>— creates empty files, graph would be empty anywayfind ... | xargs rm/ pipe-through-xargs — best left toengram watchor explicitengram reindexrm -rf src/) — already called out in the current CHANGELOG under v2.2 as needing per-file directory-prefix pruning before it's safePrecedent & primitives
src/intercept/handlers/bash.tsalready parses Read-like bash (cat,head,less, etc.) for the PreToolUse interception path. Extending that module (or mirroring its lexer in a newparseWriteLikeBashCommand()) is the natural home — same quoting/glob/sudo edge cases already handled there.The hook contract remains
always exit 0— any parser ambiguity resolves to "skip this path" rather than error.Acceptance criteria
engram install-hook --auto-reindexregisters a PostToolUse entry whose matcher includesBash(proposed:Edit|Write|MultiEdit|Bash).engram reindex-hookhandles Bash payloads: detects the command verbs above, resolves each path againstcwd, callssyncFile.parseWriteLikeBashCommand("rm src/foo.ts")→[{ action: "prune", path: "src/foo.ts" }]parseWriteLikeBashCommand("mv a.ts b.ts")→[{ action: "prune", path: "a.ts" }, { action: "reindex", path: "b.ts" }]runReindexHookwith a Bash payload re-indexes / prunes as expected.Notes
--auto-reindexsame as feat(cli): engram reindex <file> + optional --auto-reindex hook (#8) #13 — no change for users who skip that flag.syncFile, which is already fast).mainafter feat(cli): engram reindex <file> + optional --auto-reindex hook (#8) #13 lands, or stack on top of feat(cli): engram reindex <file> + optional --auto-reindex hook (#8) #13 if you'd prefer to review them together.Investigated with AI assistance and human review.