Repository navigation
feat(patches): bpatch apply auto-annotates into feature commits - #1539
Conversation
…onflicts and junk apply now creates feature commits automatically after a conflict-free run (--no-annotate opts out; filtered applies never auto-annotate). The intent survives a conflict pause via resolve state, so continue/skip finish the annotation apply asked for. sync is unchanged. annotate itself gains three guards that caused 'apply && annotate' to misbehave: - refuses to run while a conflict resolution is pending instead of committing a half-applied tree (reject files were ending up in feature commits) - filters untracked junk (*.rej, *.orig, .llm/, .browseros-patchignore) through the same ignore set extract uses - reports changed files no feature claims as 'unclaimed' instead of silently leaving the checkout dirty
- apply and sync refuse to start while a conflict resolution is pending, instead of silently clobbering the paused state and bypassing the annotate guard - continue/skip completion excludes skipped conflicts from annotation: the --reject fallback can leave half-applied hunks in tracked files - --changed applies no longer auto-annotate (surgical, like -- files) - a repo without a features registry skips annotation instead of failing the apply - annotate failures are recorded on the result (annotate_error) so the apply outcome is never discarded; commands render it and exit 1 with a standalone-annotate hint - annotate reuses one status snapshot, rescanning only after a commit (untracked-files=all scans are seconds on a Chromium checkout) - dedupe completion + report-path helpers
- skipped rename conflicts now exclude BOTH the new and old paths from auto-annotate, so a half-applied rename's old-file deletion is never committed into feature history - the pending-resolution guard also refuses on unmerged (UU) index entries — the state a conflicted stash pop leaves — and auto-annotate skips a checkout with a live pending stash, so conflict markers and parked local changes never land in feature commits - resolve.Save writes atomically (temp + rename) so a crash mid-write can't leave a corrupt resolve.json that wedges continue/skip/abort - an annotate failure after a skip now appends the still-uncommitted skipped paths to the error, so following the recovery hint does not sweep them in - annotate consumes one status snapshot via in-memory partition instead of rescanning after every feature commit - skipped reasons (no registry, pending stash) surface in output and --json (annotate_skipped); Annotate reuses the shared guard helper
…resence Round-3 review found the pending-stash skip cut both ways: it over-blocked the safe case (a --no-rebase sync parks changes in the stash, leaving the worktree pure patches) while the standalone annotate command stayed unprotected against the unsafe case (a stash rebase leaves conflict markers as plain modified files, not unmerged index entries). Replace it with a precise signal: requireNoPendingResolution — shared by apply, sync, and annotate — now refuses when git's own detector (git diff --check) reports leftover conflict markers. A parked-clean stash has no markers and annotates normally; a stash-rebase conflict is blocked on every path.
|
Greptile (@greptileai) review |
|
Claude (@claude) review |
|
Claude finished Nikhil (@shadowfax92)'s task in 5m 54s —— View job Code ReviewTasks
This is well-structured, carefully hardened code. Three adversarial review rounds show — the edge cases (rename exclusion, parked stash, marker detection, N+1 status scan, atomic resolve.Save) are all handled. No correctness bugs found. The findings below are mostly observations and minor nits. DesignThe core flow is clean:
Findings1. Both for _, rel := range changeReportPaths(change) {
appendUniquePath(&set.report, reportSeen, rel)
}
// ...
for _, rel := range changeReportPaths(change) { // same source
appendUniquePath(&set.commit, commitSeen, rel)
}
2. func UnmergedFiles(ctx context.Context, dir string) ([]string, error) {
result, err := Run(ctx, dir, nil, "diff", "--name-only", "--diff-filter=U")
3. func finishResolvedRun(...) error {
if err := markApplyComplete(ws.Path, state.BaseCommit, state.RepoRev); err != nil { ... }
if err := resolve.Delete(ws.Path); err != nil { ... } // state deleted here
if state.AutoAnnotate {
annotateApplied(ctx, ws, repoInfo, skippedExcludePaths(state), result, progress)
}If 4.
5.
6. Auto-annotate enabled for AutoAnnotate: !noAnnotate && len(filters) == 0 && changed == "",
Test coverageExcellent — the new tests cover every case called out in the design doc:
The SummaryNo correctness bugs. The three minor actionable items are: (1) simplify |
Greptile SummaryThis PR wires
Confidence Score: 4/5Safe to merge; all three write commands now share the same conflict-state guard and the resolve.State is written atomically, closing the previously silent failure modes. The orchestration logic is well-structured and the test suite is comprehensive, covering the key edge cases (partial apply, skipped renames, junk filtering, unmerged entries, conflict markers, parked stashes). Two small issues keep this below a clean score: annotateApplied appends a skipped-conflict recovery hint to any annotate error when excluded paths are present, which can send a user on the wrong recovery path for unrelated failures; and ConflictMarkerFiles produces an empty error message when git is killed by a signal. Neither causes incorrect data to be committed or state to be lost. The annotateApplied function in engine/apply.go and ConflictMarkerFiles in git/git.go are the two spots worth a second look before merging. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant User
participant apply as bpatch apply
participant engine as engine.Apply
participant guard as requireNoPendingResolution
participant annotate as engine.Annotate
participant resolve as resolve.State
User->>apply: bpatch apply ch1
apply->>engine: "Apply(AutoAnnotate=true)"
engine->>guard: check pending resolution / unmerged / conflict markers
guard-->>engine: OK
engine->>engine: applyOperationRange
alt no conflicts
engine->>engine: finishApply
engine->>engine: clearResolveState
engine->>annotate: "annotateApplied(exclude=nil)"
annotate->>guard: requireNoPendingResolution
guard-->>annotate: OK
annotate-->>engine: AnnotateResult
engine-->>apply: "ApplyResult{Annotate}"
apply-->>User: feature commits created
else conflict encountered
engine->>resolve: "Save(AutoAnnotate=true)"
engine-->>apply: "ApplyResult{Conflicts}"
apply-->>User: fix conflict then run continue
User->>apply: bpatch skip/continue
apply->>engine: Skip/Continue
engine->>resolve: "Load(AutoAnnotate=true)"
engine->>engine: finishResolvedRun
engine->>resolve: Delete
engine->>annotate: "annotateApplied(exclude=skipped paths)"
annotate-->>engine: AnnotateResult
engine-->>apply: "ApplyResult{Annotate}"
apply-->>User: feature commits created (skipped files excluded)
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant User
participant apply as bpatch apply
participant engine as engine.Apply
participant guard as requireNoPendingResolution
participant annotate as engine.Annotate
participant resolve as resolve.State
User->>apply: bpatch apply ch1
apply->>engine: "Apply(AutoAnnotate=true)"
engine->>guard: check pending resolution / unmerged / conflict markers
guard-->>engine: OK
engine->>engine: applyOperationRange
alt no conflicts
engine->>engine: finishApply
engine->>engine: clearResolveState
engine->>annotate: "annotateApplied(exclude=nil)"
annotate->>guard: requireNoPendingResolution
guard-->>annotate: OK
annotate-->>engine: AnnotateResult
engine-->>apply: "ApplyResult{Annotate}"
apply-->>User: feature commits created
else conflict encountered
engine->>resolve: "Save(AutoAnnotate=true)"
engine-->>apply: "ApplyResult{Conflicts}"
apply-->>User: fix conflict then run continue
User->>apply: bpatch skip/continue
apply->>engine: Skip/Continue
engine->>resolve: "Load(AutoAnnotate=true)"
engine->>engine: finishResolvedRun
engine->>resolve: Delete
engine->>annotate: "annotateApplied(exclude=skipped paths)"
annotate-->>engine: AnnotateResult
engine-->>apply: "ApplyResult{Annotate}"
apply-->>User: feature commits created (skipped files excluded)
end
|
| got := skippedExcludePaths(state) | ||
| slices.Sort(got) | ||
| want := []string{"chrome/new.cc", "chrome/old.cc", "content/plain.cc"} | ||
| if !slices.Equal(got, want) { | ||
| t.Fatalf("skippedExcludePaths = %v, want %v (rename old path and skipped paths, not applied ops)", got, want) | ||
| } | ||
| } | ||
|
|
||
| func TestApplyRefusesDuringConflictResolution(t *testing.T) { | ||
| ctx := context.Background() | ||
| workspacePath := initGitRepo(t) | ||
| writeFile(t, filepath.Join(workspacePath, "chrome", "core.cc"), "base\n") | ||
| runGit(t, workspacePath, "add", "chrome/core.cc") | ||
| runGit(t, workspacePath, "commit", "-m", "workspace base") | ||
| baseCommit := gitOutput(t, workspacePath, "rev-parse", "HEAD") | ||
|
|
||
| repoInfo := newPatchRepo(t, baseCommit) | ||
| if err := resolve.Save(workspacePath, &resolve.State{ | ||
| Workspace: workspacePath, | ||
| RepoRoot: repoInfo.Root, | ||
| BaseCommit: baseCommit, | ||
| Operations: []resolve.Operation{{ChromiumPath: "chrome/core.cc", PatchRel: "chrome/core.cc", Op: patch.OpModify}}, | ||
| }); err != nil { |
There was a problem hiding this comment.
Missing test for
continue completing auto-annotate
TestSkipCompletesAutoAnnotateFromPausedApply exercises the apply → conflict pause → skip → finish-with-annotation path and passes through finishResolvedRun. The symmetric path via Continue is not covered by a dedicated test. Both calls enter finishResolvedRun through the same branch (next >= len(state.Operations) && len(result.Conflicts) == 0), but a regression specific to how Continue loads or forwards state.AutoAnnotate would not be caught today — for example, state.AutoAnnotate being ignored or not loaded correctly in the Continue code path.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/browseros/tools/patch/internal/engine/engine_test.go
Line: 858-880
Comment:
**Missing test for `continue` completing auto-annotate**
`TestSkipCompletesAutoAnnotateFromPausedApply` exercises the apply → conflict pause → skip → finish-with-annotation path and passes through `finishResolvedRun`. The symmetric path via `Continue` is not covered by a dedicated test. Both calls enter `finishResolvedRun` through the same branch (`next >= len(state.Operations) && len(result.Conflicts) == 0`), but a regression specific to how `Continue` loads or forwards `state.AutoAnnotate` would not be caught today — for example, `state.AutoAnnotate` being ignored or not loaded correctly in the `Continue` code path.
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Greptile SummaryThis PR wires
Confidence Score: 4/5The apply-annotate integration, conflict-pause persistence, and single-snapshot annotate loop are all well-constructed. One gap in the new ConflictMarkerFiles function means a fatal git diff --check failure is silently treated as no markers present, which could let an annotate or apply run on a checkout whose conflict state was not actually verified — a one-line fix. The overall design is solid and the test suite is thorough. The ConflictMarkerFiles exit-code gap is a present defect in a safety guard: git diff --check HEAD exiting 128 returns an empty file list and no error, so requireNoPendingResolution passes without having verified the marker state. All other changed files — the partition loop, atomic resolve.Save, AutoAnnotate persistence, skippedExcludePaths rename handling — reviewed cleanly. packages/browseros/tools/patch/internal/git/git.go — the ConflictMarkerFiles exit-code guard needs the Code > 2 check added. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[bpatch apply] --> B{requireNoPendingResolution}
B -- pending state / unmerged / markers --> ERR[error: finish resolution first]
B -- clean --> C{ops to apply?}
C -- none --> FA[finishApply]
C -- yes --> D[applyOperationRange]
D -- conflict --> E[resolve.Save with AutoAnnotate intent]
E --> PAUSE[return: paused on conflict]
D -- all done --> FA
FA --> G[markApplyComplete + clearResolveState]
G --> H{AutoAnnotate?}
H -- no --> DONE[done]
H -- yes --> I[annotateApplied]
I --> J{features registry?}
J -- missing --> SKIP[AnnotateSkipped recorded]
J -- present --> K[Annotate: one git status snapshot]
K --> L[requireNoPendingResolution]
L --> M[partitionFeatureChanges loop]
M --> N[commitFeatureFiles per feature]
N --> O[Unclaimed = leftover changes]
PAUSE -- user resolves --> P[bpatch continue / skip]
P --> Q[applyOperationRange from state.Current+1]
Q -- more conflicts --> E
Q -- all done --> R[finishResolvedRun]
R --> G2[markApplyComplete + resolve.Delete]
G2 --> S{state.AutoAnnotate?}
S -- yes --> T[annotateApplied with skippedExcludePaths]
S -- no --> U{RestorePendingStash?}
T --> U
U -- yes --> V[restorePendingStash]
U -- no --> DONE2[done]
V --> DONE2
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[bpatch apply] --> B{requireNoPendingResolution}
B -- pending state / unmerged / markers --> ERR[error: finish resolution first]
B -- clean --> C{ops to apply?}
C -- none --> FA[finishApply]
C -- yes --> D[applyOperationRange]
D -- conflict --> E[resolve.Save with AutoAnnotate intent]
E --> PAUSE[return: paused on conflict]
D -- all done --> FA
FA --> G[markApplyComplete + clearResolveState]
G --> H{AutoAnnotate?}
H -- no --> DONE[done]
H -- yes --> I[annotateApplied]
I --> J{features registry?}
J -- missing --> SKIP[AnnotateSkipped recorded]
J -- present --> K[Annotate: one git status snapshot]
K --> L[requireNoPendingResolution]
L --> M[partitionFeatureChanges loop]
M --> N[commitFeatureFiles per feature]
N --> O[Unclaimed = leftover changes]
PAUSE -- user resolves --> P[bpatch continue / skip]
P --> Q[applyOperationRange from state.Current+1]
Q -- more conflicts --> E
Q -- all done --> R[finishResolvedRun]
R --> G2[markApplyComplete + resolve.Delete]
G2 --> S{state.AutoAnnotate?}
S -- yes --> T[annotateApplied with skippedExcludePaths]
S -- no --> U{RestorePendingStash?}
T --> U
U -- yes --> V[restorePendingStash]
U -- no --> DONE2[done]
V --> DONE2
|
| // --check exits 2 when it reports markers or whitespace; both are expected | ||
| // signals, not command failures. A negative code means git never ran. | ||
| if result.Code < 0 { | ||
| return nil, errors.New(strings.TrimSpace(result.Stderr)) | ||
| } |
There was a problem hiding this comment.
git diff --check exits 0 (clean), 1 or 2 (found whitespace/marker issues), or 128 (fatal git error). Only codes 0–2 are "expected signals." Anything above 2 is a fatal error that should be surfaced, not silently treated as "no markers found." This applies the same defensive pattern used by UnmergedFiles.
| // --check exits 2 when it reports markers or whitespace; both are expected | |
| // signals, not command failures. A negative code means git never ran. | |
| if result.Code < 0 { | |
| return nil, errors.New(strings.TrimSpace(result.Stderr)) | |
| } | |
| // --check exits 0 (clean), 1 (found issues), or rarely 2 on some platforms; | |
| // all are expected signals, not command failures. Negative means the process | |
| // was signal-killed; anything above 2 is a fatal git error (e.g. exit 128). | |
| if result.Code < 0 || result.Code > 2 { | |
| return nil, errors.New(strings.TrimSpace(result.Stderr)) | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/browseros/tools/patch/internal/git/git.go
Line: 613-617
Comment:
`git diff --check` exits 0 (clean), 1 or 2 (found whitespace/marker issues), or 128 (fatal git error). Only codes 0–2 are "expected signals." Anything above 2 is a fatal error that should be surfaced, not silently treated as "no markers found." This applies the same defensive pattern used by `UnmergedFiles`.
```suggestion
// --check exits 0 (clean), 1 (found issues), or rarely 2 on some platforms;
// all are expected signals, not command failures. Negative means the process
// was signal-killed; anything above 2 is a fatal git error (e.g. exit 128).
if result.Code < 0 || result.Code > 2 {
return nil, errors.New(strings.TrimSpace(result.Stderr))
}
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
…fication rather than a patch (#366) Five branches had jammed the landing pipeline for four rounds. `land` could say they conflict and how far the base had travelled; the next question was the one a person has to answer, and I kept deferring it: rebase, or re-do the work? Rebasing browseros-ai#1302 would have deleted browseros-ai#1308's landed code and reintroduced a non-ASCII ellipsis into a redaction the base already performs in ASCII, breaking L3 in the same stroke. AND A CLEAN REBASE WOULD NOT HAVE SHOWN IT. A semantic conflict carries no markers: git resolves the text correctly while the combined code is logically broken - upstream renames a function your commit still calls, the changes sit on different lines, nothing conflicts, everything is wrong. So "it applied cleanly" is not evidence, and `salvage.mjs` does not offer it. It applies nothing. It never rebases. The selftest fails if the words `git rebase`, `cherry-pick` or `git apply` ever appear in it. WHAT IT PRODUCES is the standard answer for a branch whose module was restructured underneath it: use the old diff as a statement of what was WANTED, not as patches. Concretely - the names the branch introduces that the repository still does not have: queen-1302 8 of 200 candidates absent: quotaAuthority, provider_quota, estimated_usd_gate, statusMode, readTelemetry, QuotaAuthority, billingProjection, ResearchDeps queen-1303 4 of 243: started_running, plantedSecret, startedUnfinished, startedRunning queen-1387 19 of 273: specQualityHeadingParity, extractorHeadings, checkNames, maskNonAscii, RING_COPY, scanMatchingClose, ... Three branches of 881 added lines reduce to 31 missing names, because most of what they carried had already landed by another route. IT IS NOT A PARSE, deliberately. Six false accusations in this project came from a checker that tried to recognise a declaration by its shape across Swift, TypeScript and markdown, and the list of ways to spell "define" has no end. Every identifier-shaped token is a candidate and the BASE TREE decides which are new - absence is checkable without knowing any language. A hex blob is dropped, because queen-1303's diff offered `fedcba9876` from a fake sha in a fixture, and proposing that as a capability the repository lacks is the `node_modules` accusation in a new costume. The cap on candidates is printed alongside the total, since a silent truncation would read as "this is everything". `--brief` writes the measurement as an issue a bee can pass, and every criterion is FAIL-TO-PASS BY CONSTRUCTION: the name was measured absent from the base a moment earlier, so it cannot be satisfied by an empty patch and `verdict-audit` will check it against both the fork point and the branch without being asked. Filed as Trinity-S-AI-DNA/trios#1537, browseros-ai#1538 and browseros-ai#1539, all three passing the delegation gate with their boundaries parsed. They carry no `queen-authored` label on purpose: that label is `author.mjs`'s own WIP accounting, and salvage work is not the backlog it is metering. selftest 155 pass 0 fail. `tri salvage <N> [--brief]`. Co-authored-by: Dmitrii Vasilev <trackgmedernj@hotmail.com>
…and stop advising a rebase that would destroy work (#369) For four rounds `why` warned that all remaining accepted branches conflict, that nothing can land and that the swarm will starve behind them. Mechanically true and useless: those branches will never land. Their base has moved past them, replaying one would delete work that landed since, and there was nothing in the loop that could say so. `salvage` has now measured what each still owes and filed it as a brief against today's base - Trinity-S-AI-DNA/trios#1537, browseros-ai#1538, browseros-ai#1539, browseros-ai#1540. So the branches are recorded as REPLACED, with the issue that replaces each: queen-1302 -> browseros-ai#1537 billing mode and quota authority queen-1303 -> browseros-ai#1538 the started_running state queen-1387 -> browseros-ai#1539 the heading-parity checker queen-1422 -> browseros-ai#1540 the noise filter that deleted its answer Five conflicting branches become one, and the one that remains - queen-1484 - is not debt either: it is an OPEN issue whose own criterion fails against the base today, which the swarm can pick up as ordinary work. RECORDED, NEVER INFERRED. Nothing here guesses that a branch is superseded. Someone says so and says by what, in a file, with a timestamp. The branches themselves are untouched: no deletion, no force, nothing irreversible. The selftest fails if `--force`, `branch -D` or `push --delete` ever appear in this file. AND THE ADVICE CHANGED, because it was wrong. For four rounds this tool told a reader that a conflicting branch "needs a rebase, or an honest closure as superseded". Rebasing browseros-ai#1302 would have deleted browseros-ai#1308's landed code and reintroduced a non-ASCII ellipsis into a redaction the base already performs in ASCII. It now says what was learned: A rebase is usually the WRONG remedy once the base has moved: replaying an old branch can delete work that landed since, and applying cleanly proves nothing, because a semantic conflict carries no markers. tri salvage <N> measures what the branch would still add, asks the issue its own criterion against the base, and writes the remainder as a brief A tool that recommends the destructive option in one line, every round, for four rounds, is worse than one that says nothing. selftest 160 pass 0 fail. Co-authored-by: Dmitrii Vasilev <trackgmedernj@hotmail.com>
…s-ai#1539) (#370) queen-1387 carried accepted work that never reached the base: a heading-parity gate exercised by a bun test. The base has since landed the Swift-side half in its own form (7e8275d: the success-criteria Check reads the bare identifier criteriaHeadings, so extractor and judge share one list) plus a CLI gate, trios/tools/spec-heading-parity.mjs, that nothing invokes. What never arrived is the property held as a module a test can import, so the parity claim runs with the suite instead of when someone remembers a command. trios/tools/spec-quality-heading-parity.mjs is that module, written against QueenSpecQuality.swift as it stands. specQualityHeadingParity(swiftPath) reads both lists out of the source at run time - extractorHeadings from 'static let criteriaHeadings', checkNames from every hasSection(body, ...) argument in the success-criteria Check's met: span - and reports any extractor heading no check name can see, with the same anchored-substring test and in the same direction as the Swift. It doubles as a CLI over both compiled copies, masking non-ASCII in its output: exit 0 parity, 1 orphan, 2 unparsable. queen-spec-heading-parity.test.ts pins both compiled copies (the ring copy and the queen-core copy) green - extractor=10, orphans=0, lists identical across copies - and carries a negative control: a heading spliced into the extractor list that no check name can see must be named as the one orphan, because a gate that cannot fail was never a gate. The suite was seen failing before passing: with orphan reporting mutated out, exactly that control fails. The two Swift copies are deliberately untouched. The fix they carried is already the base's current state, and replaying the old branch's hunk would regress it: its separate Acceptance arm has been subsumed by criteriaHeadings since 7e8275d. Editing them would also put their Cyrillic criteriaHeadings under the ASCII-only rule this task demands of changed files. --no-verify because the pre-commit hook cannot run in this container: its biome-check shells out to npx, which is absent (the equivalent check, bunx biome check on the staged test file, exits 0 with nothing to write), and its file-length step uses bash [[ under sh and excludes tests/** in any case. The commit-msg conventional check was run by hand: OK. Co-authored-by: Trinity Bee <bee@trinity.local>
…pped connection, and I had seen it before (#373) THE SALVAGE LOOP CLOSED, and looking at it found the defect. Last round `salvage` measured what four stale branches still owed and wrote each as a brief. This round the swarm executed three of them. The audit: browseros-ai#1537 8 promised identifiers absent at the fork point, present on the branch, 6 in a declaration browseros-ai#1538 4 of 4, 3 in a declaration browseros-ai#1539 10 of 10, 9 in a declaration browseros-ai#1484 its own criterion failed at the fork point and passes on the branch Four for four, every one proven fail-to-pass. Measurement -> brief -> work -> audit -> land works end to end, and #370, #371 and #372 carry it into the base. BUT THEY HAD BEEN FINISHED FOR A ROUND AND NOBODY COULD SEE THEM. The Queen's own board said "completed" for browseros-ai#1538, browseros-ai#1539 and browseros-ai#1484 - dispatched, run for 9 to 15 minutes, 23 to 35 thousand output tokens each, reviewed and ACCEPTED - while no branch existed on the remote and no comment existed on the issue. `push-work` is the only way a bee's work leaves the container. It had died on `Operation timed out (os error 60)` from `railway ssh`, which is a network hop failing the way network hops fail. A retry thirty seconds later pushed thirteen branches. AND I HAD SEEN IT BEFORE. Two rounds ago the chain printed `push-work=FAILED`, I re-ran it by hand, it worked, and I wrote it off as transient without changing anything. "It worked when I tried again" is not a diagnosis. It is the observation that a retry belongs in the code, and skipping that step cost a round of invisible work. So the channel retries with backoff, and only for channel-shaped failures - "Operation timed out", "Connection reset", "SendRequest", ETIMEDOUT, 502. A git answer is an answer: `! [rejected] ... (non-fast-forward)` is not retried, because a partly-rejected push is a normal outcome the reporting already knows how to describe. The selftest pins both directions. AND A CHANNEL FAILURE MUST NOT LOOK LIKE "NOTHING TO PUSH". Both end without pushing anything and only one of them is fine. The chain reads this step's words, so the words differ: COULD NOT REACH THE CONTAINER after retrying. could not reach the container after 3 attempts: ... Nothing was pushed and nothing was inspected. This is NOT "nothing to push": this step is the only way a bee's work leaves the container, and it did not run. Exit 3, and never a stack trace. selftest 162 pass 0 fail. Co-authored-by: Dmitrii Vasilev <trackgmedernj@hotmail.com>
…ad been failing two thirds of the time (#374) I came to watch four salvage briefs through the swarm. They passed - and looking at them found something much larger. THE SALVAGE LOOP CLOSED. browseros-ai#1537, browseros-ai#1538 and browseros-ai#1539 were executed by bees and every promised identifier is absent at the fork point and present on the branch: 8 of 8, 4 of 4, 10 of 10, six to nine of them in a declaration. browseros-ai#1484's own criterion failed at the fork point and passes on the branch. Landed as #370, #371, #372. Measurement -> brief -> work -> audit -> land, proven end to end. BUT THEY HAD BEEN FINISHED FOR A ROUND AND INVISIBLE. The Queen's board said "completed" for three of them - dispatched, run 9 to 15 minutes, 23 to 35 thousand output tokens each, reviewed and ACCEPTED - with no branch on the remote and no comment on the issue. `push-work` had died on `Operation timed out (os error 60)`. A retry pushed thirteen branches. I HAD SEEN IT BEFORE AND FIXED NOTHING. Two rounds ago the chain printed `push-work=FAILED`, I re-ran it by hand, it worked, and I called it transient. "It worked when I tried again" is not a diagnosis; it is the observation that a retry belongs in the code. SO I ASKED THE LEDGER, for the first time in the whole run. Over 23 hours: reap 45 of 63 runs failed 71% lease 43 of 63 68% push-work 46 of 70 66% close-done 32 of 70 46% Those are exactly the four steps that FREE the swarm. All four reach the container through `railway ssh` and each carried its own six-line copy of the call. None of them retried. They had been failing about two thirds of the time for as long as the ledger goes back, and it was invisible because every run only ever reported itself - a single failure looks like bad luck, and only the record shows it is the system. `channel.mjs` is that call, once. It retries a CHANNEL failure - "Operation timed out", "Connection reset", "SendRequest", ETIMEDOUT, 502 - and never retries an ANSWER: `git push` exits non-zero when a ref is rejected, and a partly-rejected push is a normal outcome its caller already describes. Retrying an answer would be as wrong as crashing on a dropped connection, and the selftest pins both directions. Copying the push-work fix into three more files would have been L2 again: a rule transcribed four times is four rules that agree until someone edits one. It also marks the failure, so a caller can tell "I looked and found nothing" from "I never looked" - the same silence, completely different facts. AND THE LEDGER RECORDED NO REASON FOR ANY OF THEM. `line` is set only when a SUMMARY pattern matches, and no pattern matches a failure, so every one of the 46 FAILED entries carries an empty summary. The console had the reason. The record did not, and those failures can never now be diagnosed. Both chains keep the last six lines of a failing step from here on. `tri failures` reads the ledger backwards; `tri channel` says whether the one way in is up. Four `tri` commands were also passing their own name through as an argument, which is why `tri failures --since 1` looked for a step called "failures". selftest 166 pass 0 fail. Co-authored-by: Dmitrii Vasilev <trackgmedernj@hotmail.com>
Summary
bpatch applynow runsannotateautomatically after a conflict-free apply, so the commonapply && annotateflow is one command.--no-annotateopts out; a filtered (-- files) or--changedapply never auto-annotates (they're surgical).continue/skipfinish the annotation a paused apply asked for.apply && annotatedoesn't work" cases:annotatenow refuses to run mid-conflict-resolution (was committing half-applied trees + reject files into feature history), filters untracked junk (*.rej,*.orig,.browseros-patchignorepatterns) the same wayextractdoes, and reports changed files no feature claims as unclaimed instead of silently leaving the checkout dirty.apply/sync/annotaterefuse on unmerged index entries or leftover conflict markers; a parked--no-rebasestash still annotates (pure-patch tree);resolve.Saveis atomic; annotate failures are recorded on the result (annotate_error) so the apply outcome is never discarded.Design
ApplyOptions.AutoAnnotatedrives a shared completion step (finishApplyfor the no-conflict path,finishResolvedRunforcontinue/skip) that persists the intent through a conflict pause viaresolve.State, so the whole flow finishes whatapplystarted. Annotate takes onegit statussnapshot and partitions it per-feature in memory (no N+1 rescans). The one guard that matters for correctness — don't commit a conflicted/half-merged tree — lives in a single sharedrequireNoPendingResolutionhelper (pending resolve state, unmerged entries, orgit diff --checkconflict markers) thatapply,sync, andannotateall call. Default-on annotation is per the explicit request ("apply should automatically do annotate as that is the most common way");--no-annotateis the escape hatch.Test plan
go test ./...,go vet ./...,make test && make build && make cleaninpackages/browseros/tools/patch— all green