Skip to content

feat(revise): capture each merged pull request's harvest into the log, so prepare reads the month from there - #118

Merged
mmaclay merged 8 commits into
mainfrom
feat/87-capture-at-merge
Nov 7, 2026
Merged

mmaclay merged 8 commits into
mainfrom
feat/87-capture-at-merge

Conversation

@mmaclay

@mmaclay mmaclay commented Nov 7, 2026

Copy link
Copy Markdown
Member

Origin: #87, tracked in #89: harvesting a month in one prepare started about 60 agents in one session, past the subagent cap, and a failure late in the run cost everything read before it.
Sandbox: two new offline scenarios on Sonnet, both passing on the final commit. revise-prepare-captured: with the window's one merged pull request already captured, prepare restores it from the log and starts no pr-reader, thread-analyst or guidance-extractor ($0.19). revise-prepare-uncaptured: with no capture, capture.py load names the pull request and the days, and prepare goes to capture them, and does not read the month as empty ($0.21). An earlier run of the second scenario found a real bug, now fixed: a log not yet made was refused as an unreadable input instead of read as holding no capture. Private reference month: three pull requests (199 comments) stored and restored through the log give a guidance tally identical, line for line, to the one from the original slices.
Canary: the driver's first weekly /lasp-dev:revise capture, then the next prepare. Step 1 should print a harvest block from capture.py load, and the run should start no pr-reader for this repository unless git shows a merge the log lacks. A prepare that starts analysts or the extractor for pull requests a capture already read is the failure to look for.

What changes

The month's reading moves out of prepare. A new mode, /lasp-dev:revise capture, which the driver runs weekly:

  • harvests the window's merged pull requests again, with one pr-reader, so each record and comment list takes the replies since the last capture;
  • runs the analysts and the guidance extractor only on the pull requests new or changed since then;
  • keeps each pull request's harvest in the standard's log, beside its record;
  • commits it as a pull request on the person's yes.

prepare restores the window from the log. It captures this repository only when git shows a merge the log lacks, and each other covered repository always, since no git here shows what merged there. On a captured window it starts no harvest, analyst or extraction agent for this repository.

  • scripts/capture.py, three commands:
    • pending names the pull requests whose comment list is new or changed.
    • store writes <log>/capture/pr-NNNN/:
      • which files: the harvest line, the dump files the analysts and the record read, the comment list, a one-pull-request slice of guidance items, and the analysis block;
      • for pull requests not re-read, it keeps the earlier items and analysis;
      • it appends the days searched to covered.txt;
      • it refuses a report whose count line does not count its lines, items the guidance check refuses, and a --not-read that pending does not name.
    • load restores .review/harvest/ and the guidance slices, and prints the harvest block pr-reader would have. It refuses (exit 2) on uncovered days, merges git lists with no capture, failed fetches and missing records.
  • skills/revise/capture.md: the new mode. Before harvesting, it checks that no earlier capture's pull request is still open.
  • skills/revise/prepare.md:
    • step 1 loads from the capture;
    • step 2 runs analysts only for loaded records with no block, and the guidance check and tally with no extractor;
    • step 10 commits <log>/capture/.
  • Docs:
    • skills/revise/SKILL.md: the mode, the files and the bounds;
    • docs/contracts.md: "The capture", a new section;
    • docs/design.md: why the month is read as it merges;
    • README: the skill row, the log layout and the .review/ rows;
    • GUIDE.
  • Sandbox: revise-prepare-captured and revise-prepare-uncaptured, with a fixture under sandbox/fixture/capture/.
  • revise 2.24.0. feat(revise): read a reply an agent drafted, which its poster read, as the poster's guidance #117 also bumps to 2.24.0, so whichever merges second takes 2.25.0.

Not changed: record.py and the record contract, pr-reader, the analysts, the extractor, the sweep, apply, correct.

Review

