Skip to content

v0.1.29 feat: intent capture via set_intent MCP tool#28

Merged
codeprakhar25 merged 5 commits into
mainfrom
feat/capture-intent
May 22, 2026
Merged

v0.1.29 feat: intent capture via set_intent MCP tool#28
codeprakhar25 merged 5 commits into
mainfrom
feat/capture-intent

Conversation

@codeprakhar25
Copy link
Copy Markdown
Owner

Summary

Intent capture — agents now record why they made changes, not just what they changed. Intent flows from the MCP tool call at edit time through to signed traces and PR comments.

New capability

  • New set_intent MCP tool in agentdiff-mcp: agents call this before committing with a 1-2 sentence description of the change goal + an intent_type enum (bugfix/feature/refactor/test/docs/security/performance/config/dependency). Written as type=intent events to session.jsonl.
  • AgentdiffMetadata gains intent_type: Option<String> field, persisted through prepare-ledger.pyfinalize-ledger.py → signed AgentTrace.
  • agentdiff report --format markdown --context renders [type] intent in the Review Context block and a Type column in the trace details table. JSON report includes intent_type.
  • AGENTS.md managed block updated to instruct agents to call set_intent before committing.

Bug fixes (this branch)

  • Prune consumed type=intent events from session.jsonl after finalize — prevents stale intent from the previous commit bleeding into the next one when two commits land in the same second.
  • --intent-type in record-context.py now validated via argparse choices=.
  • Fix byte-boundary panic: description[..500] now slices by char boundary, not bytes — prevented panic on any non-ASCII description ≥ 500 bytes.
  • Server-side intent_type enum validation in Rust set_intent() — returns MCP error -32602 for unknown values, so the MCP schema enforcement is no longer advisory-only.
  • Skip session-id matching when session_id = "unknown" to prevent cross-session intent bleed.
  • Separate os.replace from the read-loop in remove_consumed_intents so a rename failure no longer silently leaves a .tmp ghost file and stale intent events.

End-to-end flow

Agent calls set_intent MCP tool (description + intent_type)
    ↓
type=intent event written to .git/agentdiff/session.jsonl
    ↓
pre-commit: prepare-ledger reads intent events, session-id matched first → fallback to latest
    ↓
post-commit: finalize-ledger persists intent + intent_type in AgentTrace metadata
    ↓
finalize prunes consumed intent events from session.jsonl
    ↓
agentdiff report --context → "[refactor] Extract auth middleware..."

Pre-Landing Review

10 findings reviewed. 4 HIGH/MEDIUM issues fixed before push:

  • [FIXED] Rust byte-boundary panic on multi-byte Unicode in description truncation
  • [FIXED] Server-side intent_type enum validation (Rust)
  • [FIXED] Cross-session intent bleed when session_id is "unknown"
  • [FIXED] Blanket except swallowing os.replace failure in remove_consumed_intents

Remaining noted items (non-blocking):

  • head_commit_ts returns 0 on fresh/detached repo — all historical events pass filter (correct for first commit; edge case on detached HEAD)
  • No direct unit tests for read_intent_events and remove_consumed_intents in isolation — covered by integration path via test_write_agent_trace_persists_intent_type
  • intent_type in finalize-ledger.py written to trace without allowlist check (upstream is now validated at MCP layer)

Test Coverage

All 68 tests pass (47 Rust + 21 Python). New tests added:

  • set_intent_writes_to_session_jsonl — verifies MCP tool writes correct event fields
  • set_intent_requires_description — empty description returns MCP error
  • tools_list_includes_both_tools — both tools appear in tools/list response
  • test_write_agent_trace_persists_intent_type — finalize persists intent_type to trace
  • test_write_agent_trace_persists_structured_context_metadata — full metadata roundtrip
  • markdown_trace_report_includes_review_context — report renders [type] intent format
  • context_json_report_includes_trace_metadata — JSON report includes intent_type

Test plan

  • All Rust tests pass (47/47)
  • All Python tests pass (21/21)
  • End-to-end verified: intent event injected → commit → trace contains intent + intent_type → report shows [bugfix] Fix stale intent bleed...
  • Intent events pruned from session.jsonl after commit (0 remaining verified)

🤖 Generated with Claude Code

codeprakhar25 and others added 5 commits May 22, 2026 07:32
…ering

