This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Agent Note (agent-note) is a monorepo CLI + GitHub Action that links AI coding sessions to git commits. It records every prompt, AI response, file change, and AI authorship ratio so you can trace back to why code changed.
Repository development uses Node.js >= 22.12.0 and npm 11.13.0. The published CLI package still supports Node.js >= 20.
docs/README.md: Start here for the maintainer documentation map.docs/engineering.md: Implementation guidelines for constants, comments, safety boundaries, and verification.docs/architecture.md: Canonical architecture and data-flow reference.docs/knowledge/README.md: Focused design notes, investigation history, research, and archive.
packages/cli/ # agent-note — npm package (CLI)
packages/pr-report/ # PR Report library used by the GitHub Action
packages/dashboard/ # Dashboard static app + workflow scripts
action.yml # public Action entrypoint for PR Report and Dashboard
website/ # Documentation site (Astro Starlight, 12 locales)
cd packages/cli
npm run build # esbuild → dist/cli.js (single ESM bundle)
npm run lint # biome check src/
npm run lint:fix # biome check --write src/
npm run format # biome format --write src/
npm run typecheck # tsc --noEmit
npm test # node:test runner via tsx
npm run test:coverage # same with V8 coverage
npx tsx src/cli.ts # run CLI without building (must run from packages/cli/)
node dist/cli.js # run built CLITests shell out to node dist/cli.js, so always build before running tests.
- CLI (
packages/cli/src/cli.ts→ commands): public user commands areagent-note init,agent-note deinit,agent-note status,agent-note log,agent-note show, andagent-note why. Internal or automation-facing commands includeagent-note hook,agent-note record,agent-note pr,agent-note commit, andagent-note push-notes. - Hook handler (
packages/cli/src/commands/hook.ts): Called by agent hooks via stdin JSON. All data collection. Agent-agnostic via adapter pattern.
Agent hooks → agent-note hook --agent <name> (stdin JSON) → .git/agentnote/sessions/<id>/*.jsonl (local temp)
git commit → prepare-commit-msg injects trailer when file evidence exists → post-commit calls agent-note record → git note written
git push → pre-push auto-pushes refs/notes/agentnote
agent-note show/log/why → reads git notes --ref=agentnote
Agent Note supports multiple coding agents via an adapter pattern:
types.ts:AgentAdapterinterface — each agent defines its hooks config, event parser, and transcript reader.index.ts: Agent registry —getAgent(),hasAgent(),listAgents().claude.ts: Claude Code adapter. Hooks for Edit/Write/MultiEdit/NotebookEdit and Bash.codex.ts: Codex CLI adapter. Parsesapply_patchtranscripts for file attribution.cursor.ts: Cursor adapter. Hooks via.cursor/hooks.json. Parses~/.cursor/projects/transcripts. Edit-count attribution with line-level upgrade when edit stats match commit diff.gemini.ts: Gemini CLI adapter (Preview). Hooks via.gemini/settings.json. BeforeTool/AfterTool for file edits (write_file,replace) and shell commands. Trailer injection viaprepare-commit-msggit hook (pending-commit pattern).extractInteractionsparses JSONL transcripts from~/.gemini/tmp/; transcript schema may evolve with Gemini CLI updates.
- SessionStart: Create session directory, write heartbeat, store agent name via
writeSessionAgent() - Stop: Log stop event only — does not invalidate heartbeat (Stop = AI response end, not session end)
- UserPromptSubmit: Normalize prompt text, append it to
prompts.jsonl, increment turn counter inturnfile. Agent Note strips leading runtime metadata such as<environment_context>while preserving the actual user request, and drops standalone system-injected messages (<task-notification>,<system-reminder>,<teammate-message>) to prevent turn pollution - PreToolUse (Edit/Write/MultiEdit/NotebookEdit): Capture pre-edit blob hash via
git hash-object -wfor line-level attribution (synchronous) - PreToolUse (Bash, git commit): Inject
--trailerinto thegit commitsegment only via regex replace (cmd.replace(/(git\s+commit)/, ...)) to handle chained commands likegit commit && git push(synchronous, must write to stdout) - PostToolUse (Edit/Write/MultiEdit/NotebookEdit): Track file changes in
changes.jsonlwith current turn number and post-edit blob hash - PostToolUse (Bash, git commit): Call
recordCommitEntry()to write git note, then rotate logs
Gemini-specific event handling:
- BeforeTool (
write_file,replace): Maps topre_edit; writes{"decision": "allow"}to stdout (synchronous requirement) - BeforeTool (shell, git commit): Maps to
pre_commit; writes pending-commit state toPENDING_COMMIT_FILEand{"decision": "allow"}to stdout - AfterTool (
write_file,replace): Maps tofile_change(standard) - AfterTool (shell, git commit): Maps to
post_commit; readsPENDING_COMMIT_FILEto detect HEAD change, then callsrecordCommitEntry() - BeforeAgent: Maps to
prompt(increments turn, appends toprompts.jsonl) - AfterAgent: Maps to
response - SessionStart / SessionEnd: Standard session lifecycle events
- BeforeTool (unrecognized tool): Null fallback — still writes
{"decision": "allow"}to stdout to avoid blocking Gemini CLI
agent-note init installs three git hooks alongside the agent's hook config:
prepare-commit-msg: Checks heartbeat freshness (< 1 hour) and file evidence (changes.jsonlorpre_blobs.jsonl) before injecting anAgentnote-Sessiontrailer for plain git commits. Prompt-only sessions do not get plain git hook trailers. AgentPreToolUse git commithooks may still inject trailers for prompt-only rescue because the commit command itself came from the agent. Skips amends.post-commit: Reads session ID from HEAD's trailer, callsagent-note record <sid>to write git note. Ifprepare-commit-msgmarked a long-running session as too stale for trailer injection, it callsagent-note record --fallback-head, which records only when a session post-edit blob matches a committed HEAD blob. If the current process exposes an adapter-supported session environment such asCODEX_THREAD_ID, it may also callagent-note record --fallback-envwhen HEAD still has no Agent Note after the trailer/head attempt. Env fallback prefers transcript rows tied to current commit files, ignores rows after HEAD, can recover work prepared just before the previous commit when no newer match exists, keeps bounded preceding decision-context prompts for display, and uses commit-level attribution only for mutating shell-only work without exactfiles_touched.pre-push: Auto-pushesrefs/notes/agentnoteto remote. UsesAGENTNOTE_PUSHINGrecursion guard.
Existing hooks are backed up and chained. Compatible with husky/lefthook. Git worktrees are supported by keeping session buffers in each worktree's own git dir while sharing the repo-local CLI shim from the common git dir. This must work for bare and non-bare repositories, arbitrary worktree directory layouts, and agent-managed worktree commits after init from either the main checkout or a linked worktree. Claude Agent View is one example, but the worktree behavior must stay agent-agnostic for Codex, Cursor, Gemini, and future adapters.
core/record.ts: SharedrecordCommitEntry()used by bothhook.tsandcommit.ts. Reads JSONL, builds entry, writes git note, rotates logs. Agent-aware via registry. Idempotent (checks existing note). Includes consumed-pairs deduplication to prevent re-attribution across split commits.core/prompt-window.ts: Prompt-window policy and stable selection evidence. Keeps commit-to-commit prompt context while trimming stale task prompts, tail duplicates, quoted history, and non-primary edit turns.core/entry.ts:buildEntry()andcalcAiRatio(). Structured schema withfiles: [{path, by_ai}],attribution: {ai_ratio, method, lines},model,interactions[].contexts[], andinteractions[].tools.core/attribution.ts: 3-diff position algorithm for line-level AI attribution. Parses unified diff hunks, computes AI vs human line positions.core/session.ts:writeSessionAgent()/readSessionAgent()/writeSessionTranscriptPath()/readSessionTranscriptPath(). Per-session agent metadata.core/constants.ts: Shared constants —TRAILER_KEY,SESSION_AGENT_FILE,HEARTBEAT_FILE,PENDING_COMMIT_FILE, etc.core/jsonl.ts:readJsonlField()(deduplicated single field),readJsonlEntries()(full objects),appendJsonl().core/storage.ts:writeNote()andreadNote()usingrefs/notes/agentnote.core/rotate.ts: Rename JSONL files with commit SHA prefix after each commit.
Layer 1 — Local temp (.git/agentnote/sessions/): Append-only JSONL files accumulated during a session. Rotated after each commit. Never pushed.
Layer 2 — Git notes (refs/notes/agentnote): One JSON note per commit with "v": 1 schema. Permanent, pushable, shareable.
Each UserPromptSubmit increments a turn counter. File changes inherit the current turn number. At commit time, recordCommitEntry() groups files by turn and attaches them as files_touched per interaction. Prompt lists are selected from the commit-to-commit window: turns after the previous recorded commit through the current commit's surviving edit turns, with structurally stale leading quoted history and overwritten edit turns trimmed. If a short selected prompt depends on the immediately previous response but that previous turn is already consumed by an older commit, Agent Note may attach display-only context; this never changes attribution. This avoids timestamp-based attribution which is unreliable under async hooks.
initmodifies agent config (.claude/settings.jsonfor Claude Code,.codex/for Codex,.cursor/hooks.jsonfor Cursor,.gemini/settings.jsonfor Gemini CLI) and installs git hooks (prepare-commit-msg, post-commit, pre-push). Agent config is intended to be committed to git so the team shares the same hooks config.hookis called by the coding agent at runtime. It never modifies config files.- Public user installs generate agent hooks that call
npx --yes agent-note hook --agent <name>. This repository may keep repo-local development hooks that callnode packages/cli/dist/cli.js hook --agent <name>so local changes can be tested before publishing. Treatcli.js hookas a maintainer-only compatibility path, not public setup guidance.
.claude/settings.json includes quality gates beyond agent-note tracking:
- Stop (async): Run tests after each turn
- PreToolUse (sync): Typecheck before
git commit— blocks if types fail - PostToolUse (async): Biome lint after Edit/Write — feedback only
- Conventional Commits required. Prefix:
feat:,fix:,refactor:,test:,docs:,ci:,chore:,perf:,build:. Used by git-cliff for changelog generation. - Scope is optional but encouraged for targeted changes:
feat(website):,fix(action):. - Write commit subjects for release notes. The subject should describe the user-facing outcome, not the mechanical edit. Prefer
fix(report): hide absorbed external review promptsoverfix: address review comments. - Use the body when the subject is not enough. Good AI-generated commits should include
Why,User impact,Verification, andRelease note:sections. Do not writeWhy:,User impact:, orVerification:with colons because git-cliff may parse them as commit footers instead of body text. - Release note rules.
feat:,fix:, andperf:commits are included by default.docs:,test:,refactor:,ci:,chore:, andbuild:commits are omitted unless the body containsRelease note:with a value other thanskip. - Keep release notes human-sized. A multi-commit PR should normally produce one clear release bullet per user-visible change, not one bullet per review fix. Put the public wording on the primary implementation commit with
Release note: <sentence>, and addRelease note: skipto follow-up commits such asaddress review findings,tighten fallback,bound window, or generated bundle syncs unless they describe a distinct user-visible outcome. - Write release notes as natural English sentences. The generator capitalizes the first character as a safety net, but do not rely on that to fix awkward wording. Prefer
Release note: Codex commits made from cmux sessions are now recorded reliably.overRelease note: recover codex env sessions. - PR titles should be release-summary quality. Write PR titles as a user-facing outcome, not an implementation step. Even though GitHub Releases are generated from commits, a good PR title is the easiest review-time signal that the eventual release note will be understandable.
- Preview release notes before merging release-sensitive PRs. If
git-cliff --config .github/cliff.toml --latest --strip headerreads like an implementation log, rewrite commit subjects/bodies before merge. Before tagging, use the release command below so the preview targets the next version. - Do not rely on merge commits for release copy. Release notes include merged PR links from
Merge pull request...commits, but the user-facing bullets still come from implementation commits andRelease note:lines. Version bumps and generated bundle sync commits stay hidden. - Structural vs behavioral changes must not be mixed in a single commit. Renames/reformats separate from feature/fix commits.
- Before committing, all four checks must pass (run from
packages/cli/):npm run build— esbuild bundlenpm run typecheck— tsc --noEmitnpm run lint— biome checknpm test— node:test (requires build first)
- Version bumps go in a dedicated
chore: bump version to x.y.zcommit. Prefer the repo-localagentnote-releaseskill or/releasecommand;x.y.zandvx.y.zinputs both create thevx.y.ztag. Push the tag only when ready to trigger the release workflow (test → GitHub Release → npm publish).
- Zero runtime dependencies for CLI. Only devDependencies. The action has its own deps (
@actions/core,@actions/github), bundled with ncc. - Git CLI only. All git operations go through
packages/cli/src/git.tswhich calls thegitbinary viaexecFile. Never use a git library. - Never break git commit. All agent-note recording is wrapped in try/catch. If agent-note fails, the commit must still succeed.
- All source code in English. Comments, variable names, CLI output, test descriptions — everything in English.
- PreToolUse hooks are synchronous. Must write JSON to stdout, must not be marked
async: true. - Input validation. Environment-provided session IDs must use canonical UUID format.
transcript_pathmust be under the agent's home directory (e.g.~/.claude/for Claude Code,~/.gemini/for Gemini CLI). - Git notes for persistent storage. Entry data goes to
refs/notes/agentnote, not to files. - Biome for lint + format. Run
npm run lint(biome check) andnpm run typecheck(tsc) separately. Both must pass in CI.