diff --git a/.config/nextest.toml b/.config/nextest.toml index b36d13617..8cd38cc16 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -311,6 +311,10 @@ test-group = 'external' filter = 'test(/(^|::)mcp_http_and_stdio_expose_identical_tool_set$/)' test-group = 'external' +[[profile.default.overrides]] +filter = 'test(/(^|::)memory_public_cli_lifecycle_after_agent_terminal$/)' +test-group = 'external' + [[profile.default.overrides]] filter = 'test(/(^|::)metadata_model_field_prefers_event_model$/)' test-group = 'external' diff --git a/.github/workflows/memory-portability.yml b/.github/workflows/memory-portability.yml new file mode 100644 index 000000000..2d4847ac9 --- /dev/null +++ b/.github/workflows/memory-portability.yml @@ -0,0 +1,83 @@ +name: Memory SQLite portability + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + memory-fts5-probe: + name: memory-fts5-probe (${{ matrix.platform }}) + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - platform: linux-amd64 + runner: ubuntu-latest + target: x86_64-unknown-linux-gnu + - platform: linux-arm64 + runner: ubuntu-24.04-arm + target: aarch64-unknown-linux-gnu + - platform: macos-arm64 + runner: macos-latest + target: aarch64-apple-darwin + - platform: windows-amd64 + runner: windows-latest + target: x86_64-pc-windows-msvc + env: + LIBRA_SKIP_WEB_BUILD: "1" + EXPECTED_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + MATRIX_TARGET: ${{ matrix.target }} + steps: + - name: Checkout reviewed head + uses: actions/checkout@v5 + with: + repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} + ref: ${{ github.event.pull_request.head.sha || github.sha }} + submodules: recursive + persist-credentials: false + + - name: Set up release Rust target + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + target: ${{ matrix.target }} + + - name: Verify tested commit + shell: bash + run: | + actual_sha="$(git rev-parse HEAD)" + if [[ "$actual_sha" != "$EXPECTED_SHA" ]]; then + echo "::error::checked out $actual_sha but expected reviewed head $EXPECTED_SHA" + exit 1 + fi + + - name: Select Perl for vendored OpenSSL + if: runner.os == 'Windows' + shell: pwsh + run: | + $selected = $null + $candidates = Get-Command perl.exe -All -ErrorAction SilentlyContinue | + Select-Object -ExpandProperty Source -Unique + foreach ($candidate in $candidates) { + & $candidate -MLocale::Maketext::Simple -e "1" 2>$null + if ($LASTEXITCODE -eq 0) { + $selected = $candidate + break + } + } + if (-not $selected) { + throw "No Perl installation with Locale::Maketext::Simple is available" + } + "OPENSSL_SRC_PERL=$selected" >> $env:GITHUB_ENV + + - name: Probe release-linked FTS5 + shell: bash + run: | + cargo test --locked --release \ + --features keyring \ + --target "$MATRIX_TARGET" \ + --test fts5_capability_test -- --nocapture diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 31b895b1c..f74b33079 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -156,7 +156,7 @@ compatibility guard is pinned by `compat_global_config_schema_future`. | describe | partial | basic describe, `--tags`, `--always`, `--abbrev`, `--exact-match`, `--long`, `--dirty[=]`, `--first-parent`, `--match`/`--exclude` (wax globs, ≤256 chars; exclude wins over match), `--candidates ` (n=0 ⇒ exact-match), `--all` (use any ref — branches/remotes/tags — with `heads/`/`remotes/`/`tags/` prefixes), and `--contains` (git name-rev: name a commit relative to the nearest descendant tag — ``, `~`, or `~^~`) supported | | notes | partial | `add` / `append` / `copy` / `edit` / `show` / `list` / `remove` / `merge` supported; `--ref` supported; `notes merge` is a 2-way merge of the flat note rows (Libra notes are SQLite-backed, not commit-backed trees) with `--strategy=manual` (default; aborts on a conflicting note — no NOTES_MERGE worktree)/`ours`/`theirs`/`union`/`cat_sort_uniq`; `prune` (remove notes whose annotated object no longer exists in the object store — `-n`/`--dry-run` and `-v`) and `get-ref` (print the active notes ref) supported; the interactive editor fallback for `add`/`edit`/`append` when no `-m`/`-F` is given is supported (`edit` pre-fills the existing note; notes preserve `#` lines — not stripped as comments) | | cherry-pick | partial | one-or-more commit replay, preserving the picked commit's author metadata while using current committer metadata (including `GIT_COMMITTER_*` date/identity overrides), de-signing source messages before deriving the replayed body/subject, `-n/--no-commit` (now also for multi-commit), `-x`, `-s/--signoff`, `-e/--edit`, `-m/--mainline`, `--ff`, `-S/--gpg-sign`, `--allow-empty`, `--allow-empty-message`, `--keep-redundant-commits`, `--empty=` (`stop` default / `drop` skips a redundant pick / `keep` records the empty commit — == `--keep-redundant-commits`), `--cleanup=` (`strip`/`whitespace`/`verbatim`/`scissors`/`default` message cleanup — cleans the body/edited buffer first, then appends `-x`/`Signed-off-by` trailers; `default`/`scissors` fall back to `whitespace` without an editor), repeatable last-wins `-X/--strategy-option ours|theirs` (favor only conflicting hunks while retaining clean changes; whole-path add/delete conflicts choose the requested side), and the SQLite conflict sequencer (`--continue`/`--skip`/`--abort`/`--quit` with line-level three-way conflict markers — diverging hunks only, like Git; delete/modify and binary fall back to whole-file; the `merge.conflictStyle` config is honored (`diff3` adds the `||||||| base` block) — and a merge/rebase mutex) supported; `--rerere-autoupdate` is honoured (when `rerere.enabled` it makes the rerere hook stage a replayed resolution; a no-op with rerere off); custom `--strategy` values remain explicitly rejected and unimplemented; cherry-pick's conflict state now lives in the unified `sequence_state` table (lore.md 2.6 — the lazy `cherry_pick_state` DDL and the `revert_sequence` orphan are retired by migration 2026070401; storage change is transparent). lore.md 2.6 (unified sequencer): any in-progress multi-step sequence (merge / revert / cherry-pick / rebase) now REJECTS starting a DIFFERENT one with `LBR-CONFLICT-002` naming the blocking op — a symmetric cross-op mutex (previously only merge/rebase blocked an in-progress cherry-pick); the in-progress op's own `--continue`/`--abort`/`--skip` stay available, and `libra status` surfaces a non-merge sequence in progress. Submodules are a permanent non-goal (ADR-MG-01 / declined `D24`): a pick whose three-way inputs (parent tree / index / picked tree) record different `160000` gitlink ids is refused before any write with `LBR-UNSUPPORTED-001` naming the path, through the same guard `merge` and `rebase` use. | -| push | partial | branch/tag update, multi-refspec, delete (`-d`/`--delete` or a `:` refspec), `--tags`, and `--mirror` supported; `--force-with-lease[=[:]]` (validates the remote still matches the tracking-ref/expected OID before sending; conflicts with `--force`) and `--porcelain` (machine-readable per-ref lines; conflicts with `--json`/`--machine`) supported; `--atomic` supported (advertises the `atomic` capability so the remote applies all ref updates together; refused up-front if the remote does not advertise `atomic`); `--push-option`/`-o ` supported (sends a push-options section gated on the remote's `push-options` capability); `--follow-tags` supported (also pushes annotated tags reachable from a pushed ref and missing on the remote); `--signed` supported (builds a GPG-signed push certificate via the vault signer, gated on the remote's `push-cert` capability/nonce); `--no-progress` supported (suppresses the "Compressing objects" / "Writing objects" progress meter on stderr, like `git push --no-progress`); `--no-verify` accepted as a **no-op** (Libra runs no client-side `pre-push` hook; declined-decision D3); `--no-thin` spells the self-contained default explicitly. local file remote rejected — intentional (see [docs/development/commands/_compatibility.md#d2-本地-file-remote-的-push](docs/development/commands/_compatibility.md#d2-本地-file-remote-的-push)); lore.md 2.10 refinements: `--force-if-includes` is REAL (with the All/Ref lease forms it additionally requires the remote-tracking tip to be integrated locally — pushing the tip, a descendant, or a tip REACHABLE from the pushed branch's reflog; conservative rejections, whole-push error rather than a per-ref porcelain row — documented divergence; silent no-op with the exact lease form or no lease, Git parity); `--thin` is REAL (REF_DELTA entries against server-known bases — the advertised old tips; self-contained Libra delta encoder with git-convention 64KiB copy ops since git-internal's delta module is private; net-win + 8MiB caps, full fallback on any miss; verified round-trip against real `git receive-pack` on BOTH unpack paths); self-contained remains the DEFAULT (`push.thin` config unsupported — intentionally different from git's thin-by-default, revisit recorded); lease tracking lookup consults both tracking-ref naming conventions (fully-qualified `refs/remotes//` as written by clone/fetch/push, plus a legacy short-name probe for repositories written by older binaries); `--delete`/`:ref` deletion resolves short names against the remote's advertised refs (`refs/heads/` then `refs/tags/`, ambiguity refused like Git's `dst refspec matches more than one`) and FAILS a deletion naming a ref the remote does not advertise (`unable to delete '': remote ref does not exist`) instead of reporting `Everything up-to-date` (#465); real-git-server interop matrix in L1 (capability degrades refuse cleanly with nothing sent; push-options round-trip via pre-receive hook) | +| push | partial | branch/tag update, multi-refspec, delete (`-d`/`--delete` or a `:` refspec), `--all`, `--tags`, and `--mirror` supported; the repository-local Memory authority is excluded from ordinary, `--all`, and `--mirror` plans, and exact explicit refspecs are rejected before transfer; `--force-with-lease[=[:]]` (validates the remote still matches the tracking-ref/expected OID before sending; conflicts with `--force`) and `--porcelain` (machine-readable per-ref lines; conflicts with `--json`/`--machine`) supported; `--atomic` supported (advertises the `atomic` capability so the remote applies all ref updates together; refused up-front if the remote does not advertise `atomic`); `--push-option`/`-o ` supported (sends a push-options section gated on the remote's `push-options` capability); `--follow-tags` supported (also pushes annotated tags reachable from a pushed ref and missing on the remote); `--signed` supported (builds a GPG-signed push certificate via the vault signer, gated on the remote's `push-cert` capability/nonce); `--no-progress` supported (suppresses the "Compressing objects" / "Writing objects" progress meter on stderr, like `git push --no-progress`); `--no-verify` accepted as a **no-op** (Libra runs no client-side `pre-push` hook; declined-decision D3); `--no-thin` spells the self-contained default explicitly. local file remote rejected — intentional (see [docs/development/commands/_compatibility.md#d2-本地-file-remote-的-push](docs/development/commands/_compatibility.md#d2-本地-file-remote-的-push)); lore.md 2.10 refinements: `--force-if-includes` is REAL (with the All/Ref lease forms it additionally requires the remote-tracking tip to be integrated locally — pushing the tip, a descendant, or a tip REACHABLE from the pushed branch's reflog; conservative rejections, whole-push error rather than a per-ref porcelain row — documented divergence; silent no-op with the exact lease form or no lease, Git parity); `--thin` is REAL (REF_DELTA entries against server-known bases — the advertised old tips; self-contained Libra delta encoder with git-convention 64KiB copy ops since git-internal's delta module is private; net-win + 8MiB caps, full fallback on any miss; verified round-trip against real `git receive-pack` on BOTH unpack paths); self-contained remains the DEFAULT (`push.thin` config unsupported — intentionally different from git's thin-by-default, revisit recorded); lease tracking lookup consults both tracking-ref naming conventions (fully-qualified `refs/remotes//` as written by clone/fetch/push, plus a legacy short-name probe for repositories written by older binaries); `--delete`/`:ref` deletion resolves short names against the remote's advertised refs (`refs/heads/` then `refs/tags/`, ambiguity refused like Git's `dst refspec matches more than one`) and FAILS a deletion naming a ref the remote does not advertise (`unable to delete '': remote ref does not exist`) instead of reporting `Everything up-to-date` (#465); real-git-server interop matrix in L1 (capability degrades refuse cleanly with nothing sent; push-options round-trip via pre-receive hook) | | fetch | partial | repository/refspec supports short sources, exact `:`, one wildcard per side, case-insensitive `remote..fetch`, and exact destinations limited to `refs/heads/*` / `refs/remotes//*` (reserved `HEAD` is refused). Destination/reflog/remote-HEAD writes are transactional; a checked-out destination in any worktree and non-fast-forward mappings without `+`/`--force` fail closed. `--all`, `--depth`, `--dry-run` (no download/writes; unknown pre-download ancestry is not reported forced), `-v`/`--verbose`, `--porcelain` (rejects `--json`), tags, `-f`/`--force`, `--no-auto-gc`, `--no-progress`, `--notes`, and `FETCH_HEAD`/`--append` are supported. `--prune`/`-p` deletes tracking refs not live under the effective configured destination mapping; one-off explicit refspecs preserve configured mappings plus ordinary advertised tracking destinations; empty advertisements skip prune. Deletes/reflogs are transactional and `--dry-run` only reports. `--no-prune` overrides strict `remote..prune` then `fetch.prune` defaults. `--refmap`, `--atomic`, and shallow-expansion flags remain deferred. Network fetches have bounded connect, idle/read, and first-byte timeouts; local remotes are exempt. | | format-patch | partial | `A..B`/single-revision ranges, exact `-1 [rev]`, `--root [rev]`, and `--ignore-if-in-upstream` stable patch-id suppression are supported; merge commits are skipped. Output controls include `-o`/`--output-directory`, `--stdout`, `-n`/`--numbered`, `--start-number`, `--numbered-files`, `--suffix`, `--subject-prefix`, `--cover-letter`, `--thread`/`--no-thread`, `--in-reply-to`, `-v`/`--reroll-count`, `-s`/`--signoff` and `--no-signoff`, `--full-index`, `--minimal` (equivalent to Libra's already-shortest default Myers), `--histogram`, `--src-prefix`/`--dst-prefix`, `--no-stat`, `--keep-subject`, `--zero-commit`, signature controls, and RFC 2047 header encoding. `format.subjectPrefix`, `format.signOff`, `format.outputDirectory`, and `format.suffix` use the strict local→global→system cascade with explicit CLI precedence (`--stdout` bypasses outputDirectory). Mail controls include repeatable `--to`/`--cc`, `--no-to`/`--no-cc`, `--from`, `--notes[=]`, and mutually exclusive `--attach`/`--inline`; generated plain and MIME output is consumed by real Git `am`, and Git plain format-patch output is consumed by Libra `am`. `--base ` emits a `base-commit:` plus oldest-first stable `prerequisite-patch-id:` lines on the last patch/cover letter; the base must be an ancestor, `--base=auto` is rejected, and binary prerequisite ids are not guaranteed to match Git. `--interdiff` and `--range-diff` remain unexposed (`--force` is not a Git format-patch flag). | | pull | partial | fetch + fast-forward/three-way merge supported (the merge phase inherits `merge`'s recursive virtual ancestor for criss-cross histories and its `LBR-UNSUPPORTED-001` depth/width refusals — deeper than 20 levels or more than 32 bases at one level, MG-02); `--ff-only`, `--rebase`, `--no-rebase` (countermands `--rebase`, last wins), `--ff`, `--no-ff` (forces a merge commit), and `pull.rebase` / `branch..rebase` / `pull.ff=true|false|only` defaults when CLI flags are absent (config cascade local→global→system, case-insensitive variable matching, local/global encrypted values decrypted, legacy config rows honored; empty/invalid values fail before fetch with `LBR-CLI-002`, local/global config reads with `LBR-IO-001`, unsupported `merges|interactive` modes produce an explicit `LBR-CLI-002`, system-scope failures skipped), fetch `--depth` (shallow pull), `--squash`, `--no-commit`, `--commit` (commit the merge result; may be combined with `--ff`/`--no-ff`/`--ff-only`, does not override fast-forward policy, and is last-one-wins with `--no-commit`), `--autostash` (stash tracked changes before integrating and re-apply after), `--no-progress` (forward `--no-progress` to the fetch, suppressing its progress meter), and `--notes` (forward to the fetch — import the `refs/notes/deps` dependency graph from a local Libra upstream; default OFF, D17) exposed; `pull --autostash` on the MERGE path now rides the merge-owned autostash (held on conflict, applied by merge --continue/--abort; `merge.autostash` config honored) — the rebase path keeps the legacy push/pop wrap; no-upstream pull renders Git-style tracking advice (`libra pull ` / `libra branch --set-upstream-to=...`); runs in a linked worktree in ALL modes since W2 (merge/ff v0.19.35, rebase v0.19.42, and `--rebase --autostash` v0.19.54 — the autostash wrap uses the stash stack-lock + by-id CAS protocol and pops exactly its own entry) Submodules are a permanent non-goal (ADR-MG-01 / declined `D24`): both integration paths inherit the guard — a merge or a `--rebase` replay that would have to arbitrate a `160000` gitlink is refused with `LBR-UNSUPPORTED-001` before the autostash and before any index/worktree write.; the merge phase also inherits merge's directory/file collision handling (MG-04: `~HEAD` / `~` move + `CONFLICT (file/directory)` line in human output; JSON/machine stdout stays clean) and its per-side rename detection (MG-05) | @@ -187,13 +187,14 @@ compatibility guard is pinned by `compat_global_config_schema_future`. | worktree | intentionally-different | `remove` keeps disk dir by default (no implicit data loss); since W3-s1b (§C.7) the keep-dir form DETACHES instead of dropping: the entry moves to `detached_from_registry`, the worktree's scoped DB rows (HEAD/reflog/sequencer/dirty/layer/sparse) are PRESERVED — deleting them would leave a directory that still operates but lost its HEAD — and a gitdir marker fail-closes every command inside the directory (actionable re-add/delete hint) until `worktree add ` re-attaches it (identity-checked against the registry's persisted id, mismatches refused) or `--delete-dir` finishes the removal. `--delete-dir` gives Git-style removal, refuses on a dirty worktree, deletes + fsyncs the parent BEFORE cleaning scoped rows, and on a cleanup failure keeps a `tombstone` entry that `worktree repair` retries. Both remove modes refuse while the worktree has an in-progress rebase/cherry-pick/bisect. `prune` only handles entries whose path stat says NotFound (a permission error never classifies a worktree as missing), skips tombstones (repair's job) and scopes with active sequencer state. add/move/remove/prune record a durable intent journal row before mutating (SQLite cannot join a filesystem rename into one transaction — a crash is rolled forward/back by `worktree repair`, which also retries tombstones and reconciles detached markers; recovery never deletes directories). Every MUTATING repair action (no-arg, ``, non-dry-run `--migrate-layout`) requires `--confirm` — without it the command is refused with `LBR-CONFLICT-002` before any registry lock, database write or filesystem touch — and records exactly one operation-log audit row per executed action (`libra op log`); `--migrate-layout --dry-run` stays read-only and confirmation-free, and `repair --resolve-identity` keeps its dedicated `--yes` The down path of migration 2026072402 refuses while any detached/tombstone/journal state or linked-scope sequencer/rebase/bisect state exists; live workspace leases block via migration 2026072501's own down, which every deeper rollback passes through first. lore.md 2.1: linked worktrees now have ISOLATED HEAD + index + HEAD-reflog — each has a real `.libra/` (a `commondir` pointer to the shared db/objects/hooks + a stable `worktree_id`) and its own `index`; a commit/switch/reset in one worktree never moves another's HEAD or touches its index. The canonical add surface is `worktree add []` / `--detach` / `-b []` (W3-s2 §C.7). WITHOUT a target the new worktree is created DETACHED at the source commit — intentionally different from Git, which creates and checks out a basename-named branch (influenced by `worktree.guessRemote`). A branch target checks the branch out ATTACHED, refused before any side effect when ANY scope (including the invoking worktree) already has it out; a nonexistent branch fails closed — Git's remote-branch DWIM (auto `-b --track` on a unique remote match), `worktree.guessRemote`, and `--track`/`--no-track` are deferred. A commit target (and `--detach`, which also forces branch targets detached) seeds a detached HEAD populated from THAT commit. `-b` creates and checks out a new branch from the start point (default: source HEAD) with FULL rollback on any later failure — no branch-only or orphan-registry residue; `-B`/`--force` (collision bypass / copying uncommitted state), `--lock [--reason]` (use the separate `worktree lock`), `--orphan`, and `--no-checkout` are deferred. Shared: the object store, `refs/heads`/`refs/tags`/`refs/remotes`, stash, config. `worktree list --porcelain` emits each worktree's OWN `HEAD ` plus a `branch `/`detached` line (resolved from that worktree's scoped HEAD row); an entry with no resolvable HEAD (legacy layout / missing scope) omits those lines rather than being mislabeled. Deferred (see the deferred-features list): per-worktree ref namespaces (`refs/bisect`/`refs/worktree`) and pseudo-refs (ORIG_HEAD/MERGE_HEAD/…). The ENTIRE sequencer family — `cherry-pick`, `am`, `revert`, `merge`, `bisect`, and `rebase` (v0.19.42) — IS allowed in a linked worktree: their state is fully worktree-scoped (`cherry-pick`/`am` via the `sequence_state` row keyed by `worktree_id`; `revert` via `revert-state.json`, `merge` via `merge-state.json`/`merge-autostash.json`, all in the local gitdir; `bisect` via the `bisect_state` row keyed by `worktree_id`, with its checkouts/`reset` moving only that worktree's scoped HEAD and files; `rebase` via the `rebase_state` row keyed by `worktree_id` plus the worktree-local `rebase-aux.json` (exec queue / update-refs plan / rewrites / held autostash), with GC tracing every scope's state rows and sidecars as reachability roots; the editor buffers `CHERRY_PICK_MSG`/`REVERT_EDITMSG` are local too), and the start-time sequencer mutex resolves each per-worktree, so two worktrees can run them on their own branches concurrently without interfering. The dirty-set cache is worktree-scoped since W1 (§C.4.1.1, migration 2026072302): `dirty` and `status --scan/--cached/--check-dirty` run in any worktree against that worktree's own rows and freshness meta. The layer registry is worktree-scoped since W1 too (§C.4.1.1, migration 2026072303): every `layer` subcommand runs in any worktree against that worktree's own registrations/ownership (same name and destination may exist independently per worktree; removing a worktree purges its layer rows only when the directory is deleted too — a retained directory keeps its ownership rows so the still-materialized overlay files stay un-stageable). The sparse view is worktree-scoped since W1 as well (§C.4.1.1, migration 2026072304): every `sparse-view` subcommand and the `ls-files`/`diff`/`hydrate` gates act on the current worktree's own patterns and toggle (worktree remove/prune GCs them under the same directory-gone rule as layer rows). The stash stack completes the set since W2 (§C.4.3): the stack itself stays deliberately repository-shared while push/apply/pop act on the acting worktree's own index/workdir under a stack lock with by-id CAS deletion — so `stash` (all subcommands) and `pull --rebase --autostash` run in linked worktrees too, and NO command remains refused in a linked worktree on repository-global-state grounds (`pull`'s fetch phase writes only repository-scoped state; merge/rebase modes run on scoped state as before). `maintenance run` gc/repack run in multi-worktree repositories since W2 too (§C.4.3 typed `GcObjectSource` inventory): reachability roots span every worktree's private index (all stages), every scope's sequencer/rebase/bisect rows, every gitdir's held-autostash + merge/revert/rebase-aux sidecars, the shared refs/reflogs/stash reflog, note blobs, undo view snapshots, agent-run findings manifests, and AI capture checkpoints — with a schema-scan guard test that fails when a new OID-bearing column ships un-inventoried. The inventory is typed: `ReachabilityRoot` keeps objects alive, `AntiRoot` (obliteration tombstones) must never be resurrected, `Boundary` (`.libra/shallow`) stops the traversal without demanding the graft's absent parents, and `IndexOnly` (`object_index` and the ordinal/dirty caches) keeps nothing alive and is invalidated before the deletion it describes. `FETCH_HEAD` is deliberately a NON-root (matching Git): fetch records advertised tips that are already up to date and have no local destination, so rooting it would pin objects nothing references; freshly fetched objects are protected by the one-hour prune grace window instead. `fetch` is allowed in a linked worktree — its `FETCH_HEAD` is now worktree-local, and its other writes (`refs/remotes/*` and the object store) are repository-scoped by design; fetching into a branch checked out in another worktree is still refused. The worktree registry is versioned since W3 (§C.7, migration 2026072401) and is at `schema_version: 3` since W1 (migration 2026073005): v3 adds a durable registration GENERATION (`epoch_counter` plus each entry's `epoch`, reported by `worktree list`) that fences `libra service` dirty-mark requests — instance ids are path-derived, so a worktree removed and re-added in place is otherwise indistinguishable from its predecessor. A v2-era binary would drop the generations on rewrite, so the v3 capability marker refuses it at connect time, and the migration will not roll back while a generation is live. Two entries claiming one identity (possible from an older binary via `add A` → `move A B` → `add A`) refuse every worktree MUTATION; `worktree doctor (incl. `--adopt-approved-project`/`--clear-approved-project` for legacy Always approvals)` names them and `worktree repair --resolve-identity --yes` detaches one to resolve it. `worktrees.json` carries `{schema_version: 3, entries: [...]}` with each linked entry's stable `worktree_id` persisted; a legacy v1 file upgrades in place under the registry lock on the first MUTATING worktree command (ids backfilled from each gitdir; lockless readers never rewrite it), every worktree command applies pending migrations before touching the registry so pre-v2 binaries are refused at connect time by the capability marker, and the renamed top-level key makes a v1 parser fail closed as a second belt (a file mixing both shapes, a malformed v2 document, or a v2 entry violating the identity invariants — main with an id, linked without one — is refused rather than reinterpreted). `repair []` diverges from Git's `worktree repair [...]` by design: Git rebuilds the bidirectional gitdir↔worktree links (and must sometimes run from the moved worktree itself); Libra's no-arg form dedupes the single-file registry and re-ensures the main entry, while `repair ` restores a linked worktree's missing/corrupt `.libra/worktree_id` + `commondir` from the registry's PERSISTED stable id — identity is never guessed, a commondir validly pointing at a different storage is refused without touching either file (never re-homed), unregistered paths, main, and a still-v1 registry (no persisted identities — the no-arg repair upgrades it first) are refused, and it always runs from wherever the shared storage resolves (typically main). BARE repositories (`init --bare` / mirror layouts, detected config-first via the recorded `core.bare` so even a bare directory literally named `.libra` is caught) refuse the ENTIRE worktree family with a stable error (`LBR-REPO-003`) before any registry IO (repository migrations may already apply at the CLI preflight, as for any repository command using the standard schema preflight — not a worktree side effect) — Git allows `git worktree add` from a bare repo, Libra defers bare worktree semantics by design (§C.4.1). Legacy symlink-layout worktrees (W3-s3 §C.6): read-only commands keep working through the shared HEAD/index (no regression), but the worktree-STATE mutation surface (add/commit/switch/checkout/restore/reset/merge/merge-file/rebase/cherry-pick/revert/am/bisect/stash/rm/mv/clean/pull/dirty/hydrate/rerere/read-tree/update-index, the mutating `sparse-view` and `layer` subcommands, the `symbolic-ref` write form, and non-dry-run `op restore`) refuses with `LBR-REPO-003` and a migrate hint — committing there would silently move MAIN's HEAD. `worktree list` (JSON + a new porcelain `layout` line) reports each entry's layout: `main`/`linked-v2`/`legacy-symlink`/`missing`/`corrupt` (plus `task-fuse` for FUSE task worktrees). `worktree doctor` (Libra extension, no Git equivalent) reports per-worktree scope diagnostics — layout, lifecycle state, whether the worktree's own identity is still one the registry knows, and what to do about each finding — and is STRICTLY READ-ONLY: registry, database, lease state and filesystem are byte-identical before and after. Repair actions are separate explicit subcommands, so no hint promises that a bare `doctor` will fix anything. JSON uses the `worktree.doctor` envelope with `schema_version`/`diagnostics[]`/`next_cursor`, an opaque cursor that pages `diagnostics[]` (`workspace_id` ascending, default limit 50, cap 500) per the W4 machine interface. `repair --migrate-layout [--dry-run] []` migrates legacy worktrees from the MAIN worktree under the registry lock via a journaled state machine (migration 2026072403 admits the 'migrate' intent; its down refuses while one is in flight): the target `.libra` must be a symlink resolving exactly to this repository's storage (no-follow verified); a prepared journal-stamped gitdir is installed by atomic renames with the legacy link kept as an identity-named backup until verification passes; the new worktree seeds a DETACHED HEAD at the shared snapshot and rebuilds its private index from that commit — tracked/untracked FILES are never touched (they show as dirty/untracked afterwards) and SHARED staged state is never copied (its worktree ownership is unprovable — commit or stash it in main first); an unmerged shared index, an active main-scope sequencer, or an unreadable HEAD refuses before any rename; crash recovery in `worktree repair` advances or rolls back each journal window by IDENTITY (symlink target, journal-stamped marker), never by bare existence, and keeps materials + the journal on any mismatch. Target-oriented `worktree remove ` (both modes) and `worktree repair ` also refuse a legacy-symlink target with the migrate hint (their writes would route through the shared symlink into MAIN storage), and `worktree move` refuses an entry with a pending migration journal. `worktree doctor [] [--limit N] [--cursor C]` (Libra-only, no Git equivalent) is the READ-ONLY diagnosis of Agent workspace scopes (`workspace_record`, §C.8): no invocation writes a row, registry entry, lease, or file. Without an id it pages `data.diagnostics[]` + an opaque `data.next_cursor` (`workspace_id` ASC, default limit 50, cap 500); with an id it returns the singular `data.diagnostic` and no pagination keys — combining the id with `--limit`/`--cursor` is `LBR-CLI-002`. Each diagnostic carries `workspace_id`/`repo_id`/`lease_state` (`none`|`held`|`expired`) and a `scope_diagnostics[]` of `{code,severity,detail}` findings (foreign repository identity, orphaned workspace, expired lease, missing path, missing/detached/tombstoned registry entry, corrupt or legacy-symlink layout). Records written under a PREVIOUS repository identity are visible here and nowhere else. A cursor this command did not issue fails closed with `LBR-WORKTREE-001` instead of restarting at page one, and a scope that cannot be read — unparseable registry, unreadable record, missing repository identity — fails closed with `LBR-WORKTREE-002` rather than reporting a partial diagnosis. Capture rows written before migration 2026080401 are `legacy_unknown` and excluded from capture/import/export writes until an operator explicitly attributes one through `worktree doctor --adopt-capture-session --confirm`. The command requires a live target workspace fence, converts every legacy capture row for that provider session across session/export/import tables, refuses if any scoped row already exists for the provider session, and appends an immutable audit record; it never changes the frozen read-only `worktree.doctor` JSON schema. | | cloud | intentionally-different | Libra cloud backup/restore extension, not a Git command | | publish | intentionally-different | Libra Cloudflare publish extension, not a Git command | -| agent | intentionally-different | Libra external-agent capture extension, not a Git command. OpenCode export on macOS uses seatbelt (`sandbox-exec`, deprecated / may be removed by Apple) to deny store-external writes and network; host **read is not confined** (unlike Linux bwrap default-deny). Missing `sandbox-exec` fails closed (metadata-only). Historical Claude/Codex/OpenCode sessions can be backfilled through the consented, current-repository-scoped `agent import`; typed redaction, coverage/import fences, local erase tombstones, atomic no-clobber loose-object publication, and doctor-visible crash markers make replays idempotent and fail closed. `agent graph ` is a read-only capture projection (session → turn → revision → subagent), distinct from the orchestrator-thread `libra graph`; its frozen JSON v1 uses a strict metadata whitelist, preserves shared checkpoint evidence, reports legacy captures as `unindexed`, and shows local tombstones as `erased` without resurrection. The interactive `agent graph` TUI entry was removed in the W5 breaking release (W5-08, shipped in v0.20.0): bare `libra agent graph ` fails with a usage error plus a migration hint, and the structured `libra --json agent graph` / `--machine` forms are unchanged. Claude `/subagents/*.jsonl` content is captured as independent source-revisioned `scope=subagent` checkpoints; hook boundaries remain distinct evidence, and association stays unresolved unless a provider-stable id matches exactly one boundary. The per-source import read cap consumes `agent.max_transcript_read_bytes` but never exceeds the 16 MiB adapter hard cap. `agent list --json` defaults to frozen schema v1; `--schema-version 2` opts into method availability. Deferred parity (non-goals for the current wave, tracked canonically in [`docs/development/tracing/agent.md`](docs/development/tracing/agent.md) 「还未实现的功能」 with each item's handling + restart condition): the unstable `agent add`/`remove` `--local-dev`/`--force` flags stay unpublished (canonical `enable`/`disable` + their `add`/`remove` aliases only); provider-specific transcript compaction/reassemble traits (the writer already stores large transcripts as manifest-relative chunks, but no provider-specific compactor exists yet); the optional capability traits (`ProtectedFilesProvider`/`TranscriptCompactor`/`HookResponseWriter`/`RestoredSessionPathResolver`, …) beyond the landed `DeclaredAgentCaps` matrix; external-RPC method families beyond the v2 `info`/capability gate (undeclared capabilities stay fail-closed); and the non-first-batch roster — `gemini`/`cursor`/`copilot`/`factory-ai` stay `supported=false` (unsupported, not hook-installable, not launchable for review/investigate), vs the first batch `claude-code`/`codex`/`opencode`; `agent workspace list|show` (Part C W4) is the read-only keyset-paginated machine interface over the `workspace_record` registry (states provisioning/active/releasing/released/orphaned, lease fence/expiry, canonical path; schema v1; lease mutation never exposed). `agent bridge --stdio` (plan-20260818 LB-01) is the repository-scoped DeepSeek Harness JSON-RPC 2.0 NDJSON ingress — the ONLY standard inbound write transport for Harness, intentionally different from `libra code --control stdio` (a client controlling a live Code session) and from any MCP transport; it is not a Git command and does not alter `code --control` or hook-provider semantics. All 20 v1 methods are implemented as of v0.21.1: `diff.get` takes a closed `mode` (`worktree`/`staged`/`checkpoint`) plus validated repository-relative paths (never a free-form revision or pathspec) and forces `--no-ext-diff`/`--no-textconv`; `commit.create` commits only the current index and records provenance as association rows, not in the commit message; `checkpoint.restore` requires an explicit `expected_head` fence plus a clean index/worktree and never moves HEAD; `review.run` starts a supervised read-only review whose process groups are cancelled and drained when the bridge stops. A drifted fence is refused before any write with `LBR-AGENT-038` | +| agent | intentionally-different | Libra external-agent capture extension, not a Git command. OpenCode export on macOS uses seatbelt (`sandbox-exec`, deprecated / may be removed by Apple) to deny store-external writes and network; host **read is not confined** (unlike Linux bwrap default-deny). Missing `sandbox-exec` fails closed (metadata-only). Historical Claude/Codex/OpenCode sessions can be backfilled through the consented, current-repository-scoped `agent import`; typed redaction, coverage/import fences, local erase tombstones, atomic no-clobber loose-object publication, and doctor-visible crash markers make replays idempotent and fail closed. `agent graph ` is a read-only capture projection (session → turn → revision → subagent), distinct from the orchestrator-thread `libra graph`; its frozen JSON v1 uses a strict metadata whitelist, preserves shared checkpoint evidence, reports legacy captures as `unindexed`, and shows local tombstones as `erased` without resurrection. The interactive `agent graph` TUI entry was removed in the W5 breaking release (W5-08, shipped in v0.20.0): bare `libra agent graph ` fails with a usage error plus a migration hint, and the structured `libra --json agent graph` / `--machine` forms are unchanged. Claude `/subagents/*.jsonl` content is captured as independent source-revisioned `scope=subagent` checkpoints; hook boundaries remain distinct evidence, and association stays unresolved unless a provider-stable id matches exactly one boundary. The per-source import read cap consumes `agent.max_transcript_read_bytes` but never exceeds the 16 MiB adapter hard cap. `agent list --json` defaults to frozen schema v1; `--schema-version 2` opts into method availability. Deferred parity (non-goals for the current wave, tracked canonically in [`docs/development/tracing/agent.md`](docs/development/tracing/agent.md) 「还未实现的功能」 with each item's handling + restart condition): the unstable `agent add`/`remove` `--local-dev`/`--force` flags stay unpublished (canonical `enable`/`disable` + their `add`/`remove` aliases only); provider-specific transcript compaction/reassemble traits (the writer already stores large transcripts as manifest-relative chunks, but no provider-specific compactor exists yet); the optional capability traits (`ProtectedFilesProvider`/`TranscriptCompactor`/`HookResponseWriter`/`RestoredSessionPathResolver`, …) beyond the landed `DeclaredAgentCaps` matrix; external-RPC method families beyond the v2 `info`/capability gate (undeclared capabilities stay fail-closed); and the non-first-batch roster — `gemini`/`cursor`/`copilot`/`factory-ai` stay `supported=false` (unsupported, not hook-installable, not launchable for review/investigate), vs the first batch `claude-code`/`codex`/`opencode`; `agent workspace list|show` (Part C W4) is the read-only keyset-paginated machine interface over the `workspace_record` registry (states provisioning/active/releasing/released/orphaned, lease fence/expiry, canonical path; schema v1; lease mutation never exposed). `agent bridge --stdio` (plan-20260818 LB-01) is the repository-scoped DeepSeek Harness JSON-RPC 2.0 NDJSON ingress — the ONLY standard inbound write transport for Harness, intentionally different from `libra code --control stdio` (a client controlling a live Code session) and from any MCP transport; it is not a Git command and does not alter `code --control` or hook-provider semantics. Protocol v1.2 has 22 methods: the original 20 remain compatible, while additive `memory.recall` requires a process-active session, derives the DSH Agent principal server-side, applies the fixed Project Memory policy, and returns only after its receipt is durable; `context.get` remains a parameter-free workspace summary. Opt-in `memory.episode.record` generates a structured Episode from completed DSH turn evidence using Libra's compiler and writer; generation-enabled recall carries a turn number to freeze its starting code revision. `diff.get` takes a closed `mode` (`worktree`/`staged`/`checkpoint`) plus validated repository-relative paths (never a free-form revision or pathspec) and forces `--no-ext-diff`/`--no-textconv`; `commit.create` commits only the current index and records provenance as association rows, not in the commit message; `checkpoint.restore` requires an explicit `expected_head` fence plus a clean index/worktree and never moves HEAD; `review.run` starts a supervised read-only review whose process groups are cancelled and drained when the bridge stops. A drifted fence is refused before any write with `LBR-AGENT-038` | | login | intentionally-different | Libra host-scoped HTTP auth extension (session-token login), not a Git command | | logout | intentionally-different | Libra host-scoped HTTP auth extension (session-token logout), not a Git command | | whoami | intentionally-different | Libra host-scoped HTTP auth extension (session-token identity), not a Git command | | review | intentionally-different | Libra read-only agent review extension (AG-22), not a Git command: fans a review prompt out to external reviewer CLIs (claude-code/codex/opencode) in an isolated workspace and records run state, redacted logs, and untrusted findings under `.libra/sessions/agent-runs//`; `--fix` submits only a fixed trusted request through an active authorized Code runtime and the existing serialized queue/approval/sandbox/tool gates, never external findings; no authorized runtime still fails closed with `LBR-AGENT-010`, and raw untrusted seed input with `LBR-AGENT-011`; `--checkpoint ` scopes the review to a captured agent checkpoint (PD-02) — the reviewers' workspace is the checkpoint's own content materialized read-only inside the run directory (`checkpoint-input/`, no worktree snapshot at all), missing/non-materializable checkpoints fail closed before any run exists, and the materialization shares the run's retention/doctor surface | | investigate | intentionally-different | Libra read-only agent investigate extension (AG-23), not a Git command: drives a STRICT round-robin investigation (one investigator at a time, in agent order) of external CLIs (claude-code/codex/opencode) in an isolated workspace; persists round-robin run state (`turn`/`next_agent_idx`/`stances`/`pending_turn`/`quorum`), single-writer `findings.md`, and redacted logs under `.libra/sessions/agent-runs//`; runs reach `quorum`/`max_turns`/`cancelled`/`timeout` or PAUSE (`stalled`/`agent_failure`) resumable via `continue`; the topic is an untrusted seed (redacted + spotlit before prompt injection); an OS run-lock makes concurrent `continue` on the same run fail closed; `fix` validates the run and submits only a fixed trusted request through the same controlled runtime helper as `review --fix`, never the run id/topic/stances/findings/attachments; no authorized runtime still fails closed with `LBR-AGENT-010`, and raw untrusted seed input with `LBR-AGENT-011`; `start --checkpoint ` scopes the investigation to a captured agent checkpoint (PD-02) — the investigators' workspace is the checkpoint's own content materialized read-only inside the run directory (`checkpoint-input/`, no worktree snapshot at all), the scope persists in run state so `continue` re-materializes the same checkpoint, missing/non-materializable checkpoints fail closed before any run exists, and the materialization shares the run's retention/doctor surface | | maintenance | partial | `run` / `register` / `unregister` / `status` / `start` / `stop` exposed; `start`/`stop` install/remove an OS scheduler entry (launchd LaunchAgents plist on macOS, cron fragment elsewhere; dir overridable via `LIBRA_MAINTENANCE_AGENT_DIR`); `gc` recursively traces SQLite refs/reflogs (both endpoints and annotated-tag targets), every index stage, every file-backed stash reflog entry, and held merge/rebase autostash sidecars before loose-object deletion, failing closed on malformed/unreadable roots or reachable objects; the reachability walk enumerates EVERY worktree's private index (all stages), each scope's sequencer/rebase/bisect rows, and each gitdir's held autostash and merge/revert/rebase-aux sidecars, so loose-object pruning is enabled in repositories with linked worktrees (the earlier blanket skip is lifted); a shallow clone's `.libra/shallow` entries are traversal **boundaries** — the boundary commit is kept and its absent parents are not demanded — and unreadable roots, unparseable shallow metadata, or a live `alternates` borrower all fail closed before anything is deleted; the `commit-graph` task writes a Git-compatible v1 commit-graph file (OIDF/OIDL/CDAT chunks + topological generation numbers; octopus merges are written via the EDGE chunk, and SHA-256 repositories via 32-byte OIDs + a SHA-256 header version/trailer); the `prefetch` task fetches all configured remotes via the normal fetch path (refreshing standard remote-tracking refs rather than Git's `refs/prefetch/` namespace — intentionally different; skipped when no remotes are configured) | +| memory | intentionally-different | Libra repository-local Agent development-history Memory extension, not a Git command. `search` uses structured filters plus SQLite FTS5/BM25 and current-code applicability; `show` reads one current or historical Episode with optional authorized evidence expansion; `status` reports ref/projection/job/FTS diagnostics without content; `rebuild --dry-run` validates replay with zero writes and `rebuild` replaces only the rebuildable Repo-scope projection. Manual remember/delete/update/revert, MCP, team sync, and cross-repository Memory are not exposed. | | hooks | intentionally-different | Repository hooks use `.libra/hooks`, never `.git/hooks` or `core.hooksPath`. Supported lifecycle: `pre-commit`, `prepare-commit-msg`, `commit-msg`, `post-commit`, `post-checkout`, `pre-rebase`, `pre-merge-commit`, `post-merge`, and `post-rewrite`; automatic merge commits run the message/post-commit hooks, and pull shares its selected merge/rebase lifecycle. Canonical extensionless names win over `.sh` (Unix) / `.ps1` (Windows), with symlinks and non-executable Unix files rejected fail-closed. Hooks run from a private copy in the required workspace-only, network-denied sandbox; repository metadata is protected except the commit message file for message hooks, and caller environment secrets are removed via an explicit process/locale allowlist. Blocking pre/message hooks abort before the associated history mutation; post hooks are advisory; quiet/JSON/machine suppress captured hook output. `commit --no-verify`, `merge --no-verify`, and `LIBRA_NO_HOOKS=1` are explicit policy bypasses. `.git/hooks` bridge remains deferred under D3. Customized Windows hooks currently fail closed because the restricted-token backend is not implemented; the exact shipped no-op template is skipped. See [repository hooks](docs/commands/repository-hooks.md). | | archive | partial | Creates tar/tar.gz/tar.bz2/zip archives from a committed tree; `--format`, `--output`, `--prefix`, `--list`, `-v`/`--verbose` (lists archived paths on stderr), `--add-file=` (inject an untracked working-tree file at its basename under the prefix, bypassing the pathspec filter; repeatable; nothing is written to the object store), `--compression-level <0-9>` (compression level for tar.gz/tar.bz2/zip — Git's `-0`..`-9`, which clap cannot model as bare numeric flags; plain tar ignores it, and bzip2 has no level 0 so 0 is treated as 1), `--mtime