Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
6b84d4b
docs(aidd-telemetry): record the worktree-journal survival brainstorm
VincentMenard76 Oct 9, 2026
060e78b
fix(aidd-telemetry): journal every worktree of a clone under its comm…
VincentMenard76 Oct 9, 2026
3733470
fix(cli): read the run journal from the clone's common git directory
VincentMenard76 Oct 9, 2026
0a20f77
docs(aidd-telemetry): record one run journal per clone and its plan
VincentMenard76 Oct 9, 2026
2344451
test(cli): agree on journal paths on Windows runners and keep e2e for…
VincentMenard76 Oct 9, 2026
1a58a92
fix(aidd-telemetry): read the journal where the hook now writes it in…
VincentMenard76 Oct 9, 2026
f135055
docs(aidd-telemetry): ask for the CLI to be updated before the plugin
VincentMenard76 Oct 9, 2026
f637791
fix(cli): stop inviting a commit of a journal no commit can reach
VincentMenard76 Oct 9, 2026
56f8711
test(cli): catch a worktree session up from its transcript after removal
VincentMenard76 Oct 9, 2026
b85ac77
docs(aidd-telemetry): note what a mid-session plugin update drops
VincentMenard76 Oct 9, 2026
f454731
fix(cli): name every journal directory check counted, and count a ses…
VincentMenard76 Oct 9, 2026
c496892
docs(aidd-telemetry): describe the journal location in architecture a…
VincentMenard76 Oct 9, 2026
25b2976
test(cli): pin which journal copy wins and what a removal loses
VincentMenard76 Oct 9, 2026
edd78cb
fix(cli): list a worktree reached through a link once, and report for…
VincentMenard76 Oct 9, 2026
e6c5948
docs(aidd-telemetry): note what a session resumed after the update loses
VincentMenard76 Oct 9, 2026
84f2887
fix(cli): name the directory of every journal file forget could not r…
VincentMenard76 Oct 9, 2026
eba6f50
test(cli): clean up every temp directory a reader test creates, even …
VincentMenard76 Oct 9, 2026
abc34f5
docs(aidd-telemetry): refresh the review with the final verdict and i…
VincentMenard76 Oct 9, 2026
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 @@ -54,6 +54,7 @@ Project docs, memory, specs, and plans live in `aidd_docs/`.
<!-- read on demand, not auto-loaded -->
- aidd_docs/memory/internal/decisions/a-price-table-is-the-destination-s.md
- aidd_docs/memory/internal/decisions/measurement-may-reach-a-hosted-destination.md
- aidd_docs/memory/internal/decisions/one-run-journal-per-clone.md

<!-- aidd_project_memory:end -->

Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Project docs, memory, specs, and plans live in `aidd_docs/`.
<!-- read on demand, not auto-loaded -->
- aidd_docs/memory/internal/decisions/a-price-table-is-the-destination-s.md
- aidd_docs/memory/internal/decisions/measurement-may-reach-a-hosted-destination.md
- aidd_docs/memory/internal/decisions/one-run-journal-per-clone.md

<!-- aidd_project_memory:end -->

Expand Down
1 change: 1 addition & 0 deletions aidd_docs/memory/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Read on demand:

- [internal/decisions/a-price-table-is-the-destination-s.md](internal/decisions/a-price-table-is-the-destination-s.md)
- [internal/decisions/measurement-may-reach-a-hosted-destination.md](internal/decisions/measurement-may-reach-a-hosted-destination.md)
- [internal/decisions/one-run-journal-per-clone.md](internal/decisions/one-run-journal-per-clone.md)
<!-- files:end -->

## Maintaining it
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# One run journal per clone, under its common git directory

- Date: 2026-10-09
- Status: Accepted β€” closes the open choice in #932, amends the per-worktree default kept after #693
- Decided by: proposed by Vincent Menard, for a maintainer to confirm or overturn

## Context

The hook wrote each session's journal at the root of the checkout it ran in, `<worktree>/aidd_docs/runs/`. The CLI read only the journal of the checkout it ran in, and the sink is filled only through that journal. `aidd telemetry on` git-ignores `aidd_docs/runs/`, and `git worktree remove` deletes a worktree whose only extra content is ignored without needing `--force`. A session an agent ran in its own worktree was therefore missing from every report outside that worktree, and lost for good once the worktree was removed. Agent runners give each agent its own worktree, so this was the common case.

The per-worktree default had two sound reasons: nothing should write into another branch's working tree, and a bare clone has no main working tree to write into.

## Decision