- New set_intent MCP tool appends type=intent events to session.jsonl
- AgentdiffMetadata gains intent_type: Option<String> field
- report.rs renders [type] intent in Review Context and trace details table
- context_json_report includes intent_type in JSON output
- AGENTS.md managed block instructs agents to call set_intent before committing
- 6 new tests (tools/list, set_intent happy path, empty description rejection)
…inalize

- read_intent_events() scans session.jsonl for type=intent events since last commit
- Intent priority: agent-stated event (session-id matched) > pending context > none
- intent_type propagated through pending_ledger.json to finalize-ledger.py
- 2 new tests: write_agent_trace persists intent_type and full structured metadata
- Fix byte-boundary panic: slice description by chars not bytes (Rust)
- Add server-side intent_type enum validation in set_intent() (Rust)
- Skip session-id matching when session_id is 'unknown' to prevent cross-session bleed
- Separate read-loop from os.replace in remove_consumed_intents to avoid swallowing rename errors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

AgentDiff Report

Summary

Agent Lines %
Prakhar Khatri 349 83%
claude-code 72 17%

Review Context

  • Intent: unspecified (381 lines, 9 files)
    • Agent/model: Prakhar Khatri, claude-code / claude-sonnet-4-6
    • Prompt: ok if everything seems to be good let's add commit push , create a PR wait for review from greptile check review comments then merge it into main and then fi...
  • Intent: Fix stale intent bleed by pruning consumed intent events after finalize (40 lines, 2 files)
    • Agent/model: claude-code / claude-sonnet-4-6
    • Prompt: ok so you see the app rn I updated the cap for each description capture and more and do the later changes needed and test completely as you said to me by nst...

Files To Review First

File Lines Dominant Agent Intent Context
src/bin/agentdiff-mcp.rs 216 Prakhar Khatri unspecified trace 0b8a1af7
scripts/prepare-ledger.py 71 Prakhar Khatri unspecified trace 65713b8c
scripts/tests/test_capture_prompts.py 47 Prakhar Khatri unspecified trace 65713b8c
scripts/finalize-ledger.py 46 claude-code Fix stale intent bleed by pruning consumed intent events after finalize, unspecified trace 67b44145
src/commands/report.rs 27 Prakhar Khatri unspecified trace 0b8a1af7
scripts/record-context.py 4 claude-code Fix stale intent bleed by pruning consumed intent events after finalize trace 67b44145
src/configure/agents_md.rs 4 Prakhar Khatri unspecified trace 0b8a1af7
src/data.rs 4 Prakhar Khatri unspecified trace 0b8a1af7
Cargo.lock 1 claude-code unspecified trace 5983ffac
Cargo.toml 1 claude-code unspecified trace 5983ffac
Trace details
Trace Agent Intent Files Lines
67b44145 claude-code Fix stale intent bleed by pruning consumed intent events after finalize scripts/finalize-ledger.py, scripts/record-context.py 40
0b8a1af7 Prakhar Khatri unspecified src/bin/agentdiff-mcp.rs, src/commands/report.rs, src/configure/agents_md.rs, src/data.rs 236
65713b8c Prakhar Khatri unspecified scripts/prepare-ledger.py, scripts/tests/test_capture_prompts.py 113
8e8ef007 claude-code unspecified scripts/finalize-ledger.py, scripts/prepare-ledger.py, src/bin/agentdiff-mcp.rs 30
5983ffac claude-code unspecified Cargo.lock, Cargo.toml 2

@codeprakhar25 codeprakhar25 merged commit 43c734e into main May 22, 2026
4 checks passed
@codeprakhar25 codeprakhar25 deleted the feat/capture-intent branch May 22, 2026 07:49
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 22, 2026

Greptile Summary

This PR adds intent capture to the AgentDiff commit flow. The main changes are:

  • A new set_intent MCP tool that writes intent events to session.jsonl.
  • Ledger preparation and finalization now carry intent and intent_type into trace metadata.
  • Markdown and JSON reports now show intent type in review context.
  • Finalization now prunes consumed intent events from the session log.
  • Agent instructions now ask agents to call set_intent before committing.

Confidence Score: 3/5

This should be fixed before merging because the intent flow can persist the wrong data.

  • Intent events can be deleted before the next commit consumes them.
  • A known session can receive an unrelated session's latest intent.
  • Concurrent writes and prune rewrites can corrupt or lose session.jsonl data.
  • Invalid intent_type input is reported as a server error instead of a caller input error.

scripts/prepare-ledger.py, scripts/finalize-ledger.py, and src/bin/agentdiff-mcp.rs need the most attention.

