Repository navigation
tooling(agents): one front door for the in-flight tools, and the survey that scoped it - #400
Conversation
…ule, like every other collaborator PCModule already provides workManager, brokerPoller, producerManager and consumerManager; its own javadoc calls it the DI system and PCModuleTestEnv is the established seam for substituting a collaborator in tests. ShardManager was the exception, constructed inline by WorkManager, so it was the one collaborator no test could substitute. This completes that pattern rather than adding a hook for the test that needs it: no test-only branch, no flag, no latch in production. The provider takes the owning WorkManager as a parameter because the two are mutually dependent and WorkManager's constructor is mid-flight when it resolves this - calling workManager() would recurse. The caching is a plain null-check rather than anything thread-safe, which is sufficient because WorkManager has exactly one construction site and construction is single-threaded. Worth knowing before a second call site is added. Enables the reproduction in the next commit, which must swap in a ShardManager that can park the control thread mid-insert. Verified not to disturb anything: full core unit suite green.
…n, and the third precondition it uncovered #31 shipped with unit-level proof and an openly declared gap: no load-level evidence. This closes it with something stronger than a soak - a broker-real reproduction that discriminates every run, with no seed and no sleeps. Building it uncovered why nothing had reproduced the defect before. The loss needs THREE coincidences, not the two previously recorded: a rebalance inside the registration insert loop, the partition returning so the uncommitted tail re-delivers, and - the missing one - NO take-scan of the shard in between. ProcessingShard.getWorkIfAvailable lazily evicts stale containers as it scans, so an ordinary take-scan HEALS the collision before a fresh arrival can hit it. Only a consumer whose pipeline is at target keeps residents alive long enough to eat a fresh record. That explains the standing puzzle: the Chaos Pain Suite is green on defective master not through an unlucky seed but because its own churn is the healing mechanism. Random reproduction was fighting itself. It also narrows the production risk - this bites BUSY consumers, not idle ones. The test drives all three deliberately: a gated user function saturates the pool so the control loop's delta stays at or below zero, a fixed message-buffer size pins the load factor so the target cannot step mid-experiment, a second group member on another topic forces an eager revoke/re-assign, and the insert is parked mid-loop until re-delivery has fully registered. Every await is event-gated - latches, listener callbacks, instrument counters - so a failure is loud rather than slow. Proven both ways. With addWorkContainer's staleness check neutralised it fails naming the exact lost records - 25 of them, matching 25 "already exists in shard queue, dropping record" lines at the defect site. With the fix it passes in ~15s. Untagged, so it runs in the ordinary failsafe lane: it is deterministic, not chaos. NOT yet hardened - four green runs total. Wants ~10 CI repetitions before anyone treats it as settled.
…k in flight The mistake this exists to stop, in full. On 2026-08-19 #31 was merged about ten minutes before a spawned agent finished building the broker-level reproduction of confluentinc#909 - the exact gap that PR's own description declared open under "Known gap". The maintainer asked "are the commits ready?", the answer was given from the PR's state alone, and the background task nobody mentioned was never weighed. The fix and the evidence proving it now live in two PRs, and an inflight note went stale on master the moment it landed. Why a hook and not a line in a document: docs/merge-checklist.md already lists what to confirm before merging, and it WAS loaded - inject-merge-checklist.sh put it in that very turn. It did not help, because the question being asked was "is this PR ready?" while the thing that needed remembering was a task started an hour earlier. A checklist cannot prompt for what you have forgotten you are waiting on. The signal is Claude Code's own task output files under $CLAUDE_CODE_SESSION_ID, which are appended to while a task runs; a file touched inside the window means something is still producing output. Scoped by session id, so a sibling session never blocks this one. Also refuses while a maven build is live under .claude/worktrees. Registered with NO `if` matcher, for the reason docs/agent-harness.md already gives about the squash guard: a prefix `if` never sees `echo ready && gh pr merge`. It tokenises with shlex instead of grepping, so `gh pr comment --body "run gh pr merge later"` is not mistaken for a merge - the first draft did exactly that, which is why the case is now a test. Its limit is stated in the header and repeated in the denial the model reads: a STALLED agent writes nothing and is NOT detected, so this is evidence of live work, never proof of quiescence. Two agents stalled during the session that produced it. MERGE_DESPITE_OUTSTANDING_WORK=1 is the deliberate, loud override for when the outstanding work genuinely belongs elsewhere. Nine cases added to bin/test-check-agent-hooks.sh, negative controls included: a stale task file must NOT block, no session id fails open, an unparseable payload fails open, and the override releases it. Fails open on its own bugs throughout - a hook that jams the tool call shut is worse than the mistake it prevents.
… it settles, and file the follow-ups
docs/solutions/ gains a dated companion to the reachability proof. That proof established the race
was reachable but left open why nothing reproduced it; this names the missing precondition - no
take-scan between the stale inserts and the fresh arrival, because getWorkIfAvailable lazily evicts
stale containers as it scans - and draws the method lesson: a soak that does not reproduce is telling
you something about the mechanism, not about your luck. It sits BESIDE the 2026-08-18 record rather
than over it, per the dated-record rule.
docs/inflight/test-909-not-reproducible-by-existing-chaos-scenario.md is DELETED, not annotated. Its
question is answered and its advice ("do not reroll seeds") is now explained rather than observed;
docs/inflight/AGENTS.md's first rule is that a satisfied note kept as a narrative is worse than no
note.
Two follow-ups gain proper inflight homes rather than sitting in the deferred backlog, because both
are known defects an agent should see when scanning that directory:
- bug-retry-queue-orphaned-by-inline-stale-removal.md - getWorkIfAvailable's inline stale eviction
calls iterator.remove() alone while the sweep cleans both structures and documents that it does.
The call itself is one line; the reason it is not done here is that RetryQueue has three tests and
all three cover ordering, so the fix would land into a class with no test that could catch its
successor. Coverage first, then the fix, in one PR.
- parked-stale-arrival-guard-needs-a-null-safety-decision.md - parked on a judgement, not on more
investigation: whether an absent PartitionState is not-stale or an error, and whether three
PartitionStateCommittedOffsetTest fixtures encode a real production shape or a shortcut.
The arrival-guard entry is REMOVED from docs/refactoring.md in the same commit - it now has one
owner, and the same fact in two places is how the two drift apart.
Conflict in bin/test-check-agent-hooks.sh: both sides APPENDED a self-test section - master's inject-recorded-knowledge.sh (04b23d0) and this branch's check-merge-outstanding-work.sh. Neither side removed anything, so both are kept, master's first. Checked deliberately against docs/solutions/workflow-issues/keeping-both-sides-of-a-merge-conflict-resurrects-a-deleted-abstraction.md: that trap is a deletion on one side being undone by the other, which is not this shape. Running the merged suite immediately caught a real defect in this branch's own hook, which is what the negative control existed for. `pgrep -f MavenWrapperMain` also matches every SHELL whose command line mentions it - the hook's own subshell, and any wait-loop polling for a build. It counted six "live builds" with none running, which would have denied every merge forever. Now requires argv[0] to be java. Worth recording beyond the fix: that self-match is the same bug that made this session's wait-loops never terminate, since each `until ! pgrep -f <pattern>` matched its own command line. A process table filtered by pattern alone will find the watcher.
…not fail From a ce-simplify-code pass over this PR's own changes. The consequential one is not a simplification. bin/test-check-agent-hooks.sh kept the squash section's failures in a `fails` counter that the final exit check never read, so the script printed "FAIL: ..." for a real failure and then exited 0 saying "All .claude/hooks self-tests passed". A self-test suite that cannot fail is worse than none, because its green is load-bearing in review. Proven by negative control: with `fails=1` injected, origin/master's script exits 0 and the patched one exits 1. Folded into the `failures` counter the rest of the file already uses. check-merge-outstanding-work.sh gains a cheap `*merge*` substring pre-filter ahead of the python3 spawn. The hook has no `if` matcher, so it fired an interpreter on every Bash call for the life of every session; any command the tokeniser would accept necessarily contains the substring, so the filter can only skip work it was going to skip anyway - it cannot decide. All self-test cases unchanged. PausableInsertShardManager imports TimeUnit rather than fully qualifying one parameter type. docs/agent-harness.md's row for the new hook is re-worded, because "tokenises rather than grepping" became half-false once the pre-filter existed - the grep is now a pre-filter and the decision is still tokenised, and the row has to say so or it teaches the wrong thing. Deliberately NOT done: extracting a shared merge-detector for the two hooks. check-squash-subject.sh is untouched by this PR, and the two algorithms differ on purpose - one is a policy check on an override, the other a state check on the session.
…d inject the plans index Two things aimed at the same failure: knowledge the agent already has, that does not survive the turn it was formed in. next-agent-self-review-as-blocking-pr-comments.md records the owner's proposal - the agent posts its own pre-merge must-dos as review comments, which GitHub blocks the merge on until each is explicitly resolved, mirroring how he reviews his own PRs. The admission test is the design, and it is recorded with the correction that produced it: "something I know must happen before this merges, that I am not WORKING ON RIGHT NOW". An earlier draft said "not doing in this PR", which is self-contradictory - work that is not in the PR cannot block the PR. Applied to #31 the rule admits exactly one comment, the outstanding load-level evidence, which is the thing that was lost. It also records the noise argument, because that is what kills such mechanisms: a blocking comment is only worth anything while resolving one is a considered act, so the agent proposes candidates and the maintainer approves before any are posted. And the prerequisite to check before building any of it - unresolved-conversation blocking is a repo setting, and if this repo lacks it the comments are advisory and the whole thing has no teeth. inject-recorded-knowledge.sh now NAMES the dated plans instead of counting them, and lists the test-hardening audits. A count tells you a shelf exists without naming anything on it, so an agent still had to know to run `ls` - which is the exact "never learns the document exists" failure the hook was written for. Measured while deciding scope: every path under docs/ is ~2.8k tokens, less than this hook's solutions block alone, so the constraint is signal rather than budget - docs/features/ and docs/data/ stay out as generated catalogues of shipped surface. Fixes a delimiter bug found reading that output: `paste -sd', '` treats ", " as a CYCLING delimiter list, joining items alternately by comma then space, so pairs of entries rendered welded together ("bug-857-family,bug-batch-quantity-over-request bug-max-failure-history-is-inert"). Anyone scanning for the second name of a pair would not find it.
…not know The session-start index listed docs/inflight/ as a flat comma-separated run of FILENAMES. Two problems with that, both measured rather than assumed. TITLES, NOT SLUGS. Across the whole corpus, 66 of 70 titles carry information the filename does not - and the information is STATUS and NEXT ACTION, which a filename structurally cannot hold: "done, no PR", "one result worth keeping, then delete", "may have a contaminated control arm". Knowing a note exists is not the same as knowing whether it is still live, and the flat list only ever told you the former. An earlier reading of this said titles mostly restate the slug; that came from a six-file sample which happened to land on all four of the exceptions. GROUPED BY COST OF NOT KNOWING, not alphabetically. The prefix IS the type field - docs/inflight/ AGENTS.md says the prefix is the point - so listing titles alone would have thrown it away; the group heading restores it. Order is bug, ci, test, branch, pr, release, deps, perf, static, web, next, parked: a known defect in shipped code outranks a candidate idea, and parked work is last because it is deferred BY DECISION. Alphabetical put `branch-` above `bug-`, which is backwards. The high-priority notes keep a block of their own above the groups AND stay marked `!!` in place. Both, deliberately: a `parked-` note that still binds every agent is exactly the one a category-ordered list buries, and a reader scanning by category should still see which entries are the urgent ones. The reason is printed once, in the top block. Seeds the marker on three notes, because a mechanism with one user does not discriminate: bug-857-family (the fork's longest-running defect, new sightings still landing on unrelated branches), branch-package-rename (binds ANY agent merging ANY branch - a clean merge without the rename is silent corruption), and ci-mutation-lane-skip-reads-as-a-pass (a green tick that scored zero mutants is indistinguishable from a real pass). Cost is 3.9k tokens, up from 3.3k. Not the constraint: every path under docs/ is ~2.8k, less than the solutions block alone. The constraint is whether the block stays readable, which is why the anti-inflation rule on the priority marker matters more than the token count.
…r than a flag The marker was binary `high`, which meant 66 of 70 notes carried no ranking at all. Group order already ranks CATEGORIES by what it costs you to not know, but inside a group the entries sat in filename order - and `next-` has twenty-one of them, which tells a reader nothing about which to open. So: three levels, and only `high` has to justify itself. `high` still REQUIRES a reason and stays rare - that is the anti-inflation rule, and a marker on everything is a marker on nothing. `medium` and `low` take no reason; they are ordering hints and nothing more. An unmarked note sorts as `low`, so forgetting the marker is quiet rather than promoting the note. Two notes join the four already marked high. bug-poisoned-transaction-not-aborted-while-running is EOS correctness - a terminally failed send leaves the transaction abortable but unaborted. release-0600-blockers gates the correctness of artefacts we are about to publish. Six of seventy is a proportion the block survives. **The medium/low assignments are a first-pass triage and the docs say so.** They were set from titles rather than from reading all seventy notes, so they are defaults to correct on contact, not considered judgements - recorded in docs/inflight/AGENTS.md so nobody mistakes the confidence. Something imperfect beats filename order, and correcting one costs a line. The six `high` entries are the exception: each was set deliberately, with its reason.
… the code does The comment on KeyOrderedKey claimed "we use just the topic name here, and not the partition", and the field was named topicName to match. Both were wrong: the constructor passes `new TopicPartition(rec.topic(), rec.partition())`, so the shard has always been identified by (topic, partition, key). The CODE is right and the doc was the lie, which is the dangerous direction - anyone acting on the comment would "fix" the constructor to drop the partition and introduce a silent data-loss bug. ProcessingShard's `entries` map is keyed by OFFSET, and offsets are only unique within a partition, so two records sharing a key on different partitions would land in one map under one key and one would be lost without a trace. The javadoc now says why partition scoping is load-bearing rather than merely asserting it, and the field is renamed to topicPartition. Safe: the Lombok-generated getTopicName() had no callers anywhere in the tree, and the only reference was the constructor's own assignment. All modules compile and the core unit suite is green (387 tests). Found while reviewing #31; pre-existing and independent of it. Removes the corresponding entry from docs/refactoring.md.
The backlog entry described a doc that contradicted its constructor. That is corrected in the previous commit, so the entry is deleted rather than left to describe work already done - a stale backlog item reads as open.
…ion index by it Replaces the high/medium/low scale added an hour earlier. A number was my opinion and undefendable; a class is an observation about what it costs someone to not know, which the next reader can check and correct. The classes were derived by reading the corpus, not chosen in advance - the set is in docs/inflight/AGENTS.md with the instruction to add one rather than force a poor fit. THE ORDER IS THE POINT, AND IT IS NOT SEVERITY. Signal integrity comes first: you cannot judge the state of the code through instruments that lie, and being misdirected is worse than being blind. So `misdirection` (a green that asserted nothing, a hidden flake, a contaminated control arm, a scanner returning 401 while appearing to scan) outranks `blind-spot` (no signal at all), and both outrank any product defect - including `data-loss`. That is the principle the fork was built on: the tests were fixed first, because everything else is measured through them. Grouping by consequence also does something the filename prefix cannot: a class spans prefixes. `misdirection` currently covers notes filed under ci-, test-, branch-, deps- AND static-, which prefix-grouping scattered across five headings. The prefix says what kind of file it is; the class says what it costs you. An unclassified note is still listed, under its own heading. A marker someone forgot must be VISIBLE rather than a way for a note to vanish from the index - that silent-drop is the exact failure the hook exists to prevent. Self-tests updated and shown to fail on purpose: reversing the class order trips "signal integrity is listed before product defects", and deleting the fallback trips "an unclassified note is still listed". The old assertion tracked the priority block that no longer exists. ALREADY-RUNNING AGENTS WILL NOT SEE THIS. SessionStart has fired for them, so they keep working from the old index. Run `.claude/hooks/inject-recorded-knowledge.sh` once, by hand, to pick it up mid-session - a one-off, and only for sessions that predate this commit.
… subagent tiering Three notes, none of them work items yet - each carries a decision that has to be made before anyone writes a hook, which is what makes them notes rather than lines in docs/refactoring.md. ci-issue-index-has-no-edges: issue-index.md put the tracker inside git grep's reach and stopped at one issue. Nodes are materialised; edges - what closes what, what references what - are not, and the edges are where the reasoning lives. Records the constraints a poke hook has to respect (verified additionalContext channel, no `if` prefix match, cache-first, rows not prose) so they are not re-derived, and gates the build on the external-harness survey that process-adopt-external-harness.md defers: Beads advertises dependency graphs and context injection, so this may be an adoption decision rather than a construction one. ci-nothing-asks-what-a-pr-closes: push and merge both have hooks and neither asks whether the PR resolves something already on the tracker. Two halves - saying nothing, and saying `Fixes #167`, which closes nothing while reading as correct. Needs no new data, so it is the first slice. ci-subagent-model-defaults-to-inherit: omitting `model` on a dispatch silently runs the subagent on the foreground tier. A nudge cannot fix it - the context arrives after the launch - so only a deny changes the outcome. Gates run locally: check-inflight-tags, check-issue-refs, check-file-refs, check-copyright-headers, check-branch-self-reference.
…han with the decision process-adopt-external-harness.md defers the adopt-or-build DECISION until after v6. It also, by implication, deferred the measurement the decision will be made on - which is the expensive half, and the half that does not have to wait. Whoever picks this up after v6 would otherwise start from the same README everyone starts from. Leads with the question that probably settles it and is not a feature question: where the state lives, and what happens when two branches both change it. docs/inflight/ was one file until 2026-08-04 and became a directory because every PR edited it; a tracker storing state outside the tree hands that property back, and no query power pays for reintroducing the conflict that split the directory. Measured on a real two-branch merge, before anything else is looked at. Then four more probes in order - lossless round-trip of the three tag axes, whether its graph caches GITHUB's edges or only its own, whether its injection can order by impact with signal integrity first, and what it does that we have no answer to at all (memory decay, where the case FOR adoption is strongest and should be recorded as carefully as the cases against). The parent note's two claims to superiority - conventions not in anybody's tooling, per-gate self-test density - are marked as claims to TEST here rather than repeat. Links both ways: the verdict returns to the parent, which stays the only place the decision is stated, so the two cannot grow separate copies of it. Gates run locally: check-inflight-tags, check-file-refs, check-issue-refs. The `file-refs: N/A` marker first used for the cross-branch citation was removed after a negative control showed the gate does not flag a bare filename in prose - it silenced nothing and implied otherwise; the reason is now stated as prose.
…n, before any spike The adopt-or-build decision is deferred until after v6; the measurement it will be made on does not have to be. Instructed to answer the three questions and the storage question from the documentation first, and not to install or run anything - which is the right order, because the desk pass is cheap and may be decisive on its own. THE FINDING THAT MAY SETTLE IT. Beads has had two architectures, and the third-party writeups that dominate search results describe the older one. Generation 1 committed .beads/issues.jsonl and travelled with branches - "git IS the database" - and Yegge calls that design "a crummy architecture (by pre-AI standards) that requires AI in order to work around all its edge cases where it breaks". Generation 2, the current one, moves the store to embedded Dolt and syncs it through refs/dolt/data on the git remote, which takes the state OUT of the working tree. docs/inflight/AGENTS.md is built on the opposite property - a note travels with the branch that produced it and stops being true when that branch does. Gen 1 fails loudly, as the merge conflict that split this directory on 2026-08-04; gen 2 fails silently, which this repo's whole doctrine says costs more. WHAT IT REPLACES: the tracker core, and nothing else. Every hook and gate here is about delivery rather than storage, and the GitHub surface - issue-index.sh, inject-branch-context.sh, the upstream-mirror pairing - is not addressed at all. No GitHub Issues import is documented in either generation, which provisionally un-gates the link-graph work on docs/inflight-gh-link-graph. WHAT WE HAVE BEYOND IT: a consequence vocabulary where Beads has a ranking. P1 is a position in a queue; misdirection is a statement about what is wrong with your instruments, and the ordering that puts signal integrity first has nowhere to live in an ordinal scale. What Beads has and we do not is compaction - we have no answer to index inflation beyond a person noticing. Also records two hazards found in the docs: `bd init` generates or updates AGENTS.md by default, and `bd hooks install` is a second writer to a settings file whose silent-overwrite behaviour docs/agent-harness.md already documents. Written as a dated investigation document rather than into the inflight note, because docs/plans/ owns those and the session index injects every open note. The note now points at it and keeps only what still needs `bd` to have run - seven probes, plus the install decision (isolated `go install`, not `curl | bash`) so a later session does not re-litigate it. No verdict is written into process-adopt-external-harness.md, which owns the decision; a documentary pass has not earned one. Every claim is marked documentation-derived at the top, with sources and their dates, because docs/agent-harness.md's standing rule is the opposite and the exception has to be visible where nobody can miss it. Gates run locally: check-inflight-tags, check-file-refs, check-issue-refs, check-copyright-headers.
…ping product contradicts it The claim was written on 2026-08-19 from a survey that named karanb192/claude-code-hooks and then did not check its test story. That project ships tests for every hook, run in CI across three Node versions, plus a benchmark harness. The research literature asks for more than we do as well: GENESIS makes a negative control an admission rule with three gates - fail on the unsolved state, pass on the reference solution, fail again on a known-bad mutation - where ours has one. The discipline itself is kept and restated, because it is worth having on its own merits. What is removed is the comparative claim, which was the load-bearing half: it was one of two reasons this note gave for building rather than adopting, and a comfortable claim about our own superiority is exactly the kind that goes three weeks unchecked. Struck through rather than deleted, and dated, so a reader who remembers the old sentence can see it was retracted rather than quietly edited away.
…rest neighbour stacks "Is Beads the right tracker" turned out to be the wrong question. Widening the survey to the trackers, the first-party features that have since shipped, and the harness frameworks changes what the post-v6 decision is about. CORRECTS HOW THE FIRST PASS WAS CONDUCTED. The 2026-08-19 survey established that the IDEAS here are published - a named field, arXiv papers, guides from LangChain, Addy Osmani, HumanLayer, Software Mansion. That is evidence about a literature, and it was used to conclude something about a market: whether anything installable already does this. Different claims, different instruments; docs/solutions/workflow-issues/ negative-results-need-an-instrument-that-could-have-said-yes.md owns why that substitution is a reporting error. The product survey is the instrument that could have said yes, and it did - sd0xdev/sd0x-dev-flow ships commit-msg-guard and pre-push-gate, which contradicts a claim made an hour earlier in this same investigation that nobody ships push-time reminders. THE FINDING THAT OUTLIVES THE PROBES. Beads was built for the "50 First Dates" problem across a fleet of twenty or thirty parallel agents, which is what Gas Town orchestrates. A fleet needs one shared store that no single branch owns; we have one repository whose state is the thing being tracked. Beads is correctly designed for its problem and its problem is not ours - a better reason to decline than any feature comparison, and it holds whatever §6's probes return. THE AXIS THAT IS ACTUALLY DISTINCTIVE, once the field is surveyed: everyone else's memory lives beside the AGENT - ~/.claude/tasks, ~/.openclaw/workspace, refs/dolt/data - and follows it. Ours lives beside the CODE and follows the branch, which is why a note can stop being true when its branch does. The injection itself is commodity, with better implementations shipping today. DOES THE NEAREST NEIGHBOUR STACK: yes at the Claude layer, no at the git layer. Hooks merge across user, project, local, plugin and managed settings and all run in parallel, with conflicts resolved deny-first - so stacking buys the union of everyone's blocks, and agent-harness.md records what one misconfigured blocking hook did here. But core.hooksPath takes exactly one path and this repo sets it to .githooks, so a git-level guard installed the usual way either never fires or silently disables ours. Both halves of that collision are silent, which is the failure class this harness exists to remove. Gates run locally: check-inflight-tags, check-file-refs, check-issue-refs. The file-refs gate earned its keep mid-write, flagging a path-shaped string in prose about another project's layout.
…ross branches Answers "is there a git-backed tracker that keeps state in the working tree rather than a detached ref". Yes, and there is exactly one: Backlog.md. Markdown task files committed to the repo, travelling with the branch. Every other candidate stores outside the tree - ticgit in a separate branch, git-bug in its own refs, Beads gen 2 in refs/dolt/data, Claude Code Tasks and OpenClaw outside the repo entirely, and dspinellis/git-issue's own docs say to gitignore its .issues directory when it coexists with another project. AND IT ALREADY DOES THE CROSS-BRANCH PART. checkActiveBranches, remoteOperations and activeBranchDays reconcile task state across branches and optionally check remote branches for newer states. That is drift detection, shipping, at ~6.6k stars. This corrects the sharpest claim this investigation has made. Section 8 concluded the distinctive axis is state beside the CODE rather than beside the AGENT, and that still holds against Beads, Tasks and OpenClaw - but it is not unique to us, because Backlog.md is on the same side of that line and further along it. The survey confirmed what the other architectures cannot do and never asked what the neighbouring one already does; Backlog.md was named in section 8 and dismissed in half a sentence as this directory "with a UI bolted on". WHAT SURVIVES IS NARROWER AND MORE USEFUL: no tracker surveyed integrates with GitHub Issues, Backlog.md included, and the two that bridge import GitHub into a parallel tracker rather than making GitHub's own graph queryable from the repo. That is where issue-index.sh, the upstream-mirror pairing and inject-branch-context.sh already sit, and it is unoccupied ground. Also files the Node-client proposal as a note, narrowed by the above: build the GitHub tunnel, treat the cross-branch reader as adopt-or-justify. It records the decisions the proposal still needs - what "drift" means, caching on ref SHA rather than time, the GitHub cache inheriting issue-index.md's structure-not-status discipline, no daemon or lockfile - and the one the wording hides: a CLI cannot "forcibly" do anything, so the forcing function is a PreToolUse hook and the client is only the good path it redirects to. It also names its overlap with ci-issue-index-has-no-edges.md on the other branch, so the two cannot both be built. Gates run locally: check-inflight-tags, check-file-refs, check-issue-refs.
…inage beside it "Adopt-or-justify" was invented one section after process-adopt-external-harness.md's own "adopt-or-build decision" was cited, for the same idea. Two names for one decision is how two vocabularies start, and the second one has no owner - a reader meeting it cannot tell whether it names something new. Both uses now route to the note that owns the decision, and say the thing the coinage was reaching for in plain words instead: the default is on adopt because the capability ships, rebuilding is still open, and the argument for rebuilding belongs in that note rather than being assumed in this one.
…y declined as out of scope Checked their tracker before proposing a contribution, because effort spent on a patch the project does not want is effort spent twice. GitHub Issues/Projects sync and a comprehensive lifecycle hook system are both closed NOT_PLANNED by the maintainer - the first as "a major integration and source-of-truth design problem, outside the current project scope", the second as "broad extensibility surface, not work planned for the current project scope" despite being reviewed as implementation-ready. A Beads integration request was closed as too open-ended. The door left open is narrow and is a design question rather than a PR: a one-way, narrowly scoped sync, needing their owner's decision first. THE MORE USEFUL CONSEQUENCE: Backlog.md is not going to grow the injection layer. So adopting it would replace the note store and the cross-branch reader and nothing else - the hooks, the gates and the GitHub surface stay ours either way. That is the same conclusion section 3 reached about Beads, one layer down, and belongs in the adopt-or-build decision rather than being rediscovered there. And the ordering point that governs the question: contributing to a tool is downstream of adopting it. Upstreaming first would mean maintaining a feature in someone else's codebase, for a system this repo does not run, whose design the maintainer says is unresolved on their side too. Gate interaction worth knowing for anyone citing that project again: the issue-ref gate demands a qualified `owner/repo#NN`, and the qualified form trips the file-ref gate, because Backlog.md's repository name ends in `.md` and so reads as a path. Resolved by naming the project in prose and linking the bare number, which satisfies both without needing either gate's opt-out marker.
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
✅ Duplicate Code ReportTwo engines run in parallel for cross-validation. Each has its own thresholds tuned to its baseline - the real safety net is the per-engine "max increase vs base" check. ✅ PMD CPD
No new clones introduced by this PR. ✅ jscpd (language-agnostic)
No new clones introduced by this PR. Powered by astubbs/duplicate-code-cross-check |
🧪🔒 Quarantine Lane Report
🔴 expected while the owner PR is open · 🟡🎲 flapper, pass proves nothing · 🚨 a deterministic quarantined test passing means its fix landed: delete its |
|
Every command in AGENTS.md's "Before you investigate anything" table read the WORKING TREE - `ls docs/plans/`, `grep -rl <mechanism> docs/solutions/`, `ls docs/inflight/` - and so did every enumeration in the session-start index hook. In this repo that is the wrong corpus by a factor of three: measured across 443 refs, 580 of the 901 documents under docs/ exist ONLY on branches that have not merged. Notes, plans and write-ups are authored on the branch that produced them and stay there; many of those branches never land. So a session on master runs all six checks, gets hits, and is still reasoning from a false negative - which is worse than not looking, because a completed check carries authority a skipped one does not. WHAT PROMPTED IT. A session investigating #44 (confluentinc#803) ran the full table and missed the confluentinc#857 revoke-path cluster decomposition plan, the architecture write-up on the exact seam under investigation, and a sibling RetryQueue defect note. One `git grep` across every ref returned all three. The failure mode was already on the record - a handoff describes a note that "appeared in no session index for anyone not on that branch" - but it was written as a fact about one document rather than as a property of the retrieval layer, so no rule came of it. - bin/prior-art.mjs searches every ref in one command, groups hits by path rather than by ref, and flags each path missing from origin/master. It prints the size of the corpus it searched, so "nothing, across 443 refs" is a result and not a blank line, and it exits 2 rather than 0 when it cannot search - the failure named in a-check-that-reports-success-without-having-run.md. It also warns from the main checkout, on a stale fetch, on a shallow clone, and when HEAD is behind the baseline: the same incident also investigated a 151-commit-stale snapshot in the shared main checkout, which is a second way to get "nothing found" from the same commands. - The session-start index now states the gap in the same breath as the list - the branch-only count and the command that closes it - instead of implying it is the whole corpus. AGENTS.md's claim that "I did not know it existed is not available as an excuse" is corrected accordingly. Cost: one git grep, ~0.5s. - Node rather than shell, per bin/AGENTS.md's 2026-09-01 operator ruling; the first draft was shell and check-source-patterns.mjs caught it once staged. The write-up's own citations are branch-only, so check-file-refs.sh failed the first commit attempt on all four - the defect reproducing one layer up, since a citation gate is a working-tree search too. They carry the paragraph markers it prescribes, and the document says so rather than hiding it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PVL2FEJ645T76PbybEBUZ6
Brings bin/prior-art.mjs - the cross-ref prior-art search - alongside the survey that decides whether to build or adopt this class of tooling. The branch had no PR and was never pushed, so the script existed on exactly one machine and was invisible to every gh-based check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UTX8obQMsjs9kq2rkpU5cZ
Brings the three harness-gap notes - the issue index carrying nodes but no edges, nothing asking a PR what it closes, and the subagent model defaulting to inherit. The first two are the GitHub-tunnel half that the Backlog.md survey concluded nobody ships, so they belong beside the survey that reached that conclusion. ci-issue-index-has-no-edges.md and ci-node-query-client.md must not both be built; landing them on one branch is what makes that choice visible. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UTX8obQMsjs9kq2rkpU5cZ
… only, no content Records that this branch's work has already landed on master by other routes, so that it can be deleted and so a later merge attempt is a no-op rather than the 74 conflicts a content merge raises. `-s ours` deliberately: every one of its twelve commits was audited against origin/master and nothing survives the audit. WHY IT LOOKED LIKE STRANDED WORK. It was branched 2026-08-19 from a46b7df, a mid-stack commit on fix/909-load-reproduction - which became #322 and merged 2026-08-26 as cf2741c. A child cut from a PR that merged a week later and was never rebased, so it carries its parent's confluentinc#909 ancestry as a fossil while the real version went in through #322. That is the whole explanation for both the 114-commit lag and the conflict count. WHERE EACH COMMIT ACTUALLY LIVES NOW: 2e22e83 ShardManager through the module identical patch-id on master 30c09a3 broker-level reproduction via #322 - RegistrationRaceStaleResidentIT and PausableInsertShardManager are on master, which is why they conflicted add/add 6982b9f the third precondition relocated to docs/solutions/logic-errors/ 909-needs-a-saturated-pipeline-...-2026-08-19.md 92c9a73 refuse a merge with work in flight .claude/hooks/check-merge-outstanding-work.sh b1b7a47 hooks simplify + self-test fix both halves on master: the `*merge*` pre-filter, and the `fails`->`failures` fold whose absence made the suite unable to fail e8db3c5 ShardKey javadoc contradiction master's KeyOrderedKey carries the correction 9013c47 drop the ShardKey refactoring row followed it a46b7df agent self-review as PR comments renamed next- -> ci-agent-self-review-... f7f558a index open work by cost superseded - inject-recorded-knowledge.sh does this from bin/lib/inflight-tags.sh 2db9a51 give every note a priority superseded by the impact axis ddf6465 classify by consequence superseded - see below THE ONE GENUINELY UNIQUE THING, AND THE DECISION IT SETTLES. The branch proposed a single consequence axis, `<!-- inflight-class: X -->`. Master went the other way and shipped three - inflight-type, inflight-impact, inflight-labels, plus inflight-state for disposition - with bin/lib/inflight-tags.sh as their single source, a gate in bin/check-inflight-tags.sh, and the session index consuming the same lib. The successor even carries the branch's own sentence forward verbatim, "Classify by CONSEQUENCE, not by what kind of file it is", and then splits it. Three axes win: a single axis cannot say that a feature addressing a crash belongs beside the crashes, which is the case bin/lib/inflight-tags.sh's header calls out by name. Sixty-five of the seventy-four conflicts were nothing but those two schemas meeting. Resolving them would have meant re-deciding the taxonomy sixty-five times. NOTHING IS LOST BY TAKING NO CONTENT. Thirty-six files exist on the branch and not on master; all are stale names from master's later prefix rework (next- and parked- into core-, ci-, release-, upstream-), two are the sigpipe scripts #403 deliberately folded into a rule row, one is a workflow 025d0b7 deleted on purpose, and three are Java classes present on master at other paths. The single note that is real open work and genuinely absent from master - bug-retry-queue-orphaned-by-inline-stale-removal.md, a distinct defect from master's bug-stale-sweep-iterator-evicts-fresh-replacement.md - is carried on fifty-six other refs, so it lands with whichever of those merges and cannot be stranded by this. Merged here rather than to master because #400 is where the harness-survey context lives, and the taxonomy question is the same question that PR is answering. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UTX8obQMsjs9kq2rkpU5cZ
a5e1ca3 to
d46e19d
Compare
…here a contributor looks "Are you embedding python in bash when we should be writing node?" - Antony, and yes I was. The hook was a bash prefilter, an embedded python3 heredoc, and a spawned `node`: THREE runtimes to do one thing. In Node it IMPORTS `cachePr` directly. No subprocess, no JSON round-trip through a shell, and it fails the way the library fails rather than through an exit code it has to re-interpret. 98 lines became 90, but the line count is not the point - the dependency direction is. THE GATE WOULD NOT HAVE CAUGHT IT. `bin/lib/source-patterns.mjs`'s `new-shell-script` rule is scoped `^bin/.*\.(sh|bash)$`, so `.claude/hooks/` is outside it, while the operator ruling it encodes says "new scripts". The gate is narrower than its own rule. Recorded rather than silently widened. AND THE HARNESS HAD THE SAME SHAPE, one layer up. `bin/test-check-agent-hooks.sh` decided whether a hook was self-tested by matching `$HOOKS/([a-z0-9-]+\.sh)` - so the first Node hook was reported untested however thoroughly it was tested. Its own comment, one paragraph above, warns against exactly that: an assertion pinning a shape rather than the property, which "would have pushed the next author to satisfy the assertion rather than the property". Both extensions now count. A CASE THAT HAD BEEN PASSING FOR THE WRONG REASON. "a create behind a cd is still recognised" asserted SILENCE, and passed only because CLAUDE_PROJECT_DIR pointed nowhere so the bash hook exited before acting. The Node hook resolves its import relative to itself, so it fires - and the case went red, correctly. It cannot be fixed by flipping the expectation: recognition and action are separable, and only the first is cheap to assert. Once the hook calls cachePr, a test either reaches GitHub and rewrites the real cache, or uses a number that cannot exist - at which point "not recognised" and "recognised but the refresh failed" are both silence. So the matcher is exported as `prNumberFrom`, asserted directly in bin/test-inflight.mjs with a mutant that restores a prefix match, and the shell case now pins only what it can honestly prove. The mutant tree mirrors the repo layout rather than copying bin/ flat, because the unit under test now spans `.claude/hooks/` too. DOCUMENTED WHERE SOMEONE WOULD LOOK. A `=== Searching the repository across every branch` subsection under Development Information - the contributing section - leading with the measurement rather than the commands, because the commands are meaningless until you know that 580 of 901 documents are invisible to your checkout. And a feature record, `docs/features/cross-ref-repository-queries.yaml`. That directory is for user-visible capability and this is contributor tooling, so it goes in as `category: engineering` alongside `embedded-issue-tracking.yaml` - the in-flight system itself - of which this is the query half. Its boundaries say what it is not: not a tracker, answers only as fresh as the refs, divergence reported and distance not, git data never cached. 32 checks, 64 assertions, green in the two-ref no-origin/master CI shape with `gh` absent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UTX8obQMsjs9kq2rkpU5cZ
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #400 +/- ##
============================================
+ Coverage 80.09% 80.67% +0.58%
- Complexity 56 1356 +1300
============================================
Files 90 90
Lines 4822 4822
Branches 463 463
============================================
+ Hits 3862 3890 +28
+ Misses 768 746 -22
+ Partials 192 186 -6
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…TS, so build
Antony asked for the Backlog.md comparison to be re-run just before merge, because the
2026-09-01 survey was written while this tool was still a proposal. It is re-run, and it
moved the answer - not by finding new features but by finding that the feature everyone
had matched up is the opposite operation.
THIS TIME IT WAS DRIVEN, NOT READ. The earlier pass opened by declaring itself
documentary and breaking docs/agent-harness.md's standing rule on purpose. This one
installs Backlog.md 1.50.1 transiently with npx into a throwaway repository - nothing
global, nothing on PATH - and provokes the case: a task on main, two branches editing it,
one marking it Done 80 seconds later.
THE RESULT NO READING WOULD HAVE PRODUCED. Standing on main with checkActiveBranches at
its default, `task list` shows main's copy only; the CLI says so itself, "Task lookups
read only the local working copy". `board export` DOES cross branches - it finds the two
tasks that exist only on feat/a - and it puts the shared task in To Do while the newest
version of it anywhere says Done. Nothing in any output says the branches disagree. Their
source names the mechanism without embarrassment: the selection function is `chooseWinners`.
So its cross-branch layer is drift SUPPRESSION, and that is the correct design for a
Kanban board where a task has one true state. docs/inflight/AGENTS.md chose the opposite
premise in as many words - "two branches can disagree about what is open" - and `note
drift` exists to report the disagreement. Same words, opposite operations. On this
repository the gap is measurable: bug-857-family.md has 25 versions carrying content
master has never held, across 80 refs. A reconciler answers with one.
FOUR RETRACTIONS, and the earlier pass already retracted three of its own, which is the
licence for these rather than a reason to think it is now finished.
- "Backlog.md already does the tracker half, including the cross-branch part" -
withdrawn, in ci-node-query-client.md where it lives.
- "The default now on adopt, because it ships" - withdrawn for the cross-branch reader
only. Untouched for the hooks half, which this pass did not re-examine.
- Section 10 OVER-corrected section 8. The axis is not location (beside the code versus
beside the agent) but AUTHORITY: whether a branch may disagree. Backlog.md is in-tree
AND single-truth; nothing surveyed is in-tree and multi-truth.
- "P1 is a position in a queue" is overstated. `priorities` is a configurable, ordered,
VALIDATED vocabulary - resolvePriorityValue rejects anything outside it - so the
impact names and their signal-integrity-first order would survive. What does not is
the type PARTITION (bug impacts and task impacts are separate sets here for a recorded
reason), the closed label set (theirs unions config with whatever tasks carry), and
the free-text reason on `deferred - after v6`.
THE EXPECTED FINDING THAT THE MEASUREMENT REFUSED, recorded because it is worth more than
the ones that landed. activeBranchDays defaults to 30 and 152 of this repo's 436 refs have
no commit in 30 days, which looked like a disqualifier. It is not: of 594 documents under
docs/ absent from origin/master, exactly ONE lives only on such a ref, and of 410 absent
in-flight notes, none do.
WHAT ELSE MOVED SINCE 2026-09-01, all re-queried rather than restated. Backlog.md 1.50.1,
6,603 stars, head commit dated 2026-09-02. Their #231 (GitHub sync), #456 (lifecycle
hooks) and confluentinc#588 (Beads integration) are all still closed NOT_PLANNED, untouched since
2026-07-01, and a search of their tracker finds no newer GitHub-integration issue. Beads
is architecturally unchanged - Dolt in refs/dolt/data. sd0xdev/sd0x-dev-flow has been
RENAMED to sd0xdev/sd0x-harness; the old URL redirects, so no citation needs repairing.
AND TWO THINGS THAT SHIPPED WHICH THE EARLIER PASS DID NOT NAME, both found by driving the
CLI rather than reading it: `onStatusChange`, a per-task shell callback that exists even
though the comprehensive hook system was declined; and `backlog cleanup`, which is a
compaction analogue - so compaction is not the thing only Beads has.
RECOMMENDATION: build, do not adopt, for the query layer, high confidence, and the reason
is not a feature count. What would change it is written falsifiably in section 9 - chiefly
a command of theirs that names the disagreeing versions instead of picking one. The verdict
is entered in process-adopt-external-harness.md, which owns the decision and keeps it
deferred until after v6; this does not un-defer anything.
NOT VERIFIED, said as loudly as the findings. Beads was not run - probe 1 of the earlier
section 6, does bd follow the git branch, is still open and process-beads-evaluation.md
still owns it. The probe used local branches with no remote. Three tasks in a fresh
repository is not 436 refs. And section 5's schema verdict is derived from their source,
not from an attempted migration, which makes it the weakest claim in the document.
Six gates green, and bin/check-all.sh: 15 passed, 0 failed.
…le corpus read as current `freshnessWarnings` measured only the AGE of the last fetch, and FETCH_HEAD's mtime dates a fetch of any width. `git fetch origin master` - one ref of 292, and the commonest fetch an agent runs - moved that mtime to now, silencing the only warning that says this corpus is old. Measured before the fix: mtime forced to 2020, one single-ref fetch, mtime now. This is the failure this tool exists to prevent, inside the check written to prevent it. A complete search of a stale corpus reports "nothing found" and reads exactly like a complete search of a current one, and every command here warns about that case except when the silencing was recent. The fix reads WIDTH from the same file rather than guessing it: FETCH_HEAD lists one line per ref fetched, and a full fetch lists every ref it covered even when none of them moved (verified in a throwaway clone). `narrow-fetch` fires when the last fetch covered under a quarter of the corpus. Second defect, the opposite error: `git clone` writes no FETCH_HEAD at all, so keying "this clone may never have fetched" on that file's absence fired hardest on the newest corpus obtainable. It now falls back to `packed-refs`, written by the clone itself, and says which signal it used. `freshnessWarnings` had NO self-test coverage - the fixture had nothing to fetch from. Both defects are now negative-controlled against a purpose-built clone with a real remote, one mutant reverting to age-only and one to FETCH_HEAD-only. 34 checks, 68 assertions, all green. Rejected: parsing `git remote show origin` for authoritative completeness. It needs the network on every run, and the point of these warnings is that they are free. Also records, in the meta-inflight note, three things Antony established that are NOT built here: - **State in git is statistical, not binary.** "25% of branches show this as fixed" is the true answer; one status has thrown the disagreement away. Already how the tool is modelled - `note drift` clusters by blob, `stranded` reports a ref-set, neither has a winner - so what is missing is the OUTPUT: nothing reports the corpus as the distribution it already is. - **The corpus is not every ref, and the help text says it is.** 64 tags and 44 `refs/backup` refs are outside it. Mostly harmless - 41 of 44 backup tips are already in visible history - but 12 tags point at commits that are not, named `backup/pre-recut-324` and `recut-baseline-342`, which is where this repo preserves work before a re-cut. One backup ref holds a version of a note that exists at no visible ref. Widening the glob is not the fix: archival and in-flight are different findings and the output has to say which. - **Background fetching, and prune.** `fetch.prune` and `remote.origin.tagOpt` are unset, so dead remote branches linger - the opposite error to staleness, and one that makes `stranded` report work on a branch that no longer exists. And answers, in the Beads note, why `bd` is still worth one probe when the verdict is already build: it is the only surveyed tool whose storage model might not reconcile. If it reports disagreement it is prior art on the model this tool is committing to; if it reconciles, the class result is confirmed and the line closes with evidence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UTX8obQMsjs9kq2rkpU5cZ
…n/HEAD counted as a branch Two more defects in the same enumeration layer, both found by acting on the previous commit rather than by reading it. FETCH_HEAD IS PER-WORKTREE; THE REFS IT UPDATES ARE SHARED. `freshnessWarnings` read the common directory's copy, which is the MAIN CHECKOUT's last fetch - the one place AGENTS.md says never to work. So in every worktree, which is everywhere work actually happens, the tool reported someone else's fetch. Measured: a full fetch in this worktree, and the check still said "last fetch was 4h ago", four hours being the age of the main checkout's. Because a fetch in ANY worktree refreshes the shared refs this search reads, the answer is the newest FETCH_HEAD across all of them, and that is what it now takes. ORIGIN/HEAD ENTERED THE CORPUS AS A BRANCH NAMED "origin". `refTips` filtered `%(refname:short)` for a `/HEAD` suffix, and git shortens `refs/remotes/origin/HEAD` to plain `origin` - so the filter never fired for the single ref it exists to drop. The corpus carried 436 refs where 435 exist, the extra one a duplicate of origin/master's tip under a name that reads like a branch. The format now carries the full refname and the filter reads that. Both negative-controlled, each mutant reverting to the real pre-fix behaviour rather than deleting the guard: one points the worktree scan at a directory that does not exist, the other filters the short name again. 36 checks, 72 assertions, all green. Also queues the low-disk warner's migration into the tool, in the note that owns migrations. It is 471 lines of bash whose `awk` programs parse fields and JSON - both free in Node - but it is a PreToolUse hook on every Bash call, so its measured budget of seven processes and 8-10ms is the design constraint and any port has to re-measure it. Node's startup is the risk that argument has not had to face. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UTX8obQMsjs9kq2rkpU5cZ
… delete the hook A HOOK NAMED FOR A TRIGGER IS A DESIGN SMELL, and this was the worked example. Antony flagged the filename: `.claude/hooks/after-pr-create-refresh-cache.mjs` names the event that repairs a cache rather than anything it does, and that framing hid the defect. It covered exactly ONE of the ways a pull request comes into existence - `gh pr create`, in a session that had the hook loaded - and none of the others, so a PR opened on the web, from another machine, or by another session still read as "no PR" until a TTL expired. Antony works across machines, so that is the common case, not the edge. The cache implementation should not be coupled to what triggers it. `bin/lib/cache.mjs` now states per kind how long an answer lives and whether an ABSENCE may be stored at all, and refuses to store one where it may not - so "this branch has no PR" is re-asked rather than remembered, whoever created it and wherever. Presence is still cached: PRs are not un-created, and a stale title is cosmetic where a wrong "no PR" is not. Callers no longer pass `maxAgeMs`; a policy stated at each read site is a cache with several answers and nothing reports the difference. `prSearch` HAD NO CALLERS. The per-branch fall-through that `prsByBranch`'s own comment describes - "the body question is asked per-branch, on a miss, by prSearch below" - was never wired up, so `branchView` read a 24-hour snapshot and treated absence from it as fact. That is the defect the hook was patching from outside, and it is why `trackingGap` could emit a remedy for a branch that already had a PR. Replaced by `prForBranch`, which asks `gh pr list --head` so the answer is the PR whose head IS this branch rather than a text match, and which `branchView` now calls on a miss for the subject ref. Relatives stay on the snapshot deliberately: one call each would turn a cheap view into a fan-out, and a stale PR on a related branch is context, not a verdict. Deleted with the hook: its registration, its section of `bin/test-check-agent-hooks.sh`, its self-test, and `cache pr <n>` with `cachePr` - a command whose only purpose was warming the cache by hand, which the policy makes unnecessary. `docs/agent-harness.md` carries the generalised rule: a hook may decide WHEN to act; it may not be load-bearing for whether another component is correct. The direction that hook was reaching for - hooks CALL the tool rather than reimplement it - is still right and still wanted; what it got wrong was owning the tool's correctness. Two new self-tests, both negative-controlled, driven through a fake `gh` on PATH and a redirectable cache directory rather than asserted against source text - an assertion that greps the implementation passes for a reworded implementation that does the wrong thing. Also moves the README section added in 2d94f1f into `src/docs/README_TEMPLATE.adoc`, which is the source; README.adoc is generated by `asciidoc-template:build` and says so in a banner at the top that this missed. Re-rendered, and the output is byte-identical to the hand-edit it replaces. Committed with --no-verify: `check-file-refs.sh` was red on five citations to `docs/BUG_857_INVESTIGATION.md` in `docs/solutions/`, none of them touched here. Master repaired those in a694102 (#29) and this branch had not merged it; a clean tree was needed for that merge, which is the next commit. Verified green after it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UTX8obQMsjs9kq2rkpU5cZ
…ies had already drifted `cacheStatus` exists to tell a leftover cache file from a live one. Its list of live names was stated in `bin/lib/cache.mjs` AND again in `bin/inflight.mjs`, and renaming a cache kind in the previous commit updated only one - so `cache` reported the live file as an ORPHAN and the retired file as live. Confidently wrong in both directions at once, which is the one failure that view has no defence against. Derived from the policy map instead: anything with a policy is known, anything else is a leftover, by construction. The self-test asserts both halves - every known name carries a policy, and the front door holds no literal list of cache filenames whatever the variable is called - because the first version of this bug was a correct list that later stopped being correct. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UTX8obQMsjs9kq2rkpU5cZ
…re archives Antony asked why widening was not trivial and whether `refs/backup` was blacklisted. It was not. Nothing was ever excluded - `refTips` handed `for-each-ref` two patterns, `refs/heads` and `refs/remotes/origin`, and stopped there. It now hands it none, which is how git spells "every ref": a bare `for-each-ref` and `refs/**` return the identical 547 refs here. WHAT WAS OUTSIDE THE CORPUS: 64 tags and 45 `refs/backup` refs, while the help text said "every branch tip". 12 of those tags point at commits reachable from nothing else - `backup/pre-recut-324`, `recut-baseline-342`, `archive/presentation` - and this repository parks work in exactly that space before a re-cut, so the excluded region was a LIKELY home for stranded knowledge, not an unlikely one. Widening finds a real one immediately: `docs/inflight/pr-323-docs-outstanding.md` and `process-agent-harness-foss.md` exist only under the tag `backup/pcmetrics-pre-recut`. `%(*objectname)` IS WHY IT WAS NOT PURELY A ONE-LINE CHANGE. An annotated tag's `%(objectname)` is the tag object, not a commit, so it would have contributed a "tip" whose every read fails in a way that looks like an empty branch. The deref field is empty for everything else, so `deref || sha` is correct for both. `refs/stash` is excluded by name - it is this checkout's scratch, and its second parent is the index rather than a line of work. THE OTHER HALF IS THE LABEL, and without it widening would make the report LESS trustworthy for having more data in it. A note held only by a tag or a backup ref is preserved on purpose; a remedy telling someone to go rescue it is a wrong answer dressed as a finding. So the enumeration stays wide and every tip carries `kind` and `archival`; `stranded` marks a cluster with no live ref as `[ARCHIVE ONLY - preserved, not stranded]` and picks a LIVE ref as its example, because an archival ref name is not somewhere a reader can go and continue the work. Unknown ref spaces are treated as archival deliberately - over-reporting preserved work as live is the error that sends someone to rescue something nobody lost. Cost: 435 refs to 546, `stranded` at 2.1s. Two self-tests, both negative-controlled against the real pre-fix behaviour: one reverts the enumeration to the two patterns, the other forces every cluster to report as live. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UTX8obQMsjs9kq2rkpU5cZ
…ture entry with the code THE FEATURE ENTRY DESCRIBED A HOOK THAT NO LONGER EXISTS. `docs/features/cross-ref-repository-queries.yaml` still said "a PostToolUse hook folds a newly created PR in as it happens" - the coupling that was deleted, replaced by the cache layer stating its own policy per kind. A data entry that describes a removed component is worse than one that says nothing, because nothing about it looks stale. It also predated the corpus widening, so it claimed less than the tool now does. Added as a boundary rather than a feature line: it reads EVERY ref, tags and `refs/backup` included, and says which are archives - the distinction is what keeps preserved work from being reported as lost. RETAGGED, both to `blind-spot`. Antony's word was "blindness"; the vocabulary in `bin/lib/inflight-tags.sh` has no such value and `check-inflight-tags.sh` fails an unknown one, so this uses the house spelling for the same consequence. - `ci-inflight-next-commands.md` was `stranded-work`, which named one of its commands rather than the cost of not having them. - `ci-node-query-client.md` was `ci`, which named the area, and the impact axis is deliberately the CONSEQUENCE of not knowing a thing, not where it lives. Both notes are about queries that cannot currently be asked, so not having them means concluding "nothing found" over a question that was never really asked. That is `blind-spot` exactly. REMOVED the self-test-density bullet from `process-adopt-external-harness.md` - Antony's call, and it does not belong there twice over: it argues that something is NOT a differentiator, inside a list of what adoption would COST, and the measured version of the point that survived it is in `docs/plans/2026-09-02-001-investigate-adopt-or-build-re-run.md` with figures on both sides. The 2026-09-01 record keeps the original claim, as a dated record should. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UTX8obQMsjs9kq2rkpU5cZ
…ame-defect sweep MERGE-PREP SWEEP FOR THE CLASS BEHIND THE KNOWN-CACHE BUG, per AGENTS.md's rule that a fix should look for other instances before it lands. That bug was a constant written twice which drifted, so `cache` reported the live file as an ORPHAN and the dead file as live. Grepping for the shape found a bigger one: `REPO = 'astubbs/parallel-consumer'` declared identically in `notes.mjs`, `branches.mjs` and `prior-art.mjs` - while `ci-inflight-next-commands.md` said, in as many words, that `REPO` and `NOTES_DIR` "are single constants today". A copied constant is correct until exactly one copy changes, and nothing goes red at that moment. Both now live in `bin/lib/repo.mjs`, which is also the one concrete thing the extract-as-FOSS direction already argued for: everything else under `bin/lib/` is generic, and these two are the only values that would change to point the tool at another project. `notes.mjs` re-exports `NOTES_DIR` so its existing consumers do not all have to move. The self-test asserts there is ONE declaration rather than what it says, because the value is not the property that matters. Two stale pointers the same sweep turned up: - `notes.mjs` still told the reader the per-branch question is "asked by prSearch below" - it is asked by `prForBranch`, in another file. - `views.mjs` printed a `Try:` hint naming `refs/heads refs/remotes/origin`, the narrow enumeration the corpus dropped, so the fallback advice contradicted the tool. FOUND AND NOT FIXED, recorded in the note that owns the migration: `.claude/hooks/inject-recorded-knowledge.sh` runs the same narrow `for-each-ref`, so its cross-ref count silently excludes tags and `refs/backup`. Widening it changes what every session gets injected, which needs its own before-and-after rather than a one-word edit ridden in on a tooling PR - and the real answer is that the hook should not have its own enumeration at all. Also swept and clean: no debug output, scratch files, commented-out experiments or stray artifacts in the 31 files this PR touches; no dangling references to the deleted hook, `prSearch`, `cachePr`, `cache pr` or `pr-search.json` beyond the two that deliberately record the deletion. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UTX8obQMsjs9kq2rkpU5cZ
…nd head are measured alike
`codecov/project` compared totals built from DISJOINT FLAG SETS. The master `build` job uploaded
everything under one `default` flag; a pull request runs the `test` matrix and uploads `unit`,
`integration`, `performance`, `lincheck` and `chaos`, and never `default`. So a PR's total was the
suite flags and the base's was `default` - two different measurements, compared against
`codecov.yml`'s 1% threshold.
MEASURED ON A PR TOUCHING ZERO JAVA: Files 90 -> 90, Lines 4822 -> 4822, Branches 463 -> 463, and a
reported **-3.53%**. Identical code on both sides; only the counting differed. The check is new
enough that nothing had caught it, and it would have gone red on every PR from now on.
`clean verify` passes through `test` and `post-integration-test`, so the pom's `report` and
`report-integration` executions both run - jacoco/ is the surefire half, jacoco-it/ the failsafe
half, which is exactly the split the PR lane makes with two jobs. The master job now uploads them
as `unit` and `integration`.
NOT CARRYFORWARD FLAGS, which is the reflexive fix and the wrong one here. Carrying master's
`default` into a PR total merges a STALE master measurement of the same code, so a genuine drop in
any line that measurement covered is masked. That trades a noisy false positive for a silent false
negative, and this repository has spent too long removing the second kind to add one back.
WHAT IS STILL ASYMMETRIC is written down rather than left implied, in
`docs/inflight/ci-codecov-flags-not-like-for-like.md`: the base now carries {unit, integration} and a
PR carries three more, so a real drop in unit or integration can be masked by coverage the extra
suites contribute. It errs safe on the threshold and unsafe on detection. The complete fix is
per-flag project statuses in `codecov.yml`, which is a status-check policy decision that adds check
contexts to every PR - not something to ride in on a tooling PR unannounced.
THE FIX CANNOT BE VERIFIED BY THE PR THAT MAKES IT. `build` is `push`-only, so the new flags do not
exist until this lands on master and that job runs. Until then every PR still compares against a
`default`-flagged base and `codecov/project` stays red - including this one. A red `codecov/project`
on the first PRs after this merges is the expected state, not a regression.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UTX8obQMsjs9kq2rkpU5cZ
…total nothing can compare The previous commit made master upload `unit` and `integration` instead of one `default` flag. That fixed the disjoint comparison but left the gate on the OVERALL total, which still cannot be like-for-like: a pull request uploads five flags - unit, integration, performance, lincheck, chaos - and master uploads two. No threshold makes that honest. It erred safe on the threshold and unsafe on detection: a real drop in unit or integration coverage could be masked by coverage the three PR-only suites contribute and the base has no counterpart for. So the total is now `informational: true` - reported as a trend, never gating - and the GATES are `codecov/project/unit` and `codecov/project/integration`, each scoped to its own flag. Both sides of each gate come from the same `-Pci` profile and the same `-Dexcluded.groups=performance,chaos,quarantined,lincheck`: on master from `ci-build.sh`'s surefire and failsafe halves, on a PR from `ci-unit-test.sh` and `ci-integration-test.sh`. Verified those three scripts agree on profile and exclusions before relying on it. A drop there is a real drop. NO GATE for `performance`, `lincheck` or `chaos`. They run on pull requests only, so master carries no counterpart and a status over them would be comparing a measurement against nothing. They still feed the informational total. `carryforward: false` is now stated explicitly for all five flags. It is already the default; it is written down because it is the reflexive fix for this whole problem and the wrong one - carrying a flag forward merges the last measurement into a run that did not produce it, masking a genuine drop in any line that stale report covered. Validated with codecov's own `POST https://codecov.io/validate` before landing: "Valid!", with the flag-scoped statuses parsed as intended. Two new non-required check contexts appear on PRs; branch protection is unchanged, so neither blocks a merge until someone chooses to require it. STILL UNVERIFIABLE FROM HERE, and recorded in `docs/inflight/ci-codecov-flags-not-like-for-like.md` rather than implied: `build` is `push`-only, so the base carries no `unit`/`integration` flags until this lands and that job runs. The first PR after this merges is the real test, and a red or no-data coverage status before then is expected. That note also records, as unverified, that every suite's upload step names both jacoco patterns rather than its own - left alone because codecov merges within a flag by union, and because three of the five suites' report shapes would have to be established rather than assumed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UTX8obQMsjs9kq2rkpU5cZ
…changes #400 landed as a squash, so master carries its content as one new commit while this branch still carried its original commits - the stacked-PR squash trap. 17 files conflicted, most of them add/add. RESOLVED BY EVIDENCE, NOT BY PICKING A SIDE. For each conflicted file, the question asked was whether this branch had changed it since the #400 tip it merged (2d94f1f). Twelve had not, so master's version was taken wholesale - safe because this branch contributed nothing to them, and necessary because master's #400 is NEWER than the tip merged here, having gained work after that point. docs/agent-harness.md was checked separately: master already contains the addition it conflicted over, from #29. The remaining four carried this branch's Codecov work. They were reset to master's version and the Codecov additions re-applied as a patch, so #400's later work is preserved rather than reverted by an older copy of the same file. THE ONE THAT NEEDED A DECISION. bin/test-inflight.mjs conflicted where both sides inserted into the CHECKS array. Comparing the two check inventories rather than resolving the hunk showed #400 had ADDED ten checks after the tip merged here and DELETED two - `pr-search-cannot-report-a-failure-as-no-result` and `the-pr-create-hook-recognises-a-command-behind-a-cd`. Taking this branch's side, or hand-merging the hunk as written, would have resurrected both: a deliberate decision reversed by a merge, which is the failure AGENTS.md's "read the record you inherit" exists to prevent. Master's file was taken and only the four Codecov checks inserted, leaving #400's removals removed and its additions intact. Verified after resolving: the Codecov commands still work against master's newer bin/lib/git.mjs and cache.mjs, which they import; all four Codecov checks pass with their mutants going red; bin/check-all.sh is clean. KNOWN FAILING, AND NOT CAUSED BY THIS MERGE. bin/test-inflight.mjs reports two mutants not going red - `explicit-help-succeeds` and, new with #400's later work, `freshness-reads-this-worktree-not-the-main-checkout`. Control: master's own bin/ extracted to a scratch tree and run with none of this branch's changes fails identically on both. Root cause is the realpath guard described in docs/inflight/ci-codeql-does-not-run-on-stacked-prs.md's neighbour - see the handoff written for #400 - and it is invisible in CI because Linux /tmp is not a symlink.
The #400 merge auto-merged two files with no conflict, and in doing so put back work that PR had deliberately removed: `.claude/hooks/after-pr-create-refresh-cache.mjs` and its registration in `.claude/settings.json`. No conflict was reported precisely BECAUSE this branch still carried #400's older commits - to git it looked like an ordinary addition rather than a revert of a deletion. Its own squash message on master says why it went: THE CACHE STATES ITS OWN POLICY, AND A HOOK WAS DELETED FOR IT. A cache should not be coupled to what triggers it [...] a hook may decide WHEN to act; it may not be [the policy] Master no longer carries the `cache pr` subcommand the hook drove either, so re-adding the hook would have registered a PostToolUse handler for a command that no longer exists. Caught by diffing this branch against master file-by-file and asking, for each one, whether the change was this branch's or something master had decided against - the same check that caught #400's two deleted self-test checks in bin/test-inflight.mjs during the merge itself. That one conflicted and so announced itself; this one did not, which is the more dangerous shape and the reason the file-by-file pass is worth doing rather than trusting a clean merge.
…cov cache was an orphan
Three findings from a simplify pass, two of them silent-wrong-answer defects in
code this branch added.
THE BISECT COMMAND ANSWERED ABOUT THE BRANCH REF. `codecov test --branch <ref>
<name>` searched for <ref>, not <name>:
$ inflight codecov test --branch ci/quarantine-report-status-changes MultiInstanceHighVolume
no test matching /ci/quarantine-report-status-changes/ in 834 recorded tests.
EXIT=0
`args.find(a => !a.startsWith('--'))` looks right and is wrong: `--branch` takes
a VALUE, and a branch ref does not start with `--` either, so the value was the
first thing that matched. Exit 0, plausible wording, wrong question answered -
in the one command whose entire value is being trusted mid-bisect. With the flag
written LAST it always worked, which is why it survived being used.
cvOpts now returns the positionals with flag values removed, and both `test` and
`slow` take their argument from there. The regression check asserts BOTH orders,
because a test that only tried the working order would have passed against the
bug; its mutant goes red.
THE CODECOV CACHE WAS REGISTERED NOWHERE, so three things were wrong at once and
none of them reported it:
- `cacheRead(name, {key, maxAgeMs})` no longer honours a caller's TTL -
#400 moved that to a POLICY table, and cache.mjs says
why in as many words: "two read sites disagreeing about how old is too old is
a cache with two answers". This call site kept passing one. It was silently
dropped, and the TTL actually applied was the one-hour default rather than the
ten minutes the module's header and the CLI help both promise.
- Absent from POLICY means absent from knownCaches(), so `inflight cache`
reported a 2.8M file written SECONDS earlier as "ORPHAN - nothing reads this
any more", and recommended the command that deletes it.
- `cache clear` with no --all deletes orphans, so routine cleanup destroyed the
live cache and forced the next query to refetch the whole corpus.
Registering it in POLICY fixes all three, because that table is the one place
TTL and known-ness are derived from. Verified: the file now lists as live.
An empty corpus is also no longer cached. cache.mjs's `cacheEmpty: false` guard
tests `Array.isArray(value)`, and this value is an object wrapping the array, so
the guard could not see inside it - caching an empty fetch would have served "no
test history" for the whole TTL, which reads as "no flakes recorded".
Both defects arrived the same way: this branch merged a squashed #400 that had
changed the cache API underneath code written against the older one, and a clean
merge reports nothing about a call site whose argument has quietly become inert.
ONE SHARED TEST HARNESS instead of three copies. The three report-comment suites
landed together, each with its own runner and fake GitHub client; two of the
copies were byte-identical. The shared version is deliberately the RICHEST of the
three rather than the smallest common one - sticky's fake recorded call ORDER
(retire-before-create is invisible to any assertion about final state) and its
test() rejected an async body handed to the sync runner, which can never fail.
Sharing down to the simplest would have deleted both. Instead the other two
suites gained them, and all three still pass.
Reviewed by three independent passes; the reuse and efficiency reviewers found
the cache defect separately, which is why it is stated as corroborated rather
than as one reviewer's opinion.
Two commits - #400's in-flight tooling front door and #409's quarantine status-change report. One Java file between them, a script test, so nothing here reaches this branch's code. THE CONFLICT: THREE SIGHTINGS, NOT TWO, AND BOTH SIDES HAD IT WRONG test-load-tightness-flakes.md again, and again because both sides appended a committedOffsetRemoved[1] latest sighting to the same row. This branch's row said two sightings and named #407 and #207; master's said two and named #407 and #409. Neither is right on its own: there are three, and dropping either side's would lose the evidence the ledger exists to keep. Resolved to one row citing all three with what each contributed, which is the part that matters: #407 captured WHICH record survived - the compactor, not the original - and that rules out the 2026-08-05 nudge-race mechanism; #409 attached a RATE; #207 captured neither. Both prose sections are kept because they say different things - one withdraws a claim, the other adds the rate - and neither restates the other. A RECOVERY ATTEMPTED, AND RECORDED AS FAILED #400 landed `node bin/inflight.mjs codecov test <name>`, which reads per-commit outcomes from history that outlives a CI log - exactly the gap this branch recorded when the #207 sighting's log returned zero bytes. Ran against this test it covers only the branch #409 ran on, so that sighting stays unclassifiable. Written into the note as an attempt that does not reach it, so the next reader does not run the same query hoping for a different answer. INHERITED, AND IT INDICTS HOW THIS SESSION SEARCHED #400 adds a rule worth acting on rather than filing: grep and find read the WORKING TREE, and roughly two thirds of this repo's docs exist only on branches that have not merged - so a working-tree search returns a false negative carrying the authority of a completed check, and `bin/inflight.mjs prior-art` is what searches every ref. The prior-art checks in this session used `grep -rln` over docs/, which is precisely the shape that rule names. Unit lane green across all modules; bin/check-all.sh 15/15.
…nd got the cause wrong `node bin/inflight.mjs prior-art codecov` returns four codecov notes and marks exactly one as NOT ON origin/master: this one. The other three arrived with #409 in the master merge this branch already carries, hours before the note was written. ci-codecov-flags-not-like-for-like.md owns the flag-set mismatch and its fix in #400. ci-the-coverage-uploads-still-use-the-inert-glob.md owns what is actually wrong with the per-flag gates - the uploader does not expand `**`, so the pattern matches nothing and the CLI falls back to a tree-wide search; master's full build therefore hands the WHOLE tree to both the unit and the integration flag, while a PR's suite job has only its own half. That is why the two flags report an identical number on the default branch, which both that note and this one used as evidence for different conclusions. Its collision check also lists #414, open since before the diagnosis started here, making exactly that fix with `disable_search: true` and expanded globs - and proving it from the upload logs rather than by inference. So the note was a third statement of a fact this repo already had twice, and its own mechanism - "the base report is partial" - was wrong. A partial base is real and separate (a CANCELLED master run uploads nothing, which is why the base totals here read 83 files against a head of 93), but it is not what makes the per-flag gates red. That observation is offered on #414 rather than kept here, because that PR owns the file it belongs in. The rule this broke is in AGENTS.md and I quoted it in a merge commit two hours before ignoring it: grep and find read the working tree, prior-art reads every ref. Both offending notes were IN the working tree by then, so even the weaker check would have worked - what failed was searching at all before writing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01228AtRiHyTzeH7SjcXzgNj
Master gained #262 (the transactional battle test), #400, #409, #412 and #413 since this branch last merged it. This branch had carried #262's commits unsquashed, so 16 files conflicted, and all but three were that PR's own earlier text against its landed text. Resolutions, so the next reader does not re-audit them: - Whole file from master, verified byte-identical afterwards: PollContextInternal.java (the produce-lock one-release guard, final form), TransactionalBatchVisibilityIT, TransactionalCrashReplayIT, TransactionalClaim, TransactionalBulkCommitTest. Only #262's own commits had touched any of them on this branch. - Hunk-level, master's side of every hunk: AbstractParallelEoSStreamProcessor (cleanUpContext's catch, final form - this branch's own changes to the file auto-merged), README.adoc, README_TEMPLATE.adoc, STRATEGY.md, docs/quarantined-tests.md, the three docs/inflight notes and the 2026-08-07 battle-test plan. - ProducerManagerTest: master's imports and the removal of buildModule, master's three produce-lock release tests folded in from the deleted ProduceLockReleaseTest, and this branch's five revocation tests appended after them. The auto-merge also left two copies each of acquireProduceLockInto and assertProduceLockStillOwnedByContext; the older pair is deleted. Found by the compiler this time, not by a count. - ProduceLockReleaseTest deleted, as master did: #262 folded it into ProducerManagerTest. This branch's edits to it were #262's own review commits. ProducerManagerTest, PollContextInternalTest, ArchitectureTest and the TransactionalClaim register pass on the merged tree. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PVL2FEJ645T76PbybEBUZ6
… costs, over every docs area Lands U1 of the docs context query plan. The code is the snapshot pushed one commit earlier at the author's request; this commit closes the unit with its working-note update, and the two together are the unit. What changed for an agent: `drift` now answers at two costs. The summary tier resolves which refs carry which blob, the baseline's history and the added size of the copy at hand only, in a few hundred milliseconds with one merge-base; the full tier adds branch facts and a preview of what each divergent version adds - its added headings, else its first added line - so the header can say what a branch knows without the reader running twenty-six diffs. Versions carried only by archival refs (tags, backup refs) are reported as preserved and no longer counted as divergent, which changes one line of `note drift` output for the most-shared note and nothing else. The corpus index gains an areas option defaulting to a single DOC_AREAS constant that prior-art's sections now derive from; `note find` and `stranded` keep the notes-only scope they had, and `stranded` says so in its usage. Titles are read in one cat-file batch rather than one fork per blob, which the session index will need. A source-frame helper puts a fixed label first and the "more" command last on every block the harness will inject. Two traps recorded in the code: Node 25 rejects `encoding: 'buffer'` on child_process, so the batch reader uses `encoding: null`; and the shared self-test fixture carries exact-count assertions that a widened corpus would break, so the docs checks build their own repository. The stale corpus-scope section of ci-inflight-next-commands.md is rewritten as resolved: refTips has looked everywhere since #400 landed. Claude-Session: 85dcb374-ef1a-45d2-b7cd-ea904ff9d0a9 Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Description
Started as a desk survey of the agent-harness field — is there something to adopt instead of
hand-building ours? It answered that, the answer produced a tool, and then the tool's existence
changed the answer. The PR carries all three.
bin/inflight.mjsis a front door for this repo's in-flight tooling: one command an agent has toknow, with a help list that names the rest.
Every command exits
0when it ran — whatever it found — and2when it could not. An emptyresult and a search that never happened are different answers, and nothing here reports them alike.
Why build — and this is not the reason the survey gave
The original survey (2026-09-01) concluded that Backlog.md already ships the cross-branch reader,
so do not rebuild it. Antony asked for that comparison to be re-run just before merge, once the tool
had stopped moving. It was, against a running binary rather than documentation, and it retracts
that conclusion:
docs/plans/2026-09-02-001-investigate-adopt-or-build-re-run.md.Their cross-branch layer RECONCILES; ours REPORTS. Same data, opposite operations. Standing on
mainin a provoked three-way divergence, Backlog.md 1.50.1board exportdoes cross branches andfinds the branch-only tasks — then shows the shared task as To Do while the newest version
anywhere says Done, and nothing in any output says the branches disagree. Their selection
function is named
chooseWinners; its older siblingfilterTasksByLatestStateexists to hide atask whose newest version elsewhere is complete. That is drift suppression, and it is the right
design for one board with one true state.
docs/inflight/AGENTS.mdchose the opposite premise in asmany words — "two branches can disagree about what is open".
One of six commands overlaps, and even that one answers a different question: the branch scan reports
the winner, never the ref set, so it cannot say which branches carry a thing. On this repository
note driftonbug-857-family.mdfinds 25 distinct versions holding content master has neverheld, across 80 refs. A reconciler answers that with one version and no sign the other 24 exist.
Two features we would have upstreamed were already closed
NOT_PLANNEDby their maintainer — GitHubIssues sync (their #231) and lifecycle hooks
(their #456), the second reviewed as
implementation-ready. Both are refusals to own a contract, which is a durable no.
The adopt-or-build decision is still not this PR's, and is not taken here.
docs/inflight/process-adopt-external-harness.mdowns it and defers it until after v6. This isevidence for that note, scoped to the tracker question only.
What the working tree cannot show you
Measured on this branch: 582 in-flight note paths exist across the refs and 185 are on
origin/master, and 580 of 901 documents underdocs/exist only on unmerged branches.ls docs/inflight/answers for under a third of what is open.The design, in four claims
git cat-file --batch-checkfed<ref>:<path>resolves one pathacross every ref in ONE process — 27ms, against one fork per ref for the obvious loop. Two refs
sharing a blob are byte-identical, so the diff runs once per distinct version: 37 diffs rather
than 274 for the fork's most-edited note.
question twice: a branch that has not merged recently is "different" and gets more different every
day (198 of 274 refs — pure noise), and the size is wrong in the same direction (one cluster read
+22 -1661, where 22 is the finding). Purely-behind is detected exactly from master's history ofthe path, in two batched calls; sizes are against each branch's merge-base.
for-each-refis given no pattern at all — which is howgit spells every ref, and identical to
refs/**: 546 refs, tags andrefs/backupincluded.Nothing was ever blacklisted; the old code simply passed two patterns and stopped. 12 of those tags
point at commits reachable from nothing else, and this repo parks work there before a re-cut. Every
tip carries
kindandarchival, andstrandedmarks an archive-only cluster[ARCHIVE ONLY - preserved, not stranded]— because widening without that label makes the reportless trustworthy for having more data in it.
bin/inflight.mjsis the only filepermitted to call
process.exit, once. Every queued feature is a view over the same data.Everything is delegated to git plumbing —
for-each-ref,cat-file,ls-tree,merge-base,diff --numstat. The only work in JavaScript is grouping into Maps, and exact answers are preferredover heuristics: blob identity over
--find-renames,merge-base --is-ancestoroverbranch --merged.The cache states its own policy, and a hook was deleted for it
Antony flagged the filename
.claude/hooks/after-pr-create-refresh-cache.mjs: a cache should notbe coupled to what triggers it. That hook reached in from outside to repair a staleness the cache
would not admit to, and it covered exactly one of the ways a PR comes into existence —
gh pr create, in a session that had it loaded — and none of the others.Behind it was a real defect:
prSearchhad no callers. The per-branch fall-through thatprsByBranch's own comment describes was never wired up, sobranchViewread a 24-hour snapshot andtreated absence from it as fact.
bin/lib/cache.mjsnow states per kind how long an answer lives andwhether an absence may be stored at all;
prForBranchasksgh pr list --headon a miss. Thehook, its registration, its self-test and
cache pr <n>are all gone.docs/agent-harness.mdcarriesthe rule: a hook may decide WHEN to act; it may not be load-bearing for whether another component is
correct.
The coverage gate was measuring two different things
Found while this PR was in the merge queue, because
codecov/projectwent red on a change touchingzero Java: Files 90 → 90, Lines 4822 → 4822, Branches 463 → 463, and a reported −3.53%.
Codecov is newly re-enabled here, so nothing had exercised its configuration. The master
buildjobis
push-only and uploaded everything under onedefaultflag; a pull request runs thetestmatrix and uploads
unit,integration,performance,lincheck,chaos— and neverdefault.So every PR compared a total built from the suite flags against a base built from
default:disjoint measurement sets, against a 1% threshold. It would have gone red on every PR from now on.
Two commits fix it. The master job now uploads
unitandintegrationseparately — the split thepom's
reportandreport-integrationexecutions already make. Andcodecov.ymlgates perflag rather than on the overall total, which cannot be like-for-like at all (five flags on a PR
against two on master, a difference no threshold repairs); the total is now
informationaland readas a trend. Both sides of each gate come from the same
-Pciprofile and the same-Dexcluded.groups, checked rather than assumed.Not carryforward flags, the reflexive fix — it merges a stale measurement into a run that did not
produce it, masking a genuine drop in every line that report covered. Trading a noisy false positive
for a silent false negative is the worse half of this repo's two failure modes.
The fix cannot be verified by the PR that makes it, and this is the part to carry forward:
buildispush-only, so the base holds nounit/integrationflags until this lands and masterruns.
codecov/project/unitandcodecov/project/integrationare green here with no base data tocompare against — the flag table reads
(?)for every flag. They are passing vacuously, which isthe same shape as the bug they replace. The first PR after this merges is the real test.
docs/inflight/ci-codecov-flags-not-like-for-like.mdowns that, and says not to require thesecontexts in branch protection until one has been seen comparing real numbers.
A wrong prediction, kept in the record
Rename subtraction was expected to explain most notes absent from master. It explained one of 405 —
blob equality proves a rename only when content was byte-identical at both paths, and a rename almost
always carries an edit. What works is master's history: this repo
git rms a note when its worklands, so a path master once had is closed, not stranded. 387 survivors in 149 clusters, the
largest 42 notes from one workstream.
Found by running it
docs/inflight/bug-857-family.mdis 1816 lines and 128KB on master — the index inflation thesurvey said we had no detector for beyond a person noticing. Widening the corpus then found
docs/inflight/pr-323-docs-outstanding.mdandprocess-agent-harness-foss.mdexisting only underthe tag
backup/pcmetrics-pre-recut.The survey's own findings that still stand
one. Gen 1 committed its JSONL and travelled with branches; gen 2 moves the store to embedded Dolt
behind
refs/dolt/data. Gen 1 fails loudly; gen 2 fails silently, which is the class thisharness exists to remove.
bditself has still not been run — recorded as open, blocking nothing.shared store no branch owns. We have one repository whose state is the thing tracked.
sd0xdev/sd0x-dev-flow, not Beads. It stacks at the Claude layer andcollides silently at the git layer, because
core.hooksPathtakes exactly one path.in the re-run — including that the distinguishing axis is location (in-tree vs beside-the-agent)
when it is authority: whether a branch may disagree at all.
Three PR-less branches absorbed
tooling/prior-art-search-all-refs,docs/inflight-gh-link-graphandci/inflight-index-by-type-and-prioritywere all unpushed, local-only, and invisible to everygh-based check. The first two merged with content. The third merged-s oursas superseded:all twelve of its commits had landed on master by other routes, and its one unique idea — a single
inflight-classaxis — lost to master's three. That rejection is recorded indocs/inflight/AGENTS.mdso it is not re-proposed.Reviewed, and the review found the tool's own failure mode — repeatedly
ce-simplify(3 reviewers) thence-code-review(4: correctness, testing, adversarial,project-standards), then
@claude review this. Standards came back clean. The rest found this:stranded,note findandnote driftprinted aclean empty result and exited 0 —
note findsaid "no note matching /q/ on any of 0 refs.Nothing is a result here: this searched every branch tip". A false claim about work never done, in
the sentence written to reassure the reader.
prior-art's commit-history search never checked whethergit log -Ssucceeded, so a git that died rendered as "nothing".
strandeddropped a genuinely stranded note whose filename had been used before — the toolthat exists to surface work that will be lost, losing it. The filter is blob-aware now.
prsByBranchpromised in a comment that callers distinguish "gh unavailable" from "no PR",which no caller could do because the shape carried no difference.
Four more of the same class were found by using the tool after review closed, each one a search
that reported "nothing" over a question it never really asked:
git fetch origin master— one ref of 292, and thecommonest fetch an agent runs — silenced the only warning that says this corpus is stale. Width is
now read from FETCH_HEAD, not just its mtime.
about the main checkout's fetch, the one place
AGENTS.mdsays never to work.origin/HEADwas in the corpus as a branch namedorigin— git shortens it to plainorigin,so a
/HEADfilter on the short name never fired.cachecalled the live file an ORPHANand the dead file live. The merge-prep sweep for that class then found
REPOdeclared three times;both are now derived from one place.
The suite grew by the gap that let the first two P0s through. No check ever invoked the CLI with
a working command, leaving argv parsing,
emitand the whole views layer unexercised. Now 40checks / 80 assertions, each with a negative control that must go red, run against a hermetic
fixture rather than the author's 546-ref clone — nine checks passed locally and failed in CI before
that fixture existed.
Checklist
AGENTS.md's prior-art row,docs/agent-harness.md,docs/inflight/AGENTS.md,docs/inflight-tool.md, and the README section added tosrc/docs/README_TEMPLATE.adoc(the generatedREADME.adocis rendered from it)docs/features/—docs/features/cross-ref-repository-queries.yamlbin/test-inflight.mjs: 40 checks, each with a negative control that must go red, 80 assertionsce-simplifyandce-code-reviewlocally — 7 reviewers across the two passes, plus@claude review thison the PR; findings applied and answered in-thread