1. The run journal of a clone lives in one directory, `<git common dir>/aidd/runs/`, shared by every worktree of that clone. It is outside every working tree, so it is never committed, dirties no branch, and survives `git worktree remove`. A bare clone has a common dir too.
2. `AIDD_RUNS_DIR` still replaces that location entirely.
3. Journals already written to a live checkout's `aidd_docs/runs/` are still read, never written. The copy under the common git directory wins when one session has both.
4. `worktree_id` stays on the `session_start` line. The report gains no worktree axis: two worktrees that worked on one task add up. Only the cumulative figure matters: a worktree is a technical artefact an agent runner names, and the task, backlog, flow and agent axes already answer what a piece of work cost. The 2026-08-31 decision requires an argument for a new axis; there is none.

## Alternatives

- **Keep one journal per worktree and gather them at report time.** Solves the report, not the removal: the journal still goes with the worktree.
- **Write into the main working tree.** Impossible for a bare clone, and dirties a checkout on another branch.
- **Copy each worktree's journal into the main one.** Two copies of one session diverge, and the copy only happens if someone reports before the removal.

## Consequences

- A worktree removed before this change, never read before, stays lost.
- The hook ships in the plugin and the reader in the CLI, released apart. A newer CLI reads both locations; an older CLI with a newer plugin sees no new session and cannot forget it. The plugin README therefore asks for the CLI to be updated first.
- A session the previous plugin opened keeps its old run file; lines the new plugin writes later in that same session find no file under the new location and are dropped until the next session starts.
- A session resumed after the update starts a second run file under the new location; that copy wins over the old one, so the part of the session journalled before the update loses its attribution.
- A clone made with `--separate-git-dir`, or a submodule, has no `.git` directory to name its main working tree, so from one of its linked worktrees the main checkout's pre-move journal is neither read nor offered to forget. New journals are unaffected.
- The two clones of one remote keep two journals; the sink and the project axis already pool them.
- The backlog axis still resolves task folders in the checkout running the report, and catch-up still follows that checkout's switch. Both are outside this decision.
- Hook and CLI spell the location separately (`repo.cjs`, `cli/src/kernel/paths.ts`); a parity test holds them together.
2 changes: 1 addition & 1 deletion aidd_docs/memory/project-brief.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ What this project is, the problem it solves, and its domain language. The non-de
| Memory | the bank under `aidd_docs/memory/`, loaded every session |
| Marketplace | `.claude-plugin/marketplace.json`, the plugin registry |
| Concern | what a plugin owns; decides where a capability lives |
| Run journal | what a session did, appended by a hook under `aidd_docs/runs/` |
| Run journal | what a session did, appended by a hook under the clone's common git directory (`<git common dir>/aidd/runs/`) |
| Promote | sending `next` to `main`, which opens the release |

## Key features
Expand Down
4 changes: 2 additions & 2 deletions aidd_docs/runs/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# aidd_docs/runs

Where the run journal's records land once AIDD telemetry is turned on. This directory being present or committed is **not the permission**. The single authoritative switch is `.aidd/config.json`'s `telemetry.enabled`, read by `plugins/aidd-telemetry/hooks/journal.js` at the point of every write, never cached across a session. With that switch on, `aidd_docs/runs/` is created on demand if it does not already exist; with it off, no record lands here regardless of whether this directory exists. Records are ignored by git (see `.gitignore`), so cloning the repository never carries anyone's session history.
The run journal's contract. Records no longer land in this directory: once AIDD telemetry is turned on, the hook writes them under the clone's common git directory, `<git common dir>/aidd/runs/` (for a plain checkout, `.git/aidd/runs/`), shared by every worktree of the clone and outside every working tree, so `git worktree remove` never takes them and no commit can carry them. `AIDD_RUNS_DIR` replaces that location entirely. The single authoritative switch is still `.aidd/config.json`'s `telemetry.enabled` in the checkout a session runs in, read by `plugins/aidd-telemetry/hooks/journal.cjs` at the point of every write, never cached across a session; the directory is created on demand. Journals an earlier version wrote into a live checkout's `aidd_docs/runs/` are still read by the CLI, never written; they stay git-ignored (see `.gitignore`). Why: [one run journal per clone](../memory/internal/decisions/one-run-journal-per-clone.md).

## Shape

Expand Down Expand Up @@ -59,7 +59,7 @@ through a shell command or an `apply_patch` becomes visible at all β€” and it ca
principle, catch a file something else on the machine wrote in the same window. A reader
that must not risk that filters on this field.

