Skip to content

issue_89: Sweep the whole repository, and record what a session read - #90

Merged
dieterbaier merged 4 commits into
mainfrom
issue_89
Sep 4, 2026
Merged

issue_89: Sweep the whole repository, and record what a session read#90
dieterbaier merged 4 commits into
mainfrom
issue_89

Conversation

@dieterbaier

@dieterbaier dieterbaier commented Sep 4, 2026

Copy link
Copy Markdown
Member

Closes #89.

Two findings from a real day in a consuming private journal, both sitting in the default ritual rather than in that project's local skill. The consuming half is dieterbaier/profile-private#42.

issue_89: Sweep every branch, not just the current

The four commands that establish the state at clock-in describe the current branch plus whatever carries an open pull request. A branch with commits and no pull request is invisible to all four — the ordinary shape of work in progress.

On 3 September 2026 that cost a real day: the consuming journal recorded a topic as untouched while four commits for it sat on a side branch of the very repository being swept.

clock-in gains the enumeration; clock-out defers to it by resolvable reference rather than carrying a second copy. This is one repository's branches — the multi-project view still belongs to the private layer.

issue_89: Record what a session actually read

Both skills claim to start from the repository rather than from memory, and neither leaves a trace of it. The session now records which artifacts it fetched, with re-fetchable references, and the handover record gains a read field — deliberately separate from evidence, which is what the day produced rather than what the session fetched.

How far this reaches is stated in the skill rather than implied: the branch enumeration is enforced by a command, this record by review, because the diary form belongs to the project layer and there is no single path a toolkit-level check could look at. A consuming repository with a fixed diary shape can count it, and one does.

issue_89: Report divergence, not a list of refs

