Repository navigation
feat(repo): record public_at and guidance:rev history for leak checks - #359
antonwolfy wants to merge 5 commits into
Conversation
Settles the recording half of intel#290 ("repo compare: flag replay tasks a guidance revision may have seen"), as decided on the issue: committer date, not author date; the field is public_at; the per-file latest commit is uncapped and the newest-first list is capped. - repo tasks replay writes public_at into each task.json: the earliest createdAt of a gold thread's first comment, compared as times, as the threads file gives it. Threads the task leaves out do not count; with no timed gold thread the key is absent. - A guidance:rev:<rev> arm's meta.json arm_rev adds guidance_latest (the commit reachable from <rev> that last touched each guidance file at <rev>), guidance_commits (the commits touching a path the guidance globs match, newest first by committer date, at most 200) and guidance_commits_truncated, each commit with %aI and %cI dates. One git log per (git dir, revision, globs) in a process, in the new repos/guidance_history.py. The keys are records only: meta.json feeds neither the cell name, the execution id nor the batch's reuse and replace checks, so an existing batch resumes as before. repo compare is unchanged; its flags come in a later change. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
|
✅ OpenCodeReview: Review complete: 0 finding(s) across 9 selected item(s). Review details
|
There was a problem hiding this comment.
🟡 Changes recommended
Valid subjects or filenames containing the record delimiter can abort guidance revision runs, while history collection omits symlink-target and merge-resolution edits and permits duplicate full-history reads under concurrency.
4 open issues
What changed in this PR
4 new findings.
This PR records replay review times and guidance revision history for future leak checks under #290. It adds metadata without changing repo compare.
Changes:
- Records optional
public_atfrom the earliest valid gold-thread timestamp. - Adds per-file latest commits and capped guidance history to
guidance:rev:metadata. - Documents the new fields and adds timestamp, history, and caching tests.
| File | Description |
|---|---|
dokimi/tests/test_repo_replay.py |
Tests timestamp ordering, excluded threads, and missing times. |
dokimi/tests/test_repo_guidance_history.py |
Tests history dates, ordering, caps, revision scope, and caching. |
dokimi/tests/test_repo_arms.py |
Allows additional revision metadata while preserving existing assertions. |
dokimi/dokimi/repos/workspace.py |
Attaches guidance history to revision-arm metadata. |
dokimi/dokimi/repos/replay/build.py |
Computes and writes optional public_at. |
dokimi/dokimi/repos/guidance_history.py |
Collects, filters, sorts, and caches guidance commits. |
dokimi/docs/repo-evaluation-contract.md |
Defines the new task and arm metadata fields. |
dokimi/changelog.d/20261101_120000_antonwolfy_replay-leak-record.md |
Announces metadata for future leak checks. |
Review effort: Balanced
Settles the review of intel#359 on the guidance history a guidance:rev: arm records in meta.json arm_rev (intel#290, first half). - The log was split on \x1e and \x1f, which a subject or a path may hold: one such commit anywhere in the history raised ValueError and aborted the arm's workspace preparation. It is now read with -z --raw, where every field ends with a NUL and each file is a ':'-led status record followed by its one path, so a path is never read as a header nor a header as a path. Git cuts a subject at a NUL; a header that does not start with a commit id is a PrepError. - lru_cache did not coalesce concurrent misses, so cells of one arm that repo run's thread pool prepared together each ran the full-history log. A lock per key now lets one thread read while the others wait for its answer; different keys still read in parallel. - A merge listed no files, so a guidance edit made while resolving a merge was in neither output. With --diff-merges=dense-combined a merge counts for a path its result changes against every parent (a conflict resolution, an edit made in the merge, or a clean merge of edits to different parts of one file); a merge that takes a file from one parent still does not. - The arm installs a guidance link as its target's bytes, but only paths the globs match were counted. The links now reach arm_rev_history and are in its cache key: a commit touching a link's target counts for the link's path. The contract (section 3) and the changelog fragment say what is counted. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #359 +/- ##
========================================
Coverage 94.29% 94.29%
========================================
Files 308 309 +1
Lines 39322 39427 +105
Branches 6651 6666 +15
========================================
+ Hits 37079 37179 +100
- Misses 1430 1434 +4
- Partials 813 814 +1
🚀 New features to boost your workflow:
|
Settles two review points on the guidance history of a guidance:rev: arm (intel#290, recording half). The repository's own config could change what the history's git log prints. log.showSignature=true put verification text outside the NUL-framed fields, so one signed commit anywhere in the history stopped the arm's workspace from being prepared. Checked against git 2.51, four local keys change the output: log.showSignature, log.showRoot=false (the root commit with no files), i18n.logOutputEncoding (a UTF-16 subject holds NULs) and diff.ignoreSubmodules=all (a commit that adds a gitlink is dropped). The log now passes --no-show-signature, --root, --encoding=UTF-8 and --ignore-submodules=none. A guidance link resolved through intermediate links (AGENTS.md -> docs/current -> old.md) recorded only its final file, so a commit that only retargets docs/current changed what the arm installs but was in neither history output. _guidance_at now also returns every path each link resolves through (split into _link_chain, which lowers its complexity below the baseline), and a commit that touches any of them counts for the link's path and is part of the cache key. The arm still installs the final file's bytes. Tests with real repositories: a signed commit under repository-local log.showSignature=true, each other pinned key leaving arm_rev as it is without it, and a commit that only retargets an intermediate link. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
|
Review rounds at
The
|
napetrov
left a comment
There was a problem hiding this comment.
Verdict: comment only, not approved. One blocking thread (a local shallow checkout gives a silently wrong arm history), two nits, one question. Head 1b75473, merge base 93a5062, lane L2. CI: 0 failing Actions jobs.
| # | Finding | Label | Provenance | Scope | Probe |
|---|---|---|---|---|---|
| 1 | local shallow checkout: arm history stops at the boundary commit | blocking | introduced | new surface | probes/lead_verify.py (a1) |
| 2 | a CR in a guidance path or subject drops the commit | nit | introduced | new surface | probes/lead_verify.py (b2) |
| 3 | date tests expect Z; git 2.43 prints +00:00 |
question | - | - | probes/lead_verify.py (b3) |
| 4 | _public_at docstring says Z counts as none |
nit | introduced | new surface | probes/d4.py |
Raised before: T1 to T7 (exai-bot, copilot) are fixed on head; no new thread repeats them. Finding 2 is near T1 (framing) but is a different root cause (decoding).
Unverified: a real repo run with a shallow source (precondition of 1); git 2.51 pins and the body's timings (not probed); eval_dir-only commits in guidance_commits (contract reads as intended).
Coverage: diff, contract doc and test assertions read; passes at level high with VERIFY on 3 candidates plus the docstring, probed on head (new code, no base); prior threads checked by reading. Not run: test suite, lenses. Not looked at: full-history git log cost on large repos.
| keep = ("PATH", "LANG", "TMPDIR", "SYSTEMROOT") | ||
| env = process.Env.only(keep, {"GIT_CONFIG_NOSYSTEM": "1", "GIT_CONFIG_GLOBAL": os.devnull, "HOME": os.devnull, | ||
| "GIT_TERMINAL_PROMPT": "0"}) | ||
| done = process.git("-C", str(git_dir), "log", "-z", "--raw", "--no-abbrev", "--no-renames", |
There was a problem hiding this comment.
nit: a CR in a guidance path or subject drops the commit from the history
process.git returns stdout through _decode (dokimi/dokimi/process.py), which turns each \r into \n: the path then matches no glob and the commit disappears without a report.
How to check: commit subject guidance\redit touching a\rb/AGENTS.md; arm_rev_history gives no guidance_latest and no guidance_commits (probes/lead_verify.py b2, head only: new code).
Expected: a\rb/AGENTS.md latest at that commit and the subject kept, as the PR body's framing table promises for the record delimiter.
Fix: read _git_log as bytes and split the fields before decoding; add a test with a CR in a path.
There was a problem hiding this comment.
Fixed in f6a9cb9. _git_log now returns stdout_bytes, and _parse_log splits on NUL as bytes, then decodes each field as UTF-8 with replace. That is the same decoding dokimi.process uses, so a path that is not UTF-8 gives the same string as the arm's own file listing. New test test_a_carriage_return_in_a_guidance_path_or_a_subject_is_kept: a commit with subject guidance\redit that touches a\rb/AGENTS.md is that path's guidance_latest, and the subject keeps its \r.
End to end, the arm's own file listing (_guidance_at's ls-tree, unchanged from main) still reads through process's text decoding. So prepare_workspace does not install a guidance file with a \r in its path, and that path is not in guidance_latest. The commit is listed in guidance_commits. That listing is on main and is left for a separate change.
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Settles four review points on the guidance history a guidance:rev: arm records in meta.json arm_rev (intel#290, recording half). - A local source is used as it is, and in a shallow checkout git log takes the boundary commit for a root: with guidance v1 -> v2 -> a code commit cloned at --depth 1, the code commit was recorded as the last to touch AGENTS.md and the only guidance commit. The history now asks git rev-parse --is-shallow-repository first and raises PrepError for a shallow git dir, naming it and git fetch --unshallow. dokimi's own clones of a remote are bare and never shallow; a blob-less clone keeps working, as log --raw compares object ids only. - process.git's text output turns each \r into \n, so a guidance path or subject holding a \r lost it, and the commit matched no glob and was dropped without a report. The log is now split on NUL as bytes and each field decoded as UTF-8 on its own (with replacement, as dokimi.process decodes). - git 2.51 prints a UTC %aI / %cI as Z, git 2.43 as +00:00. A UTC date is now recorded as Z whichever git read it; other offsets stay as printed. - The _public_at docstring said a Z time counts as none. A time without an offset counts as none; GitHub's Z is UTC and counts, now pinned by a test. The contract (section 3) and the changelog fragment name the refusal and the date form. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
|
Review round at
The CI is green on |
vlad-perevezentsev
left a comment
There was a problem hiding this comment.
Review at f6a9cb9. The history read is right: NUL framing, the pinned flags, merges, link chains and the shallow refusal hold up in the tests and in my runs. public_at can come out later than the real time, and the history counts eval_dir fixtures. Both are in the line comments.
Agreed with @napetrov on the \r in a guidance path: the history keeps it now, and fixing _guidance_at's listing in a separate change is fine.
What I ran, at f6a9cb9:
make ci-local --offline: 0 failed, 19 passed, 13 skipped (network and Docker jobs).- The CI
external-catalog-smokejob (docker-oracleanddocker-probeon the pinned catalog task): both pass, every isolation probe closed. _read_guidance_commitson a local numpy clone (41,870 commits): 4.9 s and 19 MB ofgit logoutput for the first cell, 1.2 s of it in git.- A history with empty commits around a guidance commit: parsed right, and only the guidance commit is listed.
Checked, no issues
_link_chainsplit: same refusals as before (out of the tree, intoeval_dir, a cycle, a dangling link), and a plain file still gives no chain.guidance_latest: it is the newest commit by committer date that touched the path anywhere in the reachable history, as contract §3 says. An edit on a branch later dropped by-s ourscan therefore be named, but that errs toward reporting a leak, which is the safe side.- Concurrency and cache: one read per key; the lock map only grows by one small entry per key.
- Docs and changelog: contract §3 and §4.6 and the fragment describe the new fields and the shallow refusal. No scoring or
repo comparechange, so published numbers stay comparable.
| try: | ||
| at = datetime.fromisoformat(t.created) | ||
| except ValueError: | ||
| continue | ||
| if at.tzinfo is not None: # a time with no offset is not comparable with one that has it | ||
| times.append((at, t.created)) | ||
| return min(times)[1] if times else None |
There was a problem hiding this comment.
public_at is taken from only the gold threads that have a usable time, so it can be later than the real one.
A gold thread whose createdAt is missing, not ISO 8601, or has no offset is skipped, and the minimum is taken over the rest. If the skipped thread was posted first, public_at moves later, and a guidance commit made between the two times reads as made before the answer was public: the leak check misses it. test_a_z_time_counts_and_a_time_with_no_offset_does_not pins exactly this: the thread at 2026-10-01T00:00:00 is dropped and public_at is 2026-10-02T12:00:00Z. In dokimi a value that was not measured should not read as a number.
Suggestion: leave public_at out when any gold thread has no usable time (or record which threads were left out), and make that test expect no public_at.
| def _counts_for(path: str, globs: tuple[str, ...], linked: dict[str, list[str]]) -> list[str]: | ||
| """The guidance paths a change to ``path`` counts for: itself when a glob matches it, and each link through it.""" | ||
| return ([path] if any(match_glob(g, path) for g in globs) else []) + linked.get(path, []) |
There was a problem hiding this comment.
The history counts paths under eval_dir, which the arm never installs.
_guidance_at leaves out the guidance files under eval_dir (step 2 deletes it), but _counts_for matches the globs against every path in the log. With **/AGENTS.md, a commit that only edits .agent-eval/tasks/x/AGENTS.md (a task fixture) is listed in guidance_commits. It looks like a guidance change after public_at that cannot reach the agent, and it takes a place under the 200 cap.
Suggestion: pass eval_dir in (it is in the cache key anyway through the manifest) and skip _is_within(path, eval_dir) paths in _counts_for, as _guidance_at does; add a fixture-only commit to a test.
| def _git_log(git_dir: Path, sha: str) -> bytes: | ||
| """Every commit reachable from ``sha`` with its dates, subject and the paths it touched; a merge | ||
| with the paths its result changes against every parent.""" | ||
| return _git(git_dir, f"log {sha[:12]}", "log", "-z", "--raw", "--no-abbrev", "--no-renames", | ||
| "--diff-merges=dense-combined", *_PINNED, _FORMAT, sha, "--") |
There was a problem hiding this comment.
The first guidance:rev: cell reads and parses the whole history of the repository, not just its guidance history.
On a local numpy clone (41,870 commits) _read_guidance_commits takes about 4.9 s: 1.2 s for git log (19 MB) and about 3.7 s for splitting, decoding and matching every path against the globs in Python. That is fine for numpy. It grows with the size of the repository, though, and the other cells of the arm wait on the lock meanwhile. On a repository ten times larger it is close to a minute and a few hundred MB.
Suggestion: no change needed for this PR if numpy-size repositories are the target; otherwise narrow the log with :(glob) pathspecs for the guidance globs plus the link-chain paths, and keep match_glob as the final filter. A note in the docstring about the cost would also help.
| def _git(git_dir: Path, what: str, *args: str) -> bytes: | ||
| """The stdout of ``git -C git_dir *args`` as bytes, with no system or global config; ``what`` names it in an error.""" | ||
| keep = ("PATH", "LANG", "TMPDIR", "SYSTEMROOT") | ||
| env = process.Env.only(keep, {"GIT_CONFIG_NOSYSTEM": "1", "GIT_CONFIG_GLOBAL": os.devnull, "HOME": os.devnull, | ||
| "GIT_TERMINAL_PROMPT": "0"}) |
There was a problem hiding this comment.
Nit: _git builds again the isolated git environment that workspace._git_env defines, and _refuse_shallow repeats the check in source.py:388.
If the isolation policy changes later (one more allowed variable, a safe.directory setting), the history read and the arm's ls-tree on the same git dir can start to behave differently.
Suggestion: move the env policy (without the fixed author) into one helper that both modules use.


Summary
First half of #290, "repo compare: flag replay tasks a guidance revision may have seen" (issue, decisions). A
guidance:rev:<sha>arm runs a later revision of a repository's guidance againstreview_replaytasks built from earlier PRs. If that guidance was edited after a task's review, the edit can encode the review. The batch has no way to show this today. Onmain, a replaytask.jsonholdssource_pr,source_url,reviewed_commit,merge_commitandleft_out_files, and no time. Aguidance:rev:cell'smeta.jsonarm_revis exactly{"rev", "sha", "removed"}, whichtests/test_repo_arms.pyasserted onmain. This PR records both facts. The flags inrepo compareare the second PR, as the issue decided;repo comparedoes not change here.The second commit settles this PR's first review: four points from five comments. The third commit settles two more comments. At the commit before each fix:
<rev>\x1e(e.g. subjectguidance\x1eedit)ValueError: not enough values to unpackfrom workspace preparation; by the code, not run: it is not one of the errorsrepo runrecords for a cell (repos/runner.py), so it ends the runrepo run's thread pool prepares togethergit logAGENTS.mdAGENTS.md'sguidance_latestis an older parent commitAGENTS.md'sguidance_latestAGENTS.md -> docs/GUIDE.md, a later commit edits onlydocs/GUIDE.mdAGENTS.md'sguidance_latestis the commit that made the linkAGENTS.md'sguidance_latestlog.showSignature=trueand a signed commit anywhere in the historyPrepError: git log: a commit starts with 'No signature\n<sha>', not a commit id; the cell is not preparedlog.showRoot=false,i18n.logOutputEncoding=UTF-16ordiff.ignoreSubmodules=allPrepErroron the UTF-16 output; a commit that adds a gitlink at a guidance path is missingarm_revas without the keyAGENTS.md -> docs/current -> old.md, a later commit only retargetsdocs/currentto an existingreview-derived.mdAGENTS.md'sguidance_latestis the commit that addedreview-derived.mdAGENTS.md'sguidance_latestChanges
repo tasks replaywritespublic_atinto eachtask.json(repos/replay/build.py::_public_at). It is the earliestcreatedAtof a gold thread's first comment, kept as the threads file gives it. Times are compared as datetimes, not strings:2026-10-02T20:00:00+09:00is earlier than2026-10-02T12:00:00Z. A thread the task leaves out does not count. A time that is not ISO 8601, or has no offset, counts as no time; GitHub'sZis UTC and counts. When no gold thread has a time, the key is absent.New
repos/guidance_history.py: for aguidance:rev:<rev>arm,meta.jsonarm_revnow also holds:guidance_latest: for each guidance file the arm wrote, the commit reachable from<rev>that last touched it, or for a link it or any path its resolution goes through (each intermediate link and the file it resolves to), by committer date. Not capped.guidance_commits: the commits reachable from<rev>that touch a path the guidance globs match or a path a guidance link resolves through, newest first by committer date, each with itssubject. At mostGUIDANCE_COMMITS_CAP(200).guidance_commits_truncated:truewhen the cap cut the list.Each commit carries
sha,authored(%aI) andcommitted(%cI), a UTC date writtenZwhichever form the git that read it prints (git 2.43 prints+00:00, 2.51Z). The history comes from onegit log -z --raw --no-abbrev --no-renames --diff-merges=dense-combined --no-show-signature --root --encoding=UTF-8 --ignore-submodules=noneover<rev>, filtered withmatch_glob. It is memoized per (git dir, revision, globs, links), so every cell of an arm reads it once. The output is split on NUL as bytes and each field decoded on its own, so a\rin a subject or path is kept. A shallow git dir is refused with aPrepErrornaminggit fetch --unshallow: its history stops at the boundary commit and would name a later commit as the last to touch a guidance file.Review of this PR (second commit):
_parse_logreads the-z --rawoutput. Every field ends with a NUL. The four header fields are taken by position, and each file is a:-led status record followed by exactly one path. A path that looks like a commit id or a status record (:M) stays a path. Git cuts a subject at a NUL (git commitrefuses one), and a header field that is not a commit id is aPrepError, not aValueError._guidance_commitsholds a lock per key around thelru_cached read, so one thread reads and the others take its result. Different keys still read in parallel.--diff-merges=dense-combined, a merge counts for a path its result changes against every parent: a conflict resolution, or an edit made in the merge. A merge that takes a file as one parent has it is not listed; the commits it merged are. With git 2.51,--rawalso lists a clean merge that joins its parents' edits to different parts of one file, because the result differs from each parent. A test pins this, and the docstring and contract say so._apply_revpasses_guidance_at's links toarm_rev_history. A commit that touches a link's target counts for the link's path, and the links are part of the cache key.Review of this PR (third commit):
_git_logturns off the system and global config, but a local source's own git dir keeps its config. With git 2.51, four of the keys a repository can set change this command's output, and each is now overridden by a flag (guidance_history._PINNED):log.showSignature(--no-show-signature),log.showRoot=false(--root),i18n.logOutputEncoding(--encoding=UTF-8) anddiff.ignoreSubmodules=all(--ignore-submodules=none). Set one at a time, about 40 otherlog.*,diff.*,color.*,core.*andformat.*keys left the output byte for byte the same. Among them arecolor.ui=always,diff.relative,log.decorate,log.followandcore.abbrev. The keys that the explicit--no-renames,--diff-mergesand--formatalready override (diff.renames,log.diffMerges,format.pretty) need nothing._guidance_atnow also returns, for each guidance link, every path its resolution goes through: the intermediate links, then the final file. The walk moved into a new_link_chain, which lowers_guidance_at's complexity from 11 to 5 and removes its entry fromscripts/growth_baseline.json._apply_revpasses those chains toarm_rev_history. A commit that touches any path of a chain counts for the link's path, and the chains are part of the cache key._apply_revstill installs the final file's bytes.repos/workspace.py::_apply_rev: one call added wherearm_revis built, with no new branch.guidance:none|raw|bridgeandpatch:are unchanged.Contract: §3 describes the three
arm_revkeys, link chains and which merges count. §4.6 addspublic_atto the list of fields the replaytask.jsonadds for the reader.Changelog fragment (Added).
Risk
The output of
repo tasks replayandguidance:rev:cells gains keys; nothing is taken away. One input is now refused: aguidance:rev:cell whose local source is a shallow clone fails its preparation (dokimi's own clones of a remote are bare with no--depth, so they are never shallow). The new keys do not change a cell's identity. The cell name comes from task, arm, model and rep. The execution id comes from the agent options. The batch's reuse and replace checks (batch_ledger.reused_result,check_batch_executions) readresult.json. None of them readsmeta.jsonarm_rev. So rerunning an existing batch neither refuses nor reruns its finished cells; only cells that run again get the new keys. Aguidance:rev:cell now runs one moregit logover the revision's whole history, once per process. Its--rawoutput carries two object ids per file, so it is two to three times the--name-onlyoutput, and--diff-merges=dense-combinedalso diffs every merge against its parents. Measured on bare clones,--name-onlyagainst the second commit's form: this repository (485 commits, 5 merges) 0.4 MB against 1.2 MB, both under 0.1 s; dpnp (3,127 commits, 185 merges) 1.0 MB against 2.7 MB, both 0.1 s; numpy (42,470 commits, 10,335 merges) 9.3 MB in 0.67 s against 19.3 MB in 0.88 s. The first commit's text also cited 3.7 s (51 MB) for--name-onlyover a 9,384-commit repository; the--rawform was not run there. The third commit's four flags only bring back git's default output, so these sizes still hold for a repository that sets none of those keys; they were not measured again. The time limit is 1800 s, the same as workspace preparation's other git calls. A merge that cleanly joins two edits to one guidance file is listed although it adds no text of its own, so it can make a file'sguidance_latestlater than the last commit that wrote text. The same goes for a link's chain: a commit that touches an intermediate link or the final file counts for the link, even when the bytes the arm installs did not change.Testing
On a Linux review host, at
1b75473(onmain93a5062):make ci-local: 30 passed, 0 failed, 7 skipped. Skipped: external-catalog-smoke (needs docker), test-latest, docs deploy and pip-audit report (job conditions false on a PR), cli-surface "Package the result", conda-forge-deps (no micromamba), semgrep secrets. 12022 tests passed, 59 skipped, 6 xfailed; changed-line coverage 98% (121 lines; the two missed are thegit logtimeout and failure raises).At the second commit,
d35b581: 30 passed, 0 failed, 7 skipped; 12017 tests passed, 59 skipped, 6 xfailed; changed-line coverage 97% (96 lines). At the first commit,c21791a: 28 passed, 2 failed, 7 skipped. Both failures were the host:Install gitleaksgot HTTP 504 four times, soScan commitshad no binary to run (exit 127). The same scan, run separately with CI's pinned gitleaks 8.30.1 (checksum checked) over the commit: no leaks found. Both steps pass atd35b581.New tests:
tests/test_repo_guidance_history.pybuilds a toy history with per-commitGIT_AUTHOR_DATE/GIT_COMMITTER_DATE. It includes a commit authored two months before it was committed, and a commit whose committer date is older than its parent's. The tests cover:guidance:rawandpatch:cells;git logfor two cells of the arm;git logfor four threads asking for one key at once;\x1eand\x1f, a guidance path holding\x1e, a file named like a commit id and a file named:M;PrepError;AGENTS.md, a plain merge of a branch with a guidance commit, and a clean merge of edits to different parts ofAGENTS.md;AGENTS.md -> docs/GUIDE.md;ssh-keygenin the test's temporary directory (gpg.format=ssh), under repository-locallog.showSignature=true: it is read, listed andAGENTS.md'sguidance_latest. The test skips only withoutssh-keygen; it ran here;log.showRoot=false,i18n.logOutputEncoding=UTF-16anddiff.ignoreSubmodules=all, one at a time, over a history with a root commit that addsAGENTS.md, a non-ASCII subject and a commit that adds a gitlink atvendor/AGENTS.md: each gives the samearm_revas without the key;AGENTS.md -> docs/current -> old.md, then a commit that addsdocs/review-derived.md, then one that only retargetsdocs/currentto it: the retargeting commit isAGENTS.md'sguidance_latestand first inguidance_commits, and the workspace'sAGENTS.mdholdsreview-derived.md's text.In
tests/test_repo_replay.py, the tests coverpublic_atas the earliest gold thread's time, a left-out thread with an earlier time not counting, a mixed-offset pair, and no key when the gold threads have no time.Mutants: 15 breaks in a copy of the tree, 17 test runs, and every run failed. The breaks:
public_at;public_at: null;authoredholding the committer date;>=for truncated;git log --allinstead of<rev>;arm_rev;guidance:raw.Mutants for the second commit: 6 breaks, 7 test runs, and every run failed on its assertion. The breaks:
\x1e/\x1fsplit and--name-onlylog (ValueError);--diff-merges=dense-combineddropped (the resolution merge and the clean merge are not listed);--diff-merges=first-parentin its place (the plain merge is listed);{}passed in place of the links;The second commit's new test file against the first commit's code: 6 of its tests fail and 8 pass. The plain-merge test passes there, because that code listed no merge at all. The concurrency test and the commit-id test fail there on the new
linksargument and the new_parse_log, so the lock and commit-id mutants above are what pin them.Mutants for the third commit: 5 breaks, 5 test runs, and every run failed. The breaks:
--no-show-signaturedropped (the signed-commit test:PrepError: git log: a commit starts with 'No signature\n<sha>');--rootdropped (thelog.showRoot=falsecase);--encoding=UTF-8dropped (the UTF-16 case: aPrepError);--ignore-submodules=nonedropped (thediff.ignoreSubmodules=allcase);AGENTS.md'sguidance_latestis the commit that addedreview-derived.md).The third commit's test file against the second commit's code: its 5 new tests fail and the other 14 pass.
Not run: the new tests against
main's code. This is a new feature, and onmainthey fail at import (dokimi.repos.guidance_historydoes not exist).Merged together at
f6a9cb9with the branches for review_replay: optional judged recall over the credited hits #291 (judged recall,8e05118), novelty: spread the atom cap across the artifact instead of its opening #293 (novelty cap spread,82d576a) ontomain41b3d8d: the full suite (12641 passed, 5 skipped, 6 xfailed),mypy(318 files) andruff checkpass.make ci-localis green atf6a9cb9(30 passed, 0 failed).Revert
Revert the commits. The
public_atandarm_revkeys already written into task directories and batches stay. Nothing reads them yet.Checklist
pytestpassesruff,mypy, and schema checks passAGENTS.md.github/.licenserc.yaml)