Memoize quick gate on tree
Epic: Merge lane — CP-scheduled verification queue, plan-gated. Planner Task 5517; the full PRD is BA Task 5515 and the full architecture is Architect Task 5516. Read both with `task_read` for any clamped detail.
Operator ask (verbatim, excerpt): "Currently when we have multiple runners, each of them runs quick gates many times, then full gate in runner [...] That creates load on the nodes [...]"
This child is INDEPENDENT of the plan flag. It is a pure saving for every project.
## Requirement (work item 3, verbatim)
"Quick-gate memo: `tools/run_gate.py` skips the quick tier when the tree hash + gate version already passed; Stop hook and pre-commit benefit. Independent of the feature flag (pure saving)."
## Approach (architecture L13, settled)
- **Memo key.** Build it from a temporary-index `write-tree`:
1. Copy `.git/index` into a temporary `GIT_INDEX_FILE`.
2. Run `git add -A` against it.
3. Run `git write-tree`.
- The result covers tracked, modified and untracked non-ignored files exactly. The real index and the tree stay untouched.
- **Gate version.** `sha256(gate_quick_cmd)`, plus `sha256` of the content of any argv token of that command that resolves to an existing file OUTSIDE the worktree (packaged scripts). In-tree scripts are already covered by the tree hash.
- **Store.** `$(git rev-parse --git-dir)/jaah-gate-memo/quick.json` holding `{key, tree, version, passed_at}`. It is per-worktree and outside the tree.
- **Scope.** Applies to `--tier quick` ONLY.
- On a hit, print ONE line giving the tree hash and the earlier green time, then exit 0.
- Write the memo only on exit 0.
- Fail open: any read or parse error means run the gate.
- Rejected: keying on `HEAD^{tree}` (decision D5). The Stop hook runs on uncommitted edits.
- Current code: `run_gate` at `tools/run_gate.py:82-126`, `gate_command` at :74-79. The Stop hook is `infra/host/fleet-harness/settings.json:174` and pre-commit is `.claude/hooks/pre-commit.sh:61-62`. Both call `run_gate.py`, so they benefit without edits.
## Acceptance (AC3.1–AC3.5, verbatim)
- AC3.1 `tools/run_gate.py --tier quick` skips running the command when the memo key has already **passed** in this worktree, and prints one line saying it was skipped, giving the key's tree hash and the time of the earlier green run. It then exits 0.
- AC3.2 The memo key is the hash of the **exact working-tree content the gate would see**: tracked files with uncommitted changes plus untracked non-ignored files, not just `HEAD^{tree}`. It is combined with the **gate version**, which is the resolved `gate_quick_cmd` string together with the content hash of the gate entry script(s) the project row names. Any byte change to any of these is a miss.
- AC3.3 Only green results are memoized. A red or refused run (exit 78) is never memoized and always reruns. The full tier is **never** memoized by this item.
- AC3.4 The memo is stored per worktree, outside the tracked tree (for example under the git dir). It never makes the tree dirty, and a slot reset discards it.
- AC3.5 With no memo, or a memo that cannot be read or is corrupt, the gate runs, failing open to running. The memo can never turn a would-be failure into a pass.
Credentials & reach: none. Every oracle is worktree-local unit tests (verified; no external reach).
Out of scope:
- Epic (verbatim): "GitHub merge queue / merge_group; any CP-side forge calls; changing the manual develop → master promotion; cgroup tuning of GHA runners; squash merges."
- BA-derived (verbatim): "No full-tier memo: only quick is memoized."
- Do NOT edit the Stop hook settings, the pre-commit hook, `tools/project_config.py` or `gate_directive`. Do NOT change the full tier's behaviour in any way.
Done means:
- AC3.1–AC3.5 Done-means oracle — in `tools/tests/test_run_gate.py`, use a fake gate command that counts invocations:
- Run the Stop-hook path twice on an unchanged tree: the counter is 1, and the second run prints the skip line.
- Edit a tracked file, then an untracked file, then the gate command, in turn: each edit raises the counter by 1.
- Run a failing fake gate twice: the counter rises by 2.
- A corrupt `quick.json` makes the gate run.
- `git status --porcelain` is unchanged by the memo.
- `--tier full` run twice: the counter is 2.
- On a real slot, two consecutive Stop-hook firings with no edit print the skip line on the second firing.
Required checks: gate, review-lane
Outcome
The quick gate now skips a re-run when this worktree already passed on the same files with the same gate. Key: worktree tree, index tree, HEAD, base branch, gate version, runner identity. The full tier is never memoized. `./dc check full` passed, PR #4811 merged, Jaah Deploy succeeded.
Artifacts
Activity
- Draft
- Ready
- Runner started
- Runner ended (done)