From the review. The enumeration printed ref, date and subject and then asked the reader to compare them against the base branch — which puts the missing step back into interpretation and leaves acceptance criterion 1 unmet. It now resolves the base branch instead of assuming it, prints ahead/behind per ref plus the newest commit date, deduplicates local and remote refs to one entry per branch name (local wins: a checkout's branch can be ahead of what was pushed), and ends in one verdict. The API form is a full command rather than two endpoints.

Both forms are fail-closed. The same review found the reason in the sibling repository: a gh call as the left member of a pipeline swallows its own failure, and a while read behind a pipe discards the gaps it collected in its subshell.

The wiring scenario guarded where the command lives, not that it still does anything. A second scenario guards the promise, and it fails against the previous version while the wiring scenario stays green — which is precisely the hole the review named.

issue_89: Page the list, and never hide a ref pair

From the second review. Two ways the sweep could still report a whole repository while having seen part of it.

The API form read one page. Past a hundred branches it printed SWEEP: CLEAR having looked at the first hundred. It paginates now; the checkout form never had the limit.

The deduplication was the worse one. „The local ref wins" holds only while origin/<name> is an ancestor of the local ref. A local branch left behind its remote, or diverged from it, would have had the remote state discarded — the commits the sweep exists to find, dropped by the step that claims to find them. Local wins only when the ancestry says so; otherwise both refs are listed and the pair is named. That is a repository state rather than a failed query, so it reports and does not block.

The semantic guard now covers both forms. Either regression would otherwise have passed it.

Convergence Check — authoritative, against 3df3a29

The result recorded against f6695a2 is void; this replaces it.

Result: Converged.

# Question Answer
1 Intent and scope Passed (deterministic for criteria 1, 3, 5, 7). Criterion 1 — „branches enumerated with their divergence" — is now verified by execution against a repository built to contain every ref-pair case, not by reading the command. Criterion 7 holds: no line of the session-lead rule, the self-delegation rule or the journal binding is touched.
2 Behaviour Passed (deterministic for the bridge). Two scenarios, two bridged tests, three proven failure paths between them. No waiver taken.
3 Architecture impact Passed (assisted). No new delegation edge; no architecture artifact touched.
4 Decisions Passed (human tier, applied). Three decisions are named in the skill text: command-versus-review enforcement, ancestry as the condition for collapsing a ref pair, and reporting a diverged pair as a state rather than a gap. None warrants an ADR under the proportionality rule — no structural consequence, all reversible, all recorded where they apply. Overrule welcome.
5 Traceability Passed (deterministic for the path). #89 → two scenarios → two tests → four commits → this PR. No metamodel artifact changed.
6 Implementation and verification Passed (deterministic). See below.
7 Documentation and delivery state Passed (assisted). Issue, PR and skills describe the same state; each Convergence Check result was replaced rather than left standing beside a newer head.

Verification

Run Result
node --test, all six JS suites 41 pass, 0 fail
ruby -Itest test/validate_metamodel_test.rb 29 runs, 144 assertions, 0 failures
ruby -Itest test/validate_metamodel_cli_test.rb 4 runs, 13 assertions, 0 failures
Checkout form against a purpose-built repository ahead-local one line; behind-local and diverged both refs listed with the pair named; origin/remote-only listed; SWEEP: CLEAR (base origin/main)
API form against this repository two branches with ahead/behind/date, SWEEP: CLEAR (base main)
Pagination, per_page=1 without --paginate: 1 branch. With: 6.
Negative: base unresolvable SWEEP: BLOCKED — UNAVAILABLE:base (…), exit 1
Negative: default branch unavailable SWEEP: BLOCKED — UNAVAILABLE:default-branch, exit 1
Negative: enumeration reduced to a ref list not ok 5, ok 4
Negative: --paginate removed not ok 5, ok 4

The purpose-built repository carries all four ref-pair cases — local ahead of origin, local behind origin, both diverged, remote only — because the guarantee under review is precisely about which of them may be collapsed.

Residual risk, not a blocker. Whether an agent obeys the recording rule is not verifiable from this repository — the standing limitation the feature file names, tracked as #65.

Workflow notes

The checkout was on issue_86 with an unpushed-looking local branch of its own. Unrelated work: it was left untouched and this change was made in a separate worktree off the latest main.

🤖 Generated with Claude Code

https://claude.ai/code/session_011krf2MRrinYMyArXUuvPpj

Dieter Baier and others added 2 commits September 4, 2026 12:12
The four commands that establish the state at clock-in describe the current
branch plus whatever carries an open pull request. A branch with commits and
no pull request is invisible to all four, and that is the ordinary shape of
work in progress.

It cost a real day: on 3 September 2026 a consuming private journal recorded a
topic as untouched while four commits for it sat on a side branch of the very
repository being swept. The sweep was not wrong, it was incomplete, and the
result was a durable record a later session would have believed.

Clock-in gains the enumeration and clock-out defers to it rather than carrying
a second copy. The new wiring scenario guards both halves: no other skill
carries the command, and clock-out still reaches the skill that owns it. Both
halves were verified to fail on a real regression.

Closes part of #89.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011krf2MRrinYMyArXUuvPpj
Both skills claim to start from the repository rather than from memory, and
neither leaves a trace of having done it. A session that read the issues, the
diary and the topic files and one that reconstructed them from the conversation
produce artifacts that look identical, so the failure is silent by
construction.

The session now records which artifacts it fetched, with references a later
session can re-fetch, and the handover record gains a field for it. That field
is deliberately separate from evidence: evidence is what the day produced, read
is what the session fetched to find out, and collapsing them loses the only
statement that says the day was reconstructed from the repository.

How far this reaches is stated in the skill instead of implied. The branch
enumeration is enforced by a command; this record is enforced by review,
because the diary form belongs to the project layer and there is no single path
a toolkit-level check could look at. A consuming repository with a fixed diary
shape can count it, and one does.

Closes part of #89.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011krf2MRrinYMyArXUuvPpj

@dieterbaier dieterbaier left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review-Ergebnis: noch nicht converged – ein Blocker gegen Akzeptanzkriterium 1.

Das neue deterministische Checkout-Kommando

git for-each-ref ... refs/remotes/origin refs/heads

liefert Ref, Datum und Betreff, aber keine Divergenz zum Basisbranch. Der anschließende Satz „Read it against the base branch“ verlagert genau den fehlenden Schritt wieder in eine nicht deterministische Agenteninterpretation. Auch die genannte API-Alternative beschreibt nur Endpoints, nicht das vollständige Kommando. Damit sind „branches enumerated with their divergence from the base branch“ und die Aussage, die Branch-Enumeration sei durch ein Kommando abgesichert, noch nicht erfüllt.

Bitte ein ausführbares Kommando ergänzen, das den Basisbranch bestimmt bzw. benennt und pro relevantem Ref mindestens ahead/behind sowie das Datum des neuesten Commits ausgibt. Dabei lokale und Remote-Refs deduplizieren oder klar festlegen, welche Ref-Klasse maßgeblich ist.

Der Wiring-Test schützt derzeit nur vor Kopie bzw. fehlender Referenz, nicht davor, dass die kanonische Enumeration die versprochene Divergenz verliert; dafür wäre ein zusätzlicher semantischer Guard sinnvoll.

The enumeration printed ref, date and subject and then asked the reader to
compare them against the base branch. That put the missing step back into
interpretation, which is what acceptance criterion 1 forbids: a list of names
is not divergence, and the claim that the sweep is enforced by a command was
not yet true.

It now resolves the base branch instead of assuming it, prints ahead and
behind per ref plus the newest commit date, and ends in one verdict. Local and
remote refs are deduplicated to one entry per branch name, and the local ref
wins because a checkout's branch can be ahead of what was pushed. The API form
is a full command rather than two endpoints.

Both forms are fail-closed, and the same review found the reason next door: a
gh call as the left member of a pipeline swallows its own failure, and a
while read behind a pipe discards the gaps it collected in its subshell.

The wiring test guarded where the command lives, not that it still does
anything. A second scenario guards the promise: base resolved rather than
assumed, commits counted in both directions. It fails against the previous
version while the wiring test stays green, which is exactly the hole it fills.

Closes part of #89.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011krf2MRrinYMyArXUuvPpj
@dieterbaier

Copy link
Copy Markdown
Member Author

Befund angenommen und behoben — f6695a2.

Zu Akzeptanzkriterium 1. Du hast recht: for-each-ref liefert Namen, und der Satz „Read it against the base branch" hat genau den fehlenden Schritt in die Interpretation zurückgeschoben. Das Kommando löst den Basisbranch jetzt auf (git symbolic-ref refs/remotes/origin/HEAD, kein origin/main-Rateschritt) und gibt pro Ref ahead, behind und das Datum des neuesten Commits aus. Ausgeführt gegen dieses Repository:

issue_86: 2 ahead, 0 behind origin/main, newest 2026-09-04
issue_89: 2 ahead, 0 behind origin/main, newest 2026-09-04
SWEEP: CLEAR (base origin/main)

Zur Ref-Klasse. Dedupliziert statt festgelegt: eine Zeile pro Branchname, und der lokale Ref gewinnt, weil ein lokaler Branch dem gepushten voraus sein kann und die Remote-Kopie die Arbeit dann untertreiben würde. Zwei Zeilen für einen Branch lesen sich wie zwei Branches, deshalb nicht beide.

Zur API-Alternative. Jetzt ein vollständiges Kommando mit .default_branch, Branchliste und compare, mit derselben Urteilsform.

Fail-closed, aus deinem anderen Review mitgenommen. Der Befund an PR #44 gehört zur selben Klasse, deshalb ist er hier gleich mit eingebaut: jede Abfrage einzeln geprüft, gesammelte Lücken statt übersprungener, ein Urteil am Ende. Die beiden Formen, die das still verschlucken, stehen als Begründung im Skill — gh als linkes Pipelineglied und while read hinter einer Pipe.

Zum semantischen Guard. Der wichtigste Teil deines Kommentars, und er stimmt: Der Wiring-Test wäre gegen eine auf for-each-ref reduzierte Fassung grün geblieben. Das neue Szenario prüft die Zusage statt des Orts — Basisbranch aufgelöst statt angenommen, Commits in beide Richtungen gezählt. Gegen die vorherige Fassung: not ok 5, während ok 4 grün bleibt. Genau das Loch.

Verifikation: 41/41 JS, 29+4 Ruby, plus vier Negativläufe (Basis nicht auflösbar, Default-Branch nicht abrufbar, Kommando einkopiert, Enumeration auf eine Liste reduziert).

Der Convergence Check im PR-Body ist ersetzt, nicht ergänzt — das alte Ergebnis beschrieb 4181b62 und ist mit dem neuen Head hinfällig. Neues Ergebnis gegen f6695a2: Converged.

@dieterbaier dieterbaier left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Der ursprüngliche Blocker ist sauber behoben: Basisbranch, ahead/behind, Datum und ein semantischer Guard sind jetzt vorhanden.

Im vollständigen Re-Review bleiben noch zwei Fälle, in denen der als vollständig beschriebene Sweep Arbeit unterschlagen kann:

  1. Die API-Variante liest mit per_page=100 nur die erste Seite und meldet bei mehr als 100 Branches trotzdem SWEEP: CLEAR. Bitte paginieren.
  2. „One entry per branch name, and the local ref wins“ ist nicht allgemein sicher. Nach git fetch kann ein lokaler Branch hinter origin/<name> liegen oder beide können unabhängig divergiert sein. Dann verwirft die Deduplizierung den Remote-Stand und kann genau die Arbeit ausblenden, die der Sweep finden soll. Lokal gewinnt nur dann sicher, wenn origin/<name> ein Vorfahr des lokalen Refs ist. Andernfalls müssen beide Stände sichtbar werden oder der Namenskonflikt muss als eigener Zustand/Gap ausgegeben werden.

Gerade Punkt 2 liegt im Kern der Anforderung „whole repository“ und ist deshalb kein bloßer Schönheitsfehler. Ein negativer Test mit lokalem Branch hinter bzw. divergent zu seinem Remote-Gegenstück würde die beabsichtigte Garantie gut absichern.

Two ways the sweep could still report a whole repository while having seen
part of it.

The API form read one page. Past a hundred branches it printed SWEEP: CLEAR
having looked at the first hundred - silent incompleteness arriving through
the door marked success, which is the outcome this step exists to remove. It
paginates now; the checkout form never had the limit.

The deduplication was the worse one. "The local ref wins" holds only while
origin/<name> is an ancestor of the local ref. A local branch left behind its
remote, or diverged from it, would have had the remote state discarded - the
commits the sweep is supposed to find, dropped by the step that claims to find
them. Local wins only when the ancestry says so; otherwise both refs are
listed and the pair is named. That is a repository state, not a failed query,
so it reports rather than blocks.

Verified against a repository built for it, with all four cases: local ahead,
local behind, diverged, remote only.

The semantic guard now covers both forms, because either regression would
otherwise pass it while quietly narrowing what the sweep sees.

Closes part of #89.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011krf2MRrinYMyArXUuvPpj
@dieterbaier

Copy link
Copy Markdown
Member Author

Beide Punkte angenommen und behoben — 3df3a29.

1. Pagination. Behoben mit gh api --paginate. Gemessen an diesem Repository mit per_page=1: ohne --paginate 1 Branch, mit --paginate 6. Die Checkout-Form hatte die Grenze nie — for-each-ref zählt alles.

2. Die Deduplizierung — du hast recht, und es war der schwerwiegendere Fall. „Local wins" gilt nur, solange origin/<name> Vorfahr des lokalen Refs ist. Ein lokaler Branch hinter seinem Remote oder divergent dazu hätte den Remote-Stand verworfen: genau die Commits, die der Sweep finden soll, gelöscht von dem Schritt, der behauptet sie zu finden.

Jetzt entscheidet die Ancestry. git merge-base --is-ancestor origin/$name $name — Exitcode 0: lokal gewinnt. Exitcode 1: beide Refs werden gelistet und das Paar benannt. Alles andere: UNAVAILABLE:ancestry(<name>), also fail-closed. Ein divergentes Paar ist ein Zustand des Repositories und keine fehlgeschlagene Abfrage, deshalb wird es berichtet und blockiert nicht.

Negativ getestet gegen ein eigens gebautes Repository mit allen vier Fällen:

ahead-local: 2 ahead, 0 behind origin/main, newest 2026-09-04
behind-local: origin/behind-local is not contained in local — both listed
behind-local: 1 ahead, 0 behind origin/main, newest 2026-09-04
origin/behind-local: 2 ahead, 0 behind origin/main, newest 2026-09-04
diverged: origin/diverged is not contained in local — both listed
diverged: 2 ahead, 0 behind origin/main, newest 2026-09-04
origin/diverged: 2 ahead, 0 behind origin/main, newest 2026-09-04
origin/remote-only: 1 ahead, 0 behind origin/main, newest 2026-09-04
SWEEP: CLEAR (base origin/main)

behind-local ist der Fall, der die alte Fassung entlarvt: Der Remote-Stand liegt zwei Commits vor der Basis, der lokale einen — die Deduplizierung hätte den weiteren von beiden verworfen.

Zum Guard. Beide Regressionen wären an ihm vorbeigekommen, deshalb deckt das semantische Szenario jetzt beide Formen ab: Checkout-Form mit aufgelöstem Basisbranch und Zählung in beide Richtungen, API-Form mit Pagination. Negativlauf mit entferntem --paginate: not ok 5, während ok 4 grün bleibt.

Verifikation: 41/41 JS, 29+4 Ruby, vier Negativläufe, zwei Positivläufe. Der Convergence Check im PR-Body ist gegen 3df3a29 ersetzt: Converged.

@dieterbaier dieterbaier left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Erneut gegen Anforderungen, Nacharbeit und vollständigen aktuellen Diff geprüft: converged.

Die zuletzt offenen Garantien sind jetzt sauber umgesetzt:

  • Die API-Variante paginiert die gesamte Branchliste.
  • Die Checkout-Variante berechnet die Divergenz zum ermittelten Basisbranch.
  • Gleichnamige lokale und Remote-Refs werden nur kollabiert, wenn der Remote-Stand im lokalen enthalten ist; andernfalls bleiben beide sichtbar.
  • Der semantische Guard deckt neben Basisbranch und ahead/behind nun auch die Paginierung der API-Variante ab.
  • clock-out referenziert weiterhin die kanonische Definition, statt sie zu duplizieren.

Damit sind die sieben Akzeptanzkriterien aus #89 erfüllt, ohne die Session-Lead-, Self-Delegation- oder Journal-Binding-Regeln zu verändern. Für den aktuellen Head sind keine GitHub-Status-Checks registriert; im geprüften Diff sehe ich keinen verbleibenden Blocker.

@dieterbaier
dieterbaier merged commit 8821428 into main Sep 4, 2026
1 check passed
@dieterbaier
dieterbaier deleted the issue_89 branch September 4, 2026 11:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

clock-in/clock-out: the sweep sees one branch, and "from the repository, not from memory" leaves no trace

1 participant