Skip to content

Derived index: a worktrees table mirroring every lane's #97 inventory under inventory:<Workstation>, nudged by every inventory write, and lanes --worktrees to read it with or without --index - #171

Merged
brettheap merged 13 commits into
mainfrom
feat/amendment-14-worktrees-index
Oct 6, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

What

The derived index (Amendment 14, #160/#164) gains its second source: inventory:<Workstation>, this workstation's #97 worktree inventory, copied into the worktrees table schema 1 already created with this issue's columns — so no store needs a new schema version. A new read, lanes --worktrees [<lane> | --all], lists every open worktree; with --index it reads the table, which holds every workstation syncing into the same store, so Raven sees Eagle's leftovers.

  • lanes-edit.sh worktrees [<lane> | --all] [--index] [--fetch] — one row per tree, the ONE implementation both the listing and the indexer read. Lanes are the listing's own (lanes_rows --all --closed, so a closed lane's leftovers are not hidden); trees are lane_trees_list's sidecars; owner is lane_is_managed_owned (the read managed-projection makes); lifecycle is lane_state_read's word for a legacy lane; writer liveness is the listing's LIVE. No directory is walked for a tree no sidecar names, and nothing is recomputed: the only git reads are of the RECORDED head (its subject, and its merge-base with origin/main, else main, else origin/HEAD). With --index it goes through index_open worktrees → lanes-index export --worktrees, setting its own export directory and never LANES_IDX_DIR, so no register read is answered out of the index by it.
  • lanes-index sync|reconcile --source inventory — reads lanes-edit.sh worktrees --all (never a parser of its own), upserts keyed by (source, <lane>:<path>), versioned by a digest of the row and its provenance; rows leave when the inventory no longer names them. Provenance only moves forward, per lane: a sync that reads a lane's lifecycle generation below the one its rows were written at writes nothing for that lane and says so; reconcile takes the lane as it now stands. A lane whose trees directory is there and cannot be listed keeps its rows. The source's own row carries a fingerprint of the whole table (the compare-and-swap token) and a sync count. pr is asked of gh best effort (never under LANES_NO_GITHUB=1; a MERGED/CLOSED answer is not asked again while branch and last commit stand). A container with no LANES_WORKSTATION syncs nothing (exit 2, R-A11-14). status names this workstation's inventory; export --worktrees writes every inventory source as the helper's own lines.
  • The nudge — set-lane-state, set-lane-tree, the lifecycle follow after STARTED/RESUMED/ENDED/RETIRED, and a rename's move of the control root leave LANES_INDEX_NUDGE_INV; cleanup starts the same detached fork a landed push does, as lanes-index sync --source inventory (stdin closed, never waited, skipped where lanes-index is not on PATH or LANES_INDEX=off). The sync lock's rerun mark now names its source, so a register sync holding the lock when an inventory nudge arrives syncs the inventory before it lets go (the register's mark keeps its old path).
  • lanes --worktrees renders WORKSTATION · LANE · OWNER · LIFECYCLE · WRITER · BRANCH · DIRTY · UNPUSHED · LAST SEEN · PATH, with a footer that every field is the last observation and that the current truth of a lane is lanes-edit.sh lane-reconcile <lane>. stderr says read: index (<store>) at inventory:<ws> synced <UTC> or read: sources (index <why>).
  • README-lanes.md — "The worktrees table" under The derived index: what it is for (the next account's first query), every column, how rows follow the inventory, the nudge, the read, and what never reads it (no act: not lane-reconcile — there is no lane-reconcile --index — not lane-start, lane-end, lane-handoff or the sweep lane-worktrees sweep: one act that archives, rescues and removes a lane's abandoned worktrees, branches, scratch and killed-suite sandboxes after an ungraceful swap — dry run first, rescue branch or bundle before any removal, never a live writer #162).

Why

Amendment 14(e) left "#97's lifecycle snapshots and worktree inventories" to "a later word's"; #161 is that word. A resuming account — or one sitting down at the other workstation — wants every open worktree before it decides anything, and today that is one lane-reconcile per lane, on the workstation that holds it.

The columns

column from
workstation, lane lanes-edit.sh workstation; the lane's canonical name
owner legacy / managed <owner> / unknown — managed-projection's read
path, branch, dirty_count, unpushed_count, last_seen_utc the tree's sidecar (the last observation); detached <sha>; an unknown count is NULL; an unreadable or other-schema sidecar is unreadable sidecar / unknown schema <n> with the record id where its path would be
base, last_commit merge-base of the recorded head with origin/main (else main, else origin/HEAD); <sha> <subject> — in the tree, or its recorded checkout once it is gone
lifecycle, generation, operation lane-state: RUNNING/SWAPPING/SWAPPED/CLOSED for a legacy lane; INDETERMINATE for a managed or unknown owner, or a snapshot missing, unreadable or of another schema
writer_live the listing's LIVE; NULL where session records could not be read or the lane is bound on another host (18(b))
pr_number, pr_state gh at sync, best effort, nullable
provenance, indexed_utc the generation the tree's own record was filed under; the sync

Decisions worth a reviewer's eye

Tests

tests/test_lanes_index.py (new inventory section) and the Amendment 14 section of tests/test_lane_helpers.sh:

promise where
the table mirrors the sidecars, every column test_the_inventory_source_mirrors_every_lanes_sidecars
replay writes nothing; one write moves one row test_an_inventory_replay_changes_nothing_and_one_write_moves_one_row
rows leave when a tree leaves the inventory test_a_tree_the_inventory_no_longer_names_leaves_the_table
wiped-index reconcile, row for row; a hand-edited row put right test_a_wiped_inventory_reconciles_row_for_row
monotonic provenance, per lane test_an_inventorys_provenance_only_moves_forward_per_lane
the seam: valid / malformed / legacy markers test_the_inventory_owner_column_is_the_seams_answer
--worktrees for a lane with one RUNNING tree and one dirty one test_lanes_worktrees_shows_one_running_tree_and_one_dirty_one
the index read is the source read, byte for byte test_the_index_read_of_the_worktrees_is_the_source_read
fallback ×6 (missing, unreachable, wiped, unknown-schema, refused, off) says read: sources test_each_worktrees_index_failure_falls_back_to_the_sidecars[*]
offline no-read: the inventory writers and lane-reconcile byte-identical against an unreachable store, and the nudges reached it test_offline_no_read_the_inventory_acts_… + shell a14_three (set-lane-tree, set-lane-state, lane-reconcile ×2, lane-trees, lane-state)
poisoned worktrees rows change no act's answer — and the poison is real test_a_poisoned_worktrees_table_… + shell a14_three
the writers nudge sync --source inventory, detached; reads never nudge; LANES_INDEX=off stops it test_an_inventory_write_nudges_and_a_read_never_does
gh best effort; a final answer not asked again test_a_pull_request_is_asked_of_gh_best_effort
status names the inventory; a rerun mark names its source test_status_names_the_inventory_and_a_mark_names_its_source
another workstation's inventory, and a nameless container, refused test_the_inventory_source_is_this_workstations_alone
18(b): a lane bound elsewhere is not pronounced not-live test_a_lane_bound_elsewhere_has_no_liveness_pronounced_here

No test creates a real database or reaches GitHub: every store is SQLite in tmp_path, Postgres is a fake psql, gh is a fake.

Measurements

On Eagle, 2026-10-05, against the live register (61 lanes; this workstation's inventory currently records no trees, so these are the enumeration cost every sync pays), read-only: LANES_NO_FETCH=1, and every store a scratch SQLite under a temporary XDG_STATE_HOME — nothing was written to the workstation's own state. Load average 4–7 (other lanes' suites running).

read time
lanes-edit.sh worktrees --all (the sources) 2.6 – 4.3 s (7.9 s before the listing's dir column was handed to lane_control_root, re-reading every lane's log a second time)
lanes-index sync --source inventory — first sync, then two no-op replays 6.1 s, 6.3 s, 5.7 s
lanes-index reconcile --source inventory --dry-run 6.8 s
lanes --worktrees 2.7 s
lanes --index --worktrees 0.19 – 0.26 s

The sync is detached behind every inventory write and never waited on, so its cost lands on no act.

Closes #161
Refs #97 #160 #164 brettheap/new-workstation#48

Lane: openRepoTools-3

🤖 Generated with Claude Code

Summary by Sourcery

Mirror each workstation's lane worktree inventory into the derived index and expose it through a new worktree listing without changing authoritative lane actions.

New Features:

  • Add lanes --worktrees to list recorded worktree inventory data locally or from the derived index.
  • Add inventory synchronization and reconciliation for the derived index worktrees table, including cross-workstation sources and provenance tracking.

Bug Fixes:

  • Ensure inventory changes trigger detached index synchronization without affecting reads or lane actions.
  • Preserve indexed rows when inventory reads fail or lifecycle provenance moves backward.

Enhancements:

  • Keep indexed worktree observations separate from authoritative lane actions while providing fallback reads from sidecars.
  • Add best-effort pull-request metadata, workstation-aware writer liveness, index status reporting, and source-specific rerun handling.

Documentation:

  • Document the worktrees table, its columns, synchronization behavior, read modes, fallback behavior, and actions that never consume the index.

Tests:

  • Add comprehensive coverage for inventory mirroring, replay, removals, reconciliation, provenance fences, fallback reads, nudges, poisoning, GitHub metadata, workstation restrictions, and liveness semantics.

brettheap and others added 10 commits October 5, 2026 19:37
lanes-edit.sh gains `worktrees [<lane> | --all] [--index] [--fetch]`, the
#97 inventory of every lane on this workstation as US-separated rows: the
lanes are the listing's own (`lanes_rows --all --closed`, so a closed lane's
leftovers are not hidden), the trees are `lane_trees_list`'s sidecars, the
owner is `lane_is_managed_owned`'s answer, the lifecycle is the snapshot's
word for a legacy lane only, and writer liveness is the listing's LIVE.
Nothing is recomputed and no directory is walked for a tree no sidecar
names; the only git reads are of the recorded head (its subject and its
merge-base with main).

This is the one implementation that both `lanes --worktrees` and the
indexer's new inventory source read, so the listing and the derived
index's `worktrees` table cannot disagree. With `--index` the read goes
through `index_open worktrees`, which asks `lanes-index export --worktrees`
and sets its own export directory, never `LANES_IDX_DIR`, so no published
register read is answered out of the index by it.

The listing's directory column is handed to `lane_control_root` as the
payload it takes, so sixty lanes' logs are not read a second time: the
whole read went from 7.9 s to 2.7 s on Eagle's 61 lanes.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lanes-index gains the source `inventory:<Workstation>` (opensoft/
openRepoTools#161), the "later word" Amendment 14(e) left open for #97's
inventories. `sync`/`reconcile --source inventory` read `lanes-edit.sh
worktrees --all` - the inventory through the helper that writes it - and
upsert the `worktrees` table schema 1 already created with #161's columns,
so no store needs a new version. Rows are keyed by (source, `<lane>:<path>`)
and versioned by a digest of the row and its provenance (the generation the
tree's own record was filed under); a tree the inventory no longer names
leaves in the same sync.

The provenance moves only forward per lane: a sync that reads a lane's
lifecycle generation below the one its rows were written at writes nothing
for that lane, and reconcile takes it as it now stands. A lane whose trees
directory cannot be listed keeps its rows. The source's own row carries a
fingerprint of the table as its compare-and-swap token and a sync count.
`pr` is asked of `gh` best effort, never under LANES_NO_GITHUB=1, and a
MERGED or CLOSED answer is not asked again while the branch stands.

The sync lock's rerun mark now names its source, so a register sync that
holds the lock when an inventory nudge arrives syncs the inventory before
it lets go. `export --worktrees` writes every inventory source the store
holds as the helper's own lines, and `status` names this workstation's
inventory. A container with no LANES_WORKSTATION syncs nothing.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
set-lane-state, set-lane-tree, the lifecycle follow after a STARTED,
RESUMED, ENDED or RETIRED, and a rename's move of the control root write
no commit and push nothing, so the derived index's `worktrees` table would
only follow them on a person's reconcile. Each now leaves
LANES_INDEX_NUDGE_INV for `cleanup`, which starts the same detached
`lanes-index sync` a landed push does, as `sync --source inventory`:
stdin closed, never waited, its status never read, skipped where
lanes-index is not on PATH or LANES_INDEX=off. Like the register's flag it
is assigned at start-up whatever the environment says.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`lanes --worktrees [<lane> | --all]` renders `lanes-edit.sh worktrees`:
workstation, lane, owner, lifecycle, writer liveness, branch, dirty and
unpushed counts, last seen and path, with a footer saying every field is
the inventory's last observation and that the current truth of a lane is
`lanes-edit.sh lane-reconcile <lane>`, which reads the disk. With --index
it reads the derived index's `worktrees` table - every workstation syncing
into that store - and stderr says which was read, as for the listing. It
takes none of the listing's narrowing flags, and a helper predating #161
is named as one.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
README-lanes.md's "The derived index" gains the `worktrees` table: what it
is for (the next account's first query, across workstations), each column
and where it comes from, how rows follow the inventory and why the
provenance moves forward per lane, the nudge, the `lanes --worktrees` read
with and without --index, and what never reads it - no act, not
`lane-reconcile` (there is no `lane-reconcile --index`), `lane-start`,
`lane-end`, `lane-handoff` or the worktree sweep (#162). The inventory
section points at the listing, and the reserved-table note becomes the
inventory's.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests/test_lanes_index.py gains an inventory fixture written by the real
writers over real worktrees - a RUNNING lane with one clean tree and one
dirty, unpushed one, a SWAPPING lane whose session is live, and the
leftovers of a valid and a malformed managed marker - and holds the
`worktrees` table to the promise #160 made for the register: the rows
mirror the sidecars (owner, lifecycle, liveness, counts, base, last
commit, provenance); a replay writes nothing and one write moves one row;
a tree the inventory no longer names leaves; a wiped table reconciles row
for row and a hand-edited row is put right; the provenance only moves
forward per lane; the owner column is the seam's answer; `lanes
--worktrees` shows the running and the dirty tree; the index read is the
source read byte for byte; each of six failures falls back to the
sidecars and says so; the inventory writers and `lane-reconcile` are
byte-identical against an unreachable store and over a poisoned table;
the writers nudge `sync --source inventory` and the reads never do; `gh`
is asked best effort; a rerun mark names its source; and another
workstation's inventory, or a nameless container's, is refused.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`worktrees` read `writer_live` as the listing's LIVE and wrote 0 for every
other lane. For a lane whose last STARTED or RESUMED bound it on another
host, this workstation's session records say nothing about its writer, and
Amendment 18(b) pronounces liveness only from inside the binding: from here
it is unknown, never dead. The listing's column 13 says where a lane is
bound, so `elsewhere` now leaves the field empty - NULL in the index,
`unknown` in `lanes --worktrees` - exactly as unreadable session records
already did. A test binds a fixture lane on Raven and reads both.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The column table said only that unreadable session records leave it NULL;
since the previous commit a lane bound on another host does too, and a
reader of the table has to know that NULL is "not established here", not
"nobody is writing".

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A register sync that takes the inventory's rerun mark runs the inventory
sync in its own loop; a refusal there (a container with no
LANES_WORKSTATION) used to propagate as an exception out of that loop.
It is now said and returned as the 2 it is, exactly as `main` would say
it, so the exit is the same and the loop ends the way every other failed
round ends.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shell suite's Amendment 14 section runs each act three ways - as it
always ran, against an indexer configured at a store nobody can reach,
and over a poisoned index. It now syncs this suite's whole inventory into
the poisoned store and makes those rows wrong too (a forged tree, forged
lifecycles, counts and owner), proves `worktrees --index` reads the poison
while the plain read does not, and adds the inventory's acts to the
comparison: set-lane-tree and set-lane-state, whose nudge must reach the
unreachable store, and lane-reconcile, lane-trees and lane-state, which
reach for nothing. The snapshot taken between runs now carries the lanes'
control root, or a writer's second run would be a generation ahead of its
first. set-lane-tree writes repoRC-2's record, because repoRC-5's record
of the same tree was left at a schema the writer refuses to replace.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 5, 2026 21:40
@sourcery-ai

sourcery-ai Bot commented Oct 5, 2026

Copy link
Copy Markdown

Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 1 day and 21 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@brettheap

Copy link
Copy Markdown
Contributor Author

@codex review

@sourcery-ai

sourcery-ai Bot commented Oct 5, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a second derived-index source for each workstation’s #97 worktree inventory, exposes it through lanes --worktrees with optional indexed reads, synchronizes it with guarded per-lane provenance and detached write nudges, and verifies that operational acts remain independent of the derived table.

Sequence diagram for worktree inventory synchronization

sequenceDiagram
    participant Writer as Inventory writer
    participant Helper as lanes-edit.sh
    participant Cleanup as cleanup
    participant Indexer as lanes-index
    participant Store as Derived index

    Writer->>Helper: set-lane-state or set-lane-tree
    Helper->>Cleanup: set LANES_INDEX_NUDGE_INV
    Cleanup->>Indexer: sync --source inventory
    Indexer->>Helper: worktrees --all
    Helper-->>Indexer: inventory rows and provenance
    Indexer->>Store: upsert worktrees rows
    Indexer->>Store: remove rows no longer named
Loading

Sequence diagram for indexed worktree listing with fallback

sequenceDiagram
    participant User as User
    participant Lanes as lanes --worktrees
    participant Index as lanes-index
    participant Store as Derived index
    participant Helper as lanes-edit.sh
    participant Sidecars as Worktree sidecars

    User->>Lanes: --index --all
    Lanes->>Index: export --worktrees
    alt index available
        Index->>Store: read worktrees sources
        Store-->>Index: indexed rows
        Index-->>Lanes: export rows
    else index unavailable
        Lanes->>Helper: worktrees --all
        Helper->>Sidecars: read recorded inventory
        Sidecars-->>Helper: inventory rows
        Helper-->>Lanes: source rows
    end
    Lanes-->>User: render worktree listing
Loading

Entity relationship diagram for the worktrees index

erDiagram
    INVENTORY_SOURCE ||--o{ WORKTREE : contains
    INVENTORY_SOURCE {
        string workstation
        string fingerprint
        int sync_count
        datetime synced_utc
    }
    WORKTREE {
        string lane
        string path
        string owner
        string lifecycle
        int generation
        string branch
        int dirty_count
        int unpushed_count
        datetime last_seen_utc
        int provenance
    }
Loading

File-Level Changes

Change Details Files
Add a workstation-scoped worktree inventory listing and derived-index read path.
  • Introduce worktrees output from sidecars, lifecycle snapshots, ownership projection, and session liveness without walking or recomputing working trees.
  • Support lane/all selection, --index, --fetch, tabular rendering, footer guidance, and index-read fallback diagnostics.
  • Keep indexed worktree reads isolated from register index state.
lanes
lanes-edit.sh
Implement inventory synchronization into the derived index with guarded provenance and source-specific behavior.
  • Add inventory sync/reconcile/export/status handling keyed by workstation, lane, and path, including upsert/removal and replay-digest behavior.
  • Enforce per-lane forward provenance, preserve rows when tree enumeration is unreadable, and maintain source fingerprints and sync counts.
  • Add best-effort pull-request enrichment and reject non-local or nameless workstation inventory sources.
lanes-index
Nudge inventory indexing after inventory mutations while preserving detached, offline-safe operation.
  • Mark state, tree, lifecycle-follow, and rename writes for inventory synchronization.
  • Run detached sync --source inventory nudges when enabled and available, with source-aware rerun marks under the sync lock.
  • Ensure reads and acts do not consume the derived worktrees table.
lanes-edit.sh
lanes-index
Document the worktrees table contract, read semantics, and non-consumers.
  • Describe schema columns, provenance, last-observation semantics, workstation sources, synchronization, fallback behavior, and index isolation.
  • Document that operational acts continue reading sidecars and disk rather than the derived table.
docs/README-lanes.md
Add comprehensive coverage for inventory mirroring, reads, synchronization, safety, and edge cases.
  • Test row-for-row mirroring, replay/removal/reconcile, provenance fences, ownership seams, lifecycle and liveness semantics, and pull-request enrichment.
  • Test indexed versus source reads, fallback modes, poisoned/offline indexes, source restrictions, and detached nudges.
  • Extend shell fixtures and snapshots to validate inventory writers and act behavior.
tests/test_lanes_index.py
tests/test_lane_helpers.sh

Assessment against linked issues

Issue Objective Addressed Explanation
#161 Add a derived-index worktrees table sourced from each workstation's #97 sidecar inventory, containing the specified worktree, lane ownership, lifecycle, liveness, observation, Git, and optional pull-request metadata, with forward-only provenance and removal of rows no longer present in the inventory. ✅
#161 Provide `lanes --index --worktrees [ --all]` for cross-workstation worktree visibility, with source-sidecar fallback when the index is unavailable or invalid, while ensuring the existing reconciliation and lane acts do not read or depend on the derived table. ✅
#161 Keep the inventory index synchronized after inventory writes and validate offline no-read behavior, poisoned or wiped indexes, replay, provenance, ownership, fallback, liveness, and cross-workstation behavior through documentation and tests. ✅

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Inventory provenance repair, source-mark handling, and cross-host liveness have unresolved correctness issues.

Review effort: Balanced
Findings: 3 Medium severity · 1 Low severity

Open (4)
What changed in this PR

Adds derived-index support for lane worktree inventories and cross-workstation worktree listings.

Changes:

  • Adds inventory synchronization, reconciliation, export, and nudging.
  • Adds local/indexed lanes --worktrees rendering.
  • Adds documentation and comprehensive inventory tests.
File Description
lanes-index Implements inventory indexing and export.
lanes-edit.sh Produces inventory rows and triggers syncs.
lanes Adds the worktree listing UI.
docs/​README-lanes.md Documents the worktrees table.
tests/​test_lanes_index.py Tests inventory index behavior.
tests/​test_lane_helpers.sh Extends index-isolation integration coverage.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread lanes-edit.sh Outdated
Comment thread lanes-index Outdated
Comment thread lanes-index Outdated
Comment thread docs/README-lanes.md Outdated
Copilot AI balanced review requested due to automatic review settings October 5, 2026 22:28
Four findings, each a real defect:

- writer_live: the listing's LIVE overrode the locality and readability
  checks, so a still-running local session of an older binding read as a
  live writer for a lane now bound on another host. Amendment 18(b)
  pronounces liveness only from inside the binding: `elsewhere` and an
  unreadable session read now win, and a test binds the live fixture lane
  on Raven.
- the no-op: "nothing to write" was decided from the rows alone, so a
  fingerprint changed by hand with every row right was never repaired. The
  source row must now agree too, and a test changes it and syncs.
- the sync loop returned on the first failed source, dropping every mark
  already taken off the disk with it. It now drains them all and exits with
  the first failure; a test fails the register round and sees the
  inventory synced.
- the manual (and the listing's own comment) promised the index read was
  the source read byte for byte; on a store other workstations sync into
  it carries their rows too. The promise is now this workstation's rows,
  and the whole read only where no other workstation writes the store.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Inventory read failures, generation rollback, and sync-lock limits can incorrectly remove rows or lose pending synchronization work.

Review effort: Balanced
Findings: 3 High severity

Open (3)
Resolved since last review (4)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Preserve final PR states during reconcile

lanes-index:1859

The mode == "sync" guard makes reconcile re-query unchanged MERGED/CLOSED pull requests. That contradicts the documented final-state cache and can replace a final answer with NULL when this best-effort call fails. Preserve final states in both modes while keeping the no-GitHub carryover sync-specific.

Comment thread lanes-edit.sh Outdated
Comment thread lanes-index
Comment thread lanes-index
Copilot AI balanced review requested due to automatic review settings October 5, 2026 22:40
Three findings, each a way rows or marks could be lost:

- `worktrees` read any lane-trees failure other than "no record" as an
  empty inventory once the trees directory was found, so the next sync
  removed that lane's rows. 8 stays "nothing recorded"; any other exit is
  now an UNREAD row, whose rows every sync keeps. A test makes a lane's
  trees directory unlistable and finds its rows kept and the listing
  saying NOT READ.
- the per-lane forward fence skipped a lane that names no tree now, so a
  lane whose snapshot was moved aside and begun again without trees was
  emptied by a sync. For the few lanes whose rows would all leave, the
  indexer now asks `lanes-edit.sh lane-state` for the generation and holds
  the lane where it went back; reconcile takes it. A test does exactly
  that.
- the eighth-round cap returned with sources still due whose marks it had
  already taken off the disk. It now marks them again for the next sync,
  as a mark it never took is left; a test keeps both sources busy to the
  cap and finds both marks on disk.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

A first sync of an unread inventory can publish an empty, apparently valid indexed answer.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
Resolved since last review (3)

Comment thread lanes-index
#121 (supervised context restart) landed as 00971b9 while #171 was
open. It changed lanes-edit.sh, lane-handoff, lane-start,
docs/README-lanes.md and tests/test_lane_helpers.sh. This is a merge
and not a rebase because the branch is already pushed, and rewriting
it would need a force push.

One textual conflict: the unknown-subcommand refusal in lanes-edit.sh.
The resolution takes #121's list (lane-holders, legacy-restart-check,
publish-handoff, restart-intent, set-restart-intent) and appends this
branch's `worktrees`. The list still matches the dispatcher's 63 arms.

One semantic fix on top of #121's text: #121's new lane_state_rename
branch, taken when a lane has restart history, moves lane-state.yaml
and trees/ into the new name's control root. That move changes the
inventory the worktrees table mirrors, so it now sets
LANES_INDEX_NUDGE_INV like the other two move branches. #121's
restart-intent.yaml is not part of that inventory, so its writer
nudges nothing.

docs/README-lanes.md and tests/test_lane_helpers.sh merged without
conflict. This branch's changes to those two files and to lanes-edit.sh
are unchanged from fea9a29..f1e96c1, apart from the refusal list and
the nudge line above.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 6, 2026 09:52
@sonarqubecloud

sonarqubecloud Bot commented Oct 6, 2026

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Partial source reads can remove indexed inventory rows, and several failure paths violate documented refusal or fallback behavior.

Review effort: Balanced
Findings: 1 High severity · 2 Medium severity · 1 Low severity

Open (4)
Resolved since last review (1)

Comment thread lanes-edit.sh
Comment thread lanes-index
Comment thread lanes-index
Comment thread lanes
@brettheap brettheap added the ready landing gate: runs tests-macos once before the squash label Oct 6, 2026
@brettheap
brettheap merged commit f5444c5 into main Oct 6, 2026
15 checks passed
@brettheap
brettheap deleted the feat/amendment-14-worktrees-index branch October 6, 2026 15:02
brettheap added a commit that referenced this pull request Oct 6, 2026
Bring #168 current with main so the lander can squash it on a green
head. The merge was clean: #171 and #168 share only
docs/README-lanes.md, where git placed #171's worktrees-index section
and #168's lane-worktrees section without overlap, and both stay.
#171 adds no placed file, so the install lists and their counts keep
lanes-index (#164) and lane-worktrees (#168) as they were.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 6, 2026
Bring #169 current with main after #168 landed as 2080f3d, so the
lander can squash #169 on a green head. Main also carries #171
(f5444c5) and #172 (3660224). The merge base is 00971b9 because the
squash is not an ancestor of this branch, so git saw #168's files as
added on both sides. Four files conflicted:
- lane-worktrees (add/add) and README.md: main's copy is byte-identical
  to #168's old head 87da332, and main changed neither file after it,
  so this branch's copy (#168 plus #169) is the resolution.
- docs/README-lanes.md: the one block is #169's two report subsections
  against nothing on main's side. They stay, and #171's worktrees-table
  subsection, which merged cleanly, stays too.
- lanes-edit.sh: #171 and #169 each added a usage line, a dispatcher arm
  before `*)`, and a refusal-list entry. Main's text comes first, then
  #169's: the `worktrees` line and arm, then `pathspec-check`. The list
  ends `|set-restart-intent|worktrees|pathspec-check`, so it still
  matches the arms the repo-hygiene parser reads (64).
Changed-line comparison: this merge over main equals #169's own diff,
and over 45fa45d equals main's changes since 87da332 (#171 and #172).
The only difference in each is the refusal-list line that carries both
entries.

Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready landing gate: runs tests-macos once before the squash

Projects

None yet

2 participants