`worktree_id` is git's own name for the linked worktree the session ran in, and `worktree_repo_id` names the repository those worktrees share β€” read from one `git rev-parse`, never from an agent runner's environment variable, which names that runner's concept rather than the repository's. A plain checkout, the common case, carries **neither key at all**: absent, never `null` and never `""`, because an empty string would gather every plain checkout into one group as though they were the same worktree.
`worktree_id` is git's own name for the linked worktree the session ran in, and `worktree_repo_id` names the repository those worktrees share β€” read from one `git rev-parse`, never from an agent runner's environment variable, which names that runner's concept rather than the repository's. A plain checkout, the common case, carries **neither key at all**: absent, never `null` and never `""`, because an empty string would gather every plain checkout into one group as though they were the same worktree. Every worktree of a clone writes into the same directory, so these two keys are what keeps their sessions apart.

Neither `file_written` nor `task_declared` ever carries a `task_id`: task identity is a derivation from the path, and derivations belong to whatever reads the log, not to the hook that writes it. `task_declared` differs from `file_written` in what it takes as evidence, not in what it stores: a mention in a tool call's arguments (a read, an edit, a shell command line) rather than a payload naming a write outright - the move that reaches a task on a tool whose payload never hands over a written path at all. A reader turns a run of `task_declared` lines into a bounded interval, closed by whichever of a later declaration or a `turn_end` comes next; see `aidd_docs/product/metrics-contract.md`'s "Attributing records to a task".

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# A worktree session outlives the worktree's removal

Refines [#932](https://github.com/ai-driven-dev/framework/issues/932).

Whoever asks what a task cost must get the answer even when the work was done by an agent in a linked worktree, and even when that worktree has already been removed. Today the journal is written inside the worktree's tree, the report reads only the current checkout's journal, and `git worktree remove` deletes a journal that is merely ignored without `--force` (measured with git 2.55.0.windows.5). The session then never reaches the sink, and the task reads `no-journal` or does not exist at all. #631 asks for the link to the task and forbids losing a session silently. This is that defect.

The conclusion written in the hook ("one journal per worktree, at the worktree's root") is amended, along with its claim that `worktree_id` alone serves cross-worktree joining. Its reasons still hold: nothing writes into another branch's tree, and a bare clone has no main working tree. The clone's journal therefore lives in one place, under the clone's common git directory, outside every working tree and outside any worktree's own git directory. `git worktree remove` does not delete it. It is not a checkout file, so it is never committed and dirties no branch. Every reader (report, local read, diagnosis including its unrecognised-records evidence, forget) uses that place, and `AIDD_RUNS_DIR` still replaces it entirely.

## What Is Clear

- A report run from any checkout of the clone counts the sessions of all its worktrees, past ones included, in the totals and in the task or flow. Journals from two clones of the same remote are not gathered together; the totals and the project axis already pool them through the machine's sink.
- Writing happens during the session. A report run only after the removal comes too late.
- `worktree_id` and `worktree_repo_id` stay on the `session_start` line, absent on a plain checkout. The report gains no worktree axis: two worktrees that touched the same task add up their cost. Only the cumulative figure matters: a worktree is a technical artefact an agent runner names, and the task, backlog, flow and agent axes already answer what a piece of work cost. #932 is amended accordingly; `worktree_id` serves only to explain an attribution from the journal.
- One copy is authoritative. No second journal in the worktree's `aidd_docs/runs/`: the two would diverge.
- Journals already written in a worktree that still exists are read alongside the new place, otherwise today's sessions stay invisible. New lines no longer go there.
- A worktree removed before this change, with no earlier read, stays lost. Transcripts are not enough: without a journal, nothing names the session.
- The working checkout's switch still decides whether to write. The person's refusal always wins. The new directory gets the same owner-only permissions `aidd_docs/runs/` gets today; the `.gitignore` entry has no purpose there.
- The sink stays per machine. Task attribution does not move into it: it is still derived from the journal at report time.
- Catch-up into the sink stays gated by the switch of the checkout running the report: measurement off stores nothing, by design (`report-cost-use-case.ts`). Checkouts of one clone share the committed switch in practice.

## Still Open

- The backlog axis resolves a task folder in the current checkout's tree, so a session whose task folder exists only on another worktree's branch reads `none`. Tracked as a defect on #975, outside this change.

## Next Move

Nothing left to decide here: the backlog-axis defect lives on #975. The worktree axis is settled: the report keeps the cumulative figures only, and #932 is amended to say so.
Loading
Loading