Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Gemini-specific event handling:
- **`pre-push`**: Auto-pushes `refs/notes/agentnote` to remote. Uses `AGENTNOTE_PUSHING` recursion 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 View-style worktree commits after init from the main checkout.

### Core modules

Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Gemini-specific event handling:
- **`pre-push`**: Auto-pushes `refs/notes/agentnote` to remote. Uses `AGENTNOTE_PUSHING` recursion 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 View-style worktree commits after init from the main checkout.

### Core modules

Expand Down
33 changes: 30 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,14 @@ Append-only JSONL files, accumulated during a session, rotated after each commit
└── pre_blobs-<id>.jsonl # archived at next turn boundary
```

In git worktrees, this local temp layer intentionally lives under that
worktree's own git dir (`.git/worktrees/<name>/agentnote` for a non-bare
repository, or the equivalent worktree git dir for a bare repository). This
keeps active session pointers, heartbeats, and uncommitted JSONL buffers
isolated per worktree regardless of where the user chooses to place the
worktree directory, while git notes remain shared through the repository's
common git database.

**Layer 2 — Git notes (`refs/notes/agentnote`)**

The permanent record. One JSON note per commit, written at commit time. Pushable, fetchable, shareable with the team.
Expand Down Expand Up @@ -391,6 +399,14 @@ Three git hooks handle commit integration and notes sharing:
| `post-commit` | After commit succeeds | Reads session ID from the finalized trailer on HEAD, calls `agent-note record <session-id>` to write git note. If `prepare-commit-msg` explicitly marked a stale-heartbeat fallback, calls `agent-note record --fallback-head`, which only records when a session post-edit blob matches a committed HEAD blob. If the current process exposes an adapter-supported session environment such as `CODEX_THREAD_ID`, it may also call `agent-note record --fallback-env` when HEAD still has no Agent Note after the trailer/head attempt; fresh mutating transcript work can become commit-level attribution even when exact `files_touched` is unavailable. Direct file-matched env fallback rows may pull in bounded preceding decision-context prompts for display, but only the matched rows affect attribution. Idempotent — skips if note already exists. |
| `pre-push` | Before push to remote | Pushes `refs/notes/agentnote` to the actual remote (`$1`) and waits for `push-notes` to finish. Recursion-guarded via `AGENTNOTE_PUSHING` env var. |

Git hooks are installed into the hook directory reported by Git, not by assuming
`.git/hooks`. For worktrees, the hook script may run with a worktree-specific
`$GIT_DIR`, so `post-commit` and `pre-push` first try that worktree's local
Agent Note shim and then fall back to the common git dir shim shared by all
worktrees. This works for both bare and non-bare repositories, including custom
worktree directory layouts. It lets a main checkout `agent-note init` support
commits made inside Claude Agent View-style worktrees.

Comment thread
wasabeef marked this conversation as resolved.
Session freshness is verified via per-session heartbeat file (`sessions/<id>/heartbeat`). Heartbeat is refreshed by normalized hook events during long turns. `Stop` does NOT invalidate the heartbeat — it fires when the AI finishes responding, not when the session ends. Gemini `SessionEnd` is a real session termination and removes the heartbeat. Missing heartbeat in `prepare-commit-msg` skips trailer injection. Stale heartbeat writes a one-shot fallback marker for brand-new commits only; `post-commit` consumes that marker and records only if the active session has post-edit blob evidence that matches the committed HEAD blobs. Agent-hosted terminals may also expose the current session through adapter-specific environment variables. Today, Codex exposes `CODEX_THREAD_ID`, which lets `post-commit` recover a fresh Codex transcript even when `.git/agentnote/session` points at a stale or unrelated session.

Plain git hook trailer injection also requires file evidence. File-change records or pre-edit blobs count as safe evidence because they can be matched back to committed files. Prompts alone are not enough for plain git hooks: a fresh prompt-only active session might belong to another agent or terminal workflow. Agent hook trailer injection can still preserve prompt-only work because the commit command itself was observed inside the agent. Transcript paths are supporting metadata, not recordable data by themselves. Heartbeat, `SessionStart`, and `transcript_path` metadata alone do not receive dangling `Agentnote-Session` trailers.
Expand All @@ -402,11 +418,22 @@ Environment fallback is narrower than trailer injection. It does not trust `.git
`agent-note init` installs git hooks respecting the repository's hook directory:

```bash
# Determine hook directory
HOOK_DIR=$(git config get core.hooksPath || echo ".git/hooks")
# Determine the effective hook directory. Git resolves this for normal
# checkouts, bare repositories, custom core.hooksPath, and worktrees.
HOOK_DIR=$(git rev-parse --git-path hooks)
```