Important Files Changed

Filename Overview
src/bin/agentdiff-mcp.rs Adds the set_intent MCP tool, validation, and session-log append path.
scripts/prepare-ledger.py Selects intent events and carries intent metadata into the pending ledger.
scripts/finalize-ledger.py Persists intent metadata and prunes intent events from the session log.
src/commands/report.rs Displays intent type in markdown context and JSON reports.

Sequence Diagram

sequenceDiagram
    participant Agent
    participant MCP as agentdiff-mcp
    participant Session as session.jsonl
    participant Prepare as prepare-ledger.py
    participant Pending as pending-ledger.json
    participant Finalize as finalize-ledger.py
    participant Trace as AgentTrace

    Agent->>MCP: set_intent(description, intent_type)
    MCP->>Session: "append type=intent event"
    Prepare->>Session: read intent events
    Prepare->>Pending: write intent metadata
    Finalize->>Pending: read pending ledger
    Finalize->>Trace: persist intent metadata
    Finalize->>Session: prune intent events
Loading

Comments Outside Diff (1)

  1. src/commands/report.rs, line 463-471 (link)

    P2 Keep type in grouping Review Context groups traces only by intent text and then keeps the first non-empty intent_type. If two traces use the same intent wording with different types, the report merges their lines and labels the whole group with one type, so part of the displayed context is misclassified.

Reviews (1): Last reviewed commit: "chore: release v0.1.29 — intent capture ..." | Re-trigger Greptile

Comment thread src/bin/agentdiff-mcp.rs
Comment on lines +185 to +199
match set_intent(&args, default_cwd) {
Ok(out_path) => response_ok(
rid,
json!({
"content": [{
"type":"text",
"text": format!("intent recorded in {}", out_path.display())
}],
"structuredContent": {
"status":"recorded",
"path": out_path.display().to_string()
}
}),
),
Err(err) => response_error(rid, -32000, format!("{err:#}")),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Return invalid params The set_intent branch maps every set_intent() error to -32000. That includes the new enum validation for intent_type, so a caller that sends an unknown type receives an internal server error instead of the invalid-params error expected for bad tool input. MCP clients can then handle a user input problem as a server failure.

Comment on lines +82 to +85
try:
event = json.loads(line)
if isinstance(event, dict) and event.get("type") == "intent":
continue
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Prune only consumed intent This removes every type=intent line in session.jsonl, not just the intent that was attached to the commit being finalized. If an agent records the next commit's intent while this post-commit hook is still running, that new event is deleted before the next pre-commit can read it, so the next trace silently loses its intent.

Comment thread scripts/prepare-ledger.py
Comment on lines +360 to +361
if not best_intent:
best_intent = intent_events[-1]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Avoid unrelated fallback When the current commit has a known session_id but no intent event matches it, this falls back to the latest intent from any session. A commit attributed to sess-A can therefore receive an intent recorded by sess-B as long as that event is newer than HEAD, which is another cross-session bleed case.

Suggested change
if not best_intent:
best_intent = intent_events[-1]
if not best_intent and (not session_id or session_id == "unknown"):
best_intent = intent_events[-1]

Comment thread src/bin/agentdiff-mcp.rs
Comment on lines +356 to +362
let mut file = fs::OpenOptions::new()
.create(true)
.append(true)
.open(&session_path)
.with_context(|| format!("opening {}", session_path.display()))?;
writeln!(file, "{}", event.to_string())
.with_context(|| format!("writing to {}", session_path.display()))?;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Synchronize session writes set_intent appends directly to session.jsonl without taking a lock. Multiple MCP servers or capture hooks can write at the same time, and finalize-ledger.py can rewrite the same file while this append happens. That can corrupt JSONL or drop a valid intent, and prepare-ledger.py will silently skip malformed lines.

Comment thread scripts/record-context.py
"prompt": args.prompt or str(payload.get("prompt") or ""),
"files_read": parse_json_array(args.files_read) or payload.get("files_read") or [],
"intent": args.intent or str(payload.get("intent") or ""),
"intent_type": args.intent_type or str(payload.get("intent_type") or ""),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Validate merged payload The argparse choices check only applies to the CLI flag. This line still accepts intent_type from stdin JSON without validating it, and that value can be persisted by prepare/finalize into the trace. A caller piping {"intent_type":"other"} through the documented stdin path bypasses the new allowlist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant