feat(hook): auto-capture code areas the agent edits#261
Open
pszymkowiak wants to merge 1 commit into
Open
Conversation
Adds a hook-driven, no-MCP equivalent of Context-Engine's
`record_code_area` tool: every time Claude Code / Codex / Gemini /
Copilot calls `Edit` / `Write` / `MultiEdit` / `NotebookEdit`, the
already-installed PostToolUse hook (`icm hook post`) extracts
`tool_input.file_path` and upserts a row in a new `code_areas` table.
Same `(project, file_path)` increments `touch_count` instead of
duplicating, so the table grows in *file count*, not edit count.
### What's new
- New table `code_areas(id, project, file_path, description,
session_id, tool_name, touch_count, first_touched_at,
last_touched_at)` with a `UNIQUE(project, file_path)` constraint
driving the upsert.
- `SqliteStore::upsert_code_area` — ON CONFLICT bumps `touch_count`,
refreshes `last_touched_at`/`session_id`/`tool_name`, and only
overwrites `description` when the caller passes `Some` (preserves
the most recent meaningful hint).
- `SqliteStore::list_code_areas` — filter by `project`, exact or
suffix `file_path`, `since` timestamp; ordered by
`last_touched_at DESC`.
- `extract_tool_input_file_path()` in `main.rs` covers the three
shapes we've seen across Claude Code 1.x / 2.x, Codex, and Gemini
(`tool_input.file_path`, top-level `file_path`,
`tool_input.arguments.file_path`).
- `cmd_hook_post` calls `upsert_code_area` for matching tool names
**before** the extract counter — independent of the throttle, never
blocking the hot path, errors swallowed (telemetry must never fail
the hook).
- New `icm code-areas` CLI command with `--in-file`, `--project`,
`--since`, `--limit`, `--format {table,json}`.
### Notes
- `description` is `None` in the MVP. Once #165 (LLM-summarized
briefing) lands, the same provider infrastructure can feed a diff
summary into `description` opt-in.
- No new dependencies. Hook + transcript + sqlite plumbing was
already in place; the patch reuses all of it.
- Source MCP tool (`Context-Engine-AI/Context-Engine`) is
source-available proprietary; ICM ships an Apache-2.0 equivalent
from scratch.
### Tests
- 7 new unit tests in `icm-store` (upsert idempotency, touch_count
increment, description preservation/overwrite, project + path-suffix
filters, `since` filter, ordering, count).
- 513 tests pass across the workspace, `cargo clippy --workspace
--all-targets -- -D warnings` clean.
- Manual smoke against the release binary: 3 hook payloads (`Edit`,
`Write`, `MultiEdit`) → `code-areas` reports 2 unique paths,
`auth.rs` has touch_count=2, `Bash` payload correctly ignored,
`--in-file` and `--format json` both work.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #196.
Adds a hook-driven, no-MCP equivalent of Context-Engine's
record_code_area: every time Claude Code / Codex / Gemini / Copilotcalls
Edit/Write/MultiEdit/NotebookEdit, the existingPostToolUse hook (
icm hook post) extractstool_input.file_pathandupserts a row in a new
code_areastable. Same(project, file_path)increments
touch_countinstead of duplicating, so the table grows infile count, not edit count.
What's new
code_areas(id, project, file_path, description, session_id, tool_name, touch_count, first_touched_at, last_touched_at)table withUNIQUE(project, file_path)constraintdriving the upsert.
SqliteStore::upsert_code_area(project, file_path, description, session_id, tool_name)— ON CONFLICT bumpstouch_count,refreshes
last_touched_at/session_id/tool_name, and onlyoverwrites
descriptionwhen the caller passesSome(preservesthe most recent meaningful hint via
COALESCE).SqliteStore::list_code_areas(project, in_file, since, limit)—filter by exact or suffix path; ordered by
last_touched_at DESC.SqliteStore::code_area_count().extract_tool_input_file_path()covers threeshapes seen across Claude Code 1.x / 2.x, Codex, and Gemini
(
tool_input.file_path, top-levelfile_path,tool_input.arguments.file_path).cmd_hook_postcallsupsert_code_areabefore the extract counter — independent ofthe throttle, errors swallowed so telemetry never blocks the hook.
icm code-areascommand with--in-file,--project,--since(ISO-8601),--limit,--format {table,json}.Why no MCP
Per the issue thread: tying this to the PostToolUse hook gives 100%
coverage across every AI tool already
icm init'd. No new MCP toolto expose, no need for the agent to comply via system prompt. The
existing hook chain already runs after every tool call.
Notes
descriptionisNonein the MVP. Once feat: LLM-summarized wake-up briefing (auto-detect invoker, cheap defaults, configurable) #165 (LLM-summarizedbriefing) ships, the same provider infrastructure can feed a diff
summary into
descriptionopt-in.in place — the patch reuses all of it.
Context-Engine-AI/Context-Engine) issource-available proprietary; ICM ships an Apache-2.0 equivalent
built from scratch.
Tests
icm-store(upsert idempotency, touch_countincrement, description
COALESCEbehaviour, project + path-suffixfilters,
sincefilter,last_touched_at DESCordering, count).cargo clippy --workspace --all-targets -- -D warningsclean.Edit/Write/MultiEdit) → 2 unique pathscaptured.
auth.rsre-touched →touch_count= 2.Bashpayload → correctly ignored (no code-area row).--in-filefilter and--format jsonboth verified.Sample output
Test plan
cargo build --workspacecargo clippy --workspace --all-targets -- -D warningscargo test --workspace(513 passed)icm code-areasend-to-end with--in-file,--format jsonCloses #196.