Three adversarial rounds:

  • Round 1 (3 blocking, 6 should-fix):
    • unparsed report lines were silently dropped;
    • the capture lacked the thread files the analysts read;
    • another repository's stale analysis blocks were copied in;
    • a by-day check could deadlock prepare;
    • an unread slice blocked every capture;
    • records were frozen at their first capture, so later replies were lost. This one led to re-harvesting the whole window at each capture.
  • Round 2 (3 blocking, 6 should-fix):
    • the sandbox pack's tests;
    • a newly unread pull request kept a stale analysis;
    • another repository's same-day merges could be lost, which led to capturing other repositories at every prepare;
    • commit and open-capture gaps.
  • Round 3 (0 blocking, 3 should-fix):
    • three docs still described the old capture rule;
    • the open-capture check now runs before any agent;
    • a stray --not-read is refused.

All fixed.

Decisions for the maintainer

  • Trigger. The driver runs capture weekly by hand. No GitHub Action or scheduled agent: either would need a model's credentials, and a person's answers at the archive's refusal and the commit.
  • Storage. The log keeps each pull request's dump files and comment list, about 2 MB a month on the reference repository, so prepare needs no harvest. The pages/ directory and scratch files are left out.
  • Layout. capture/pr-NNNN/items.json, not pr-NNNN.items.json at the log's root as the issue sketched, so a capture's files sit together and load can restore them as a dump.
  • Other covered repositories. prepare still starts one pr-reader for each, since this checkout's git cannot show their merges. Its extractor reads only what changed.
  • Freshness. prepare reads records as of the last capture. Running capture just before prepare takes the latest replies.
  • Known gap. In the uncaptured scenario, the run tried the gh CLI once before stopping. The sandbox denied it. That habit predates this change.

Closes #87.

🤖 Generated with Claude Code. AI assistance level: 5/5 (not read by a person).

mmaclay and others added 8 commits November 6, 2026 18:13
…, and prepare reads it

A new capture mode harvests the pull requests merged since the last
capture, runs their analysts and guidance extraction, and keeps each one's
harvest line, description, comment list, guidance items and analysis in
the standard's log under capture/pr-NNNN/, with the days searched in
capture/covered.txt (scripts/capture.py store). prepare step 1 restores
the window from the log (capture.py load), captures only the days or merges
the log lacks, and runs the analysts and the extractor only for those, so
a month already captured starts no pr-reader, analyst or extractor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… changed

A record frozen at its first capture missed the replies posted after it,
so each capture harvests the whole window again with one pr-reader, and
capture.py pending names the pull requests new or changed since, the only
ones the analysts and the extractor read. A capture keeps every dump file
the analysts and the record read, takes analysis blocks only from its own
run, refuses a report whose count line does not count its lines, and keeps
a pull request whose slice stayed unread as not read. load matches
captured merges by number, puts the day on each refusal, and refuses a
failed fetch so prepare captures it again. The capture is committed in
both rows, and the scheduled-agent trigger is dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is, fix the sandbox pack

A pull request newly not read kept its stale analysis and lost the block
its analyst had just written: the block is now chosen by what pending
names, not by what was read for guidance. Another covered repository's
merges are not in this clone's git, so prepare captures it first, one
pr-reader that reads nothing unchanged, rather than trusting a same-day
covered line. The guidance check takes only the comment lists a slice
holds, step 10 commits the log's capture, capture's step 6 says what
leaving a clone's capture uncommitted costs and that a capture merges
before the next one, and the sandbox pack counts the two new scenarios
and copies its fixture files one by one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… stray not-read

The check for a capture pull request still open moves to step 1, before
any agent runs, and names how it is found. store refuses a --not-read
pull request that pending does not name, which would otherwise drop its
kept items. capture.md, the contract and the README say, as prepare.md
does, that prepare captures this repository when its git shows a merge
the log lacks and each other covered repository always.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mmaclay
mmaclay marked this pull request as ready for review November 7, 2026 06:13
@mmaclay
mmaclay merged commit fc9dd62 into main Nov 7, 2026
1 check passed
@mmaclay
mmaclay deleted the feat/87-capture-at-merge branch November 7, 2026 06:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

revise: capture each pull request's guidance at merge time

1 participant