If `core.hooksPath` is set (e.g., by husky, lefthook, or custom configuration), hooks are installed there instead of `.git/hooks/`. This ensures compatibility with any hook manager.
Git owns hook path resolution. Agent Note therefore asks Git for the effective
hook directory instead of reconstructing it from `.git/` paths or
`core.hooksPath`. This keeps hook installation correct for hook managers,
bare repositories, custom worktree layouts, and Claude Agent View-style
worktrees.

At runtime, hook scripts first try the worktree-local Agent Note shim under the
Git-reported `$GIT_DIR`. If that shim does not exist, they fall back to the
common git-dir shim shared by all worktrees. This lets `agent-note init` run
from either the main checkout or a linked worktree while still supporting
commits made from any related worktree.

When an existing hook file is found, agent-note chains to it — the original hook runs first, then agent-note's logic runs. This avoids overwriting user or tool-managed hooks.

Expand Down
11 changes: 11 additions & 0 deletions docs/knowledge/investigations.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ PR #72 で `--fallback-env` を導入し、PR #74 で stale trailer retry と bo

## Resolved Investigations

### Claude Agent View / git worktree commit で note が落ちる可能性

- 対象: `agent-note init` が生成する `post-commit` / `pre-push` hook、repo-local CLI shim、worktree 上の plain git commit。
- 背景: Claude Agent View は background Agent の編集前に `.claude/worktrees/...` 配下の git worktree へ移動する。`git worktree` では `git rev-parse --git-dir` が main repository の `.git` ではなく `.git/worktrees/<name>` を返す。一方、hooks は common git dir 側の hooks を使う。
- 過去対応: `4c08e7f fix: support git worktree by using git rev-parse --git-dir` により、`.git` が file になる worktree でも実 git dir を解決できるようになっていた。これは session buffer を worktree ごとに分けるためには正しい対応だった。
- 残っていた問題: main checkout で `agent-note init` した場合、repo-local shim は main 側の `.git/agentnote/bin/agent-note` に作られる。しかし worktree commit の hook 実行時の `$GIT_DIR` は `.git/worktrees/<name>` なので、`$GIT_DIR/agentnote/bin/agent-note` だけを見ると shim が見つからない。結果として trailer があっても `post-commit` が `record` を実行できず、git note が作られない可能性があった。
- Entire から得た判断: Entire は git worktree を session 分離境界として扱う設計を採っている。Agent Note でも session buffer / active pointer / heartbeat は worktree ごとに分離する方が正しい。ただし CLI shim と hooks は common git dir を共有できるため、session data と executable discovery は分けて考える必要がある。
- 修正: `agentnoteDir()` は従来通り worktree-specific git dir を使い、session buffer を worktree ごとに分ける。一方で `commonAgentnoteDir()` を追加し、`agent-note init` は current worktree git dir と common git dir の両方に deterministic CLI shim を作る。生成 hook は `$GIT_DIR/agentnote/bin/agent-note` を優先し、見つからなければ `git rev-parse --git-common-dir` の shim を使う。`resolveHookDir()` は fallback 時に `git rev-parse --git-path hooks` を使い、Git が実際に使う hook path に揃える。
- 追加修正: worktree 対応で hook path が絶対パスになるケースが増えたことで、既存 hook chaining の `String.replace()` replacement 文字列に含まれる `$` / `$'` が JavaScript の replacement token として展開されるバグが表面化した。function replacement に変更し、shell-special path でも backup hook path を壊さないようにした。
- Regression coverage: `packages/cli/src/commands/init.test.ts` で、main checkout で `agent-note init` した後に `.claude/worktrees/agent-view` worktree を作り、Claude hook event で file evidence を作成し、worktree 内の plain `git commit` から git note が作られることを確認する。この test は worktree-local shim が存在しないことも確認し、common shim fallback を必ず通す。追加 matrix では bare repository (`repo.bare/branch/...`) と non-bare repository の両方、repo 内 nested path、repo 外 custom sibling path、space を含む arbitrary worktree path を通し、Git worktree のディレクトリ命名ルールに依存しないことを固定する。さらに `git worktree add` の主要 mode (`--detach`, `--orphan`, `--lock`, `--relative-paths`)、duplicate basename worktree、`git worktree move` 後の path、worktree-specific `core.hooksPath` を通し、Git worktree の private git dir / common git dir / hook path が変わる仕様境界を regression test に含める。

### PR #76 deletion-only AI Ratio が 0% になる

- 対象 PR: `#76`
Expand Down
Loading
Loading