From 8cbc4434abacef1cd382154f42a9615cf03ed83e Mon Sep 17 00:00:00 2001 From: Eli Ma Date: Sat, 4 Jul 2026 20:06:13 +0800 Subject: [PATCH] chore: update release metadata and repository links Signed-off-by: Eli Ma --- AGENTS.md | 4 +- COMPATIBILITY.md | 2 +- Cargo.lock | 148 +++++----- Cargo.toml | 12 +- docs/code-of-conduct.md | 20 +- docs/commands/open.md | 12 +- docs/commands/package.md | 2 +- docs/commands/zh-CN/open.md | 12 +- docs/development/account.md | 2 +- docs/development/cli-error-contract-design.md | 8 +- docs/development/commands/_general.md | 6 +- docs/development/commands/agent.md | 9 + docs/development/commands/code.md | 9 + docs/development/commands/commit-tree.md | 2 +- docs/development/gap/agent-trace.md | 4 +- docs/development/gap/grit-gap.md | 2 +- .../internal/code-agent-runtime.md | 50 ++-- docs/development/tracing/memory.md | 8 +- docs/development/tracing/plan.md | 22 +- install.sh | 12 +- .../2026053101_ai_final_decision.sql | 2 +- src/cli.rs | 268 ++++++++++-------- src/command/clone.rs | 2 +- src/command/commit.rs | 2 +- src/command/index_pack_support.rs | 2 +- src/command/init.rs | 2 +- src/command/open.rs | 14 +- src/command/publish.rs | 2 +- src/command/push.rs | 2 +- src/command/reset.rs | 54 +++- src/command/restore.rs | 220 ++++++++++---- src/command/stash.rs | 2 +- src/command/tag.rs | 40 ++- src/internal/ai/agent_run/event.rs | 2 +- src/internal/ai/agent_run/event_store.rs | 2 +- src/internal/ai/agent_run/mod.rs | 2 +- .../ai/agent_run/workspace_strategy.rs | 2 +- src/internal/ai/capability_package/diff.rs | 2 +- .../ai/capability_package/manifest.rs | 2 +- src/internal/ai/capability_package/mod.rs | 2 +- src/internal/ai/mcp/resource.rs | 4 +- src/internal/ai/mcp/server.rs | 2 +- src/internal/ai/runtime/event.rs | 2 +- src/internal/ai/runtime/phase0.rs | 2 +- src/internal/ai/runtime/phase4.rs | 2 +- src/internal/ai/runtime/revision.rs | 2 +- src/internal/ai/skills/embedded/libra.md | 2 +- src/internal/ai/tools/handlers/web_search.rs | 2 +- src/internal/db.rs | 2 +- src/internal/db/migration.rs | 4 +- src/lib.rs | 2 +- src/utils/error.rs | 12 +- src/utils/lfs.rs | 12 +- src/utils/thin_pack.rs | 2 +- src/utils/util.rs | 4 +- template/skills/libra.md | 2 +- tests/INDEX.md | 6 +- tests/ai_provider_transform_test.rs | 2 +- tests/ai_subagent_flag_off_regression_test.rs | 2 +- tests/code_codex_default_tui_test.rs | 2 +- tests/command/case_handling_test.rs | 14 + tests/command/cli_error_test.rs | 12 +- tests/command/config_test.rs | 109 +++---- tests/command/open_test.rs | 28 +- tests/command/remote_test.rs | 41 ++- tests/command/worktree_test.rs | 211 +++++++------- tests/compat/README.md | 2 +- tests/compat/agent_docs_contract.rs | 6 +- tests/compat/matrix_alignment.rs | 10 +- tests/compat/matrix_alignment_support.rs | 4 +- tests/network_remotes_test.rs | 15 +- tests/operation_wrapper_test.rs | 4 +- tools/integration-runner/README.md | 2 +- tools/integration-runner/src/plan.rs | 2 +- web/package.json | 2 +- worker/package.json | 2 +- 76 files changed, 870 insertions(+), 636 deletions(-) create mode 100644 docs/development/commands/agent.md create mode 100644 docs/development/commands/code.md diff --git a/AGENTS.md b/AGENTS.md index 6f0eac620..9f56887d1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -91,7 +91,7 @@ Do not dismiss an issue only because: - `src/utils/` covers shared utilities: `client_storage.rs` (tiered local + S3/R2 + LRU), `d1_client.rs`, path/object/tree helpers, `ignore.rs`, `lfs.rs`, `fuse.rs`, `convert.rs`, `error.rs`, `output.rs`, `pager.rs`, `text.rs`, `worktree.rs`, `storage/`, `storage_ext.rs`, and `test.rs` (`ChangeDirGuard`, `setup_with_new_libra_in`). - `tests/` holds integration targets at the top level plus `tests/command/` for per-subcommand suites. `tests/INDEX.md` is the authoritative one-line index of every cargo `--test` target, grouped by Wave (1 command/compat, 2 Code UI & local automation, 3 network, 4 live AI, 5 live cloud, 6 perf smoke); keep it in sync when adding/renaming a test target. Shared helpers live in `tests/command/mod.rs`, `tests/helpers/`, and `tests/harness/`; fixtures live in `tests/data/` and `tests/fixtures/`; `tests/objects/` covers object-level tests; `tests/compat/` covers cross-command compatibility guards. - `web/` is the Next.js static export embedded into `WebAssets` by `build.rs` and skipped when `LIBRA_SKIP_WEB_BUILD=1` is set. `worker/` holds the Cloudflare Worker (D1 + R2) backing `libra publish` and cloud backup. -- Community docs live in `docs/` (including `docs/development/integration-test-plan.md` and the command development notes under `docs/development/commands/`); SQLite bootstrap lives in `sql/sqlite_20260309_init.sql` plus `sql/sqlite_20260415_ai_runtime_contract.sql`; runtime migrations live in `sql/migrations/`; publish-pipeline schema lives in `sql/publish/`; hooks/templates live in `template/`; release/install assets live in `install.sh`. +- Community docs live in `docs/` (including `docs/development/integration/integration-test-plan.md` and the command development notes under `docs/development/commands/`); SQLite bootstrap lives in `sql/sqlite_20260309_init.sql` plus `sql/sqlite_20260415_ai_runtime_contract.sql`; runtime migrations live in `sql/migrations/`; publish-pipeline schema lives in `sql/publish/`; hooks/templates live in `template/`; release/install assets live in `install.sh`. ## Build, Test, and Development Commands - `cargo +nightly fmt --all` then `cargo clippy --all-targets --all-features -- -D warnings` keep formatting and linting aligned (`rustfmt.toml` sets `group_imports = "StdExternalCrate"` and `imports_granularity = "Crate"`). **CI enforces `-D warnings`; all clippy warnings must be resolved before committing.** @@ -103,7 +103,7 @@ Do not dismiss an issue only because: - `--features test-live-cloud` for Wave 5 (real D1/R2; needs `LIBRA_D1_*`/`LIBRA_STORAGE_*`). - `--features test-provider` plus `LIBRA_ENABLE_TEST_PROVIDER=1` to activate the deterministic provider used by `code_ui_scenarios`, `harness_self_test`, `code_codex_default_tui_test`, `code_ui_remote_lease_matrix`, and `code_ui_remote_sse_matrix` (run with `--test-threads=1`). - `--features worktree-fuse` for Unix FUSE-backed worktree commands. - - `--features subagent-scaffold` for the gated CEX-S2-10 schema scaffold (see `docs/development/commands/agent.md`). + - `--features subagent-scaffold` for the gated CEX-S2-10 schema scaffold (see `docs/development/tracing/agent.md`). ## Coding Style & Naming Conventions - Rust 2024; 4-space indent; snake_case for modules/functions, PascalCase for types, SCREAMING_SNAKE for consts. diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index bea91a3c3..6fe08a570 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -135,7 +135,7 @@ batch document. ## Hooks - Stock Git hooks at `.git/hooks` / `core.hooksPath`: `unsupported` (see [docs/development/commands/_compatibility.md#d3-git-hooks-bridge-作为核心特性](docs/development/commands/_compatibility.md#d3-git-hooks-bridge-作为核心特性)) -- AI provider hooks: `intentionally-different` (see [docs/development/commands/agent.md](docs/development/commands/agent.md)) +- AI provider hooks: `intentionally-different` (see [docs/development/tracing/agent.md](docs/development/tracing/agent.md)) ## LFS compatibility notes diff --git a/Cargo.lock b/Cargo.lock index af225365f..f16ac5e64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -772,13 +772,13 @@ dependencies = [ [[package]] name = "bstr" -version = "1.12.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79" dependencies = [ "memchr", "regex-automata", - "serde", + "serde_core", ] [[package]] @@ -889,9 +889,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] name = "bzip2" @@ -1028,9 +1028,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", @@ -1596,9 +1596,9 @@ dependencies = [ [[package]] name = "dashmap" -version = "6.1.0" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" dependencies = [ "cfg-if", "crossbeam-utils", @@ -1892,7 +1892,7 @@ dependencies = [ "num-traits", "pkcs8", "rfc6979", - "sha2", + "sha2 0.10.9", "signature", "zeroize", ] @@ -1956,7 +1956,7 @@ dependencies = [ "ed25519", "rand_core 0.6.4", "serde", - "sha2", + "sha2 0.10.9", "subtle", "zeroize", ] @@ -2228,7 +2228,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", - "libz-sys", "miniz_oxide", "zlib-rs", ] @@ -2486,9 +2485,9 @@ dependencies = [ [[package]] name = "git-internal" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514e1c3227f3ade4a4ea2fadcf80936b034cf75ec52e9deabea34594804622c9" +checksum = "434bbb06c8bb92d21b58a61d775fed5e8a4afd65ef9b0e8d5ffa36338ef96d6e" dependencies = [ "ahash 0.8.12", "async-trait", @@ -2514,13 +2513,13 @@ dependencies = [ "path-absolutize", "rayon", "ring", - "rkyv 0.8.15", + "rkyv 0.8.17", "sea-orm", "serde", "serde_json", - "sha1 0.10.6", - "sha2", - "similar", + "sha1 0.11.0", + "sha2 0.11.0", + "similar 3.1.1", "tempfile", "thiserror 2.0.18", "threadpool", @@ -2628,6 +2627,12 @@ dependencies = [ "foldhash 0.2.0", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + [[package]] name = "hashlink" version = "0.10.0" @@ -3276,7 +3281,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "once_cell", - "sha2", + "sha2 0.10.9", "signature", ] @@ -3355,9 +3360,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libdbus-sys" @@ -3418,7 +3423,7 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libra" -version = "0.18.0" +version = "0.18.1" dependencies = [ "anyhow", "assert_cmd", @@ -3488,9 +3493,9 @@ dependencies = [ "serde_json", "serial_test", "sha1 0.11.0", - "sha2", + "sha2 0.10.9", "shlex", - "similar", + "similar 2.7.0", "tar", "tempfile", "testcontainers", @@ -3612,17 +3617,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "libz-sys" -version = "1.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52f4c29e2a68ac30c9087e1b772dc9f44a2b66ed44edf2266cf2be9b03dafc1" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - [[package]] name = "line-clipping" version = "0.3.5" @@ -3739,9 +3733,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "memmap2" @@ -4354,7 +4348,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -4366,7 +4360,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -4380,7 +4374,7 @@ dependencies = [ "elliptic-curve", "primeorder", "rand_core 0.6.4", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -4508,7 +4502,7 @@ dependencies = [ "digest 0.10.7", "hmac", "password-hash 0.4.2", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -4576,7 +4570,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" dependencies = [ "pest", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -4638,7 +4632,7 @@ dependencies = [ "rsa", "sha1 0.10.6", "sha1-checked", - "sha2", + "sha2 0.10.9", "sha3", "signature", "smallvec", @@ -5331,9 +5325,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -5657,19 +5651,19 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.8.15" +version = "0.8.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a30e631b7f4a03dee9056b8ef6982e8ba371dd5bedb74d3ec86df4499132c70" +checksum = "815cc8a37159a463064825246cadb07961e25cd9885908606f6d08a98d8f8874" dependencies = [ "bytecheck 0.8.2", "bytes", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "indexmap 2.13.0", "munge", "ptr_meta 0.3.1", "rancor", "rend 0.5.3", - "rkyv_derive 0.8.15", + "rkyv_derive 0.8.17", "tinyvec", "uuid", ] @@ -5687,9 +5681,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.8.15" +version = "0.8.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8100bb34c0a1d0f907143db3149e6b4eea3c33b9ee8b189720168e818303986f" +checksum = "c0ed1a78a1b19d184b0daa629dd9a024573173ec7d485b287cb369fb3607cc1c" dependencies = [ "proc-macro2", "quote", @@ -5765,7 +5759,7 @@ dependencies = [ "pkcs1", "pkcs8", "rand_core 0.6.4", - "sha2", + "sha2 0.10.9", "signature", "spki", "subtle", @@ -5814,7 +5808,7 @@ checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" dependencies = [ "globset", "mime_guess", - "sha2", + "sha2 0.10.9", "walkdir", ] @@ -6276,9 +6270,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "indexmap 2.13.0", "itoa", @@ -6481,6 +6475,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.2", +] + [[package]] name = "sha3" version = "0.10.8" @@ -6581,6 +6586,15 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" +[[package]] +name = "similar" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6505efef05804732ed8a3f2d4f279429eb485bd69d5b0cc6b19cc02005cda16" +dependencies = [ + "bstr", +] + [[package]] name = "siphasher" version = "1.0.2" @@ -6694,7 +6708,7 @@ dependencies = [ "rustls", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "smallvec", "thiserror 2.0.18", "time", @@ -6734,7 +6748,7 @@ dependencies = [ "quote", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "sqlx-core", "sqlx-mysql", "sqlx-postgres", @@ -6780,7 +6794,7 @@ dependencies = [ "rust_decimal", "serde", "sha1 0.10.6", - "sha2", + "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", @@ -6823,7 +6837,7 @@ dependencies = [ "rust_decimal", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", @@ -6892,7 +6906,7 @@ checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15" dependencies = [ "base64ct", "pem-rfc7468", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -6909,7 +6923,7 @@ dependencies = [ "rand_core 0.6.4", "rsa", "sec1", - "sha2", + "sha2 0.10.9", "signature", "ssh-cipher", "ssh-encoding", @@ -7205,7 +7219,7 @@ dependencies = [ "pest", "pest_derive", "phf", - "sha2", + "sha2 0.10.9", "signal-hook", "siphasher", "terminfo", @@ -7372,9 +7386,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -8058,9 +8072,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.1" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" dependencies = [ "atomic", "getrandom 0.4.2", @@ -8385,7 +8399,7 @@ checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" dependencies = [ "getrandom 0.3.4", "mac_address", - "sha2", + "sha2 0.10.9", "thiserror 1.0.69", "uuid", ] diff --git a/Cargo.toml b/Cargo.toml index cecb2ad1a..a43d7d7dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "libra" -version = "0.18.0" +version = "0.18.1" edition = "2024" license = "MIT" description = "AI agent-native version control system with Git on-disk compatibility, SQLite-backed metadata, and tiered cloud storage" -repository = "https://github.com/web3infra-foundation/libra" -homepage = "https://github.com/web3infra-foundation/libra" -authors = ["Web3 Infrastructure Foundation"] +repository = "https://github.com/libra-tools/libra" +homepage = "https://github.com/libra-tools/libra" +authors = ["Libra Tools"] keywords = ["git", "version-control", "ai", "agent", "vcs"] categories = ["command-line-utilities", "development-tools"] readme = "README.md" @@ -19,13 +19,13 @@ test-live-ai = [] # L3: tests calling real LLM APIs test-live-cloud = [] # L3: tests hitting real D1/R2 endpoints test-provider = [] # deterministic hidden provider for local TUI automation tests fastcdc = [] # lore.md §6: default-OFF FastCDC LFS media chunking client substrate (no new deps; reuses ring sha256; server protocol frozen) -subagent-scaffold = [] # CEX-S2-10 schema-only scaffold (Step 2 sub-agent contracts; gated on CP-4 in production — see docs/development/commands/agent.md "Step 2 audit closure") +subagent-scaffold = [] # CEX-S2-10 schema-only scaffold (Step 2 sub-agent contracts; gated on CP-4 in production — see docs/development/tracing/agent.md "Step 2 audit closure") otlp = ["dep:opentelemetry", "dep:opentelemetry_sdk", "dep:opentelemetry-otlp", "dep:tracing-opentelemetry"] # lore.md 1.7: OTLP trace export (default binary unaffected) keyring = ["dep:keyring"] # lore.md 2.7: OS-keyring auth backend (release builds enable it; default dev builds unaffected) [dependencies] uuid = { version = "1.23.1", features = ["v4", "v5", "v7"] } -git-internal = "0.7.6" +git-internal = "0.8.1" anyhow = "1.0.102" byte-unit = "5.2.0" byteorder = "1.5.0" diff --git a/docs/code-of-conduct.md b/docs/code-of-conduct.md index d483aca84..f406e1b99 100644 --- a/docs/code-of-conduct.md +++ b/docs/code-of-conduct.md @@ -1,10 +1,10 @@ # CODE OF CONDUCT -This code of conduct outlines the expected behavior of all members of Web3 Infrastructure Foundation to ensure a safe, productive, and inclusive environment for everyone. +This code of conduct outlines the expected behavior of all members of Libra Tools to ensure a safe, productive, and inclusive environment for everyone. -All members of Web3 Infrastructure Foundation, including employees, contractors, interns, volunteers, and anyone else represents the company, are expected to behave in a professional, respectful, considerate, and collaborative manner. Harassment, discrimination, or toxic behavior of any kind will not be tolerated. +All members of Libra Tools, including employees, contractors, interns, volunteers, and anyone else represents the company, are expected to behave in a professional, respectful, considerate, and collaborative manner. Harassment, discrimination, or toxic behavior of any kind will not be tolerated. -Web3 Infrastructure Foundation is committed to providing an environment free of harassment and discrimination for everyone, regardless of gender, gender identity and expression, sexual orientation, disability, physical appearance, body size, race, age, or religion. We do not tolerate harassment of participants in any form. Harassment includes offensive comments related to these characteristics, as well as deliberate intimidation, stalking, following, harassing photography or recording, sustained disruption of talks or other events, inappropriate physical contact, and unwelcome sexual attention. +Libra Tools is committed to providing an environment free of harassment and discrimination for everyone, regardless of gender, gender identity and expression, sexual orientation, disability, physical appearance, body size, race, age, or religion. We do not tolerate harassment of participants in any form. Harassment includes offensive comments related to these characteristics, as well as deliberate intimidation, stalking, following, harassing photography or recording, sustained disruption of talks or other events, inappropriate physical contact, and unwelcome sexual attention. If you experience or witness unacceptable behavior, see something that makes you feel unsafe, or have concerns about the well-being of a participant, please report it to Eli Ma or Charles Feng immediately. All reports will be handled confidentially. @@ -14,7 +14,7 @@ THANK YOU FOR YOUR COOPERATION IN ADVANCING OUR COMMITMENT TO INCLUSION AND RESP ## Responsibilities -All members of Web3 Infrastructure Foundation are expected to: +All members of Libra Tools are expected to: - Treat all people with respect and consideration, valuing a diversity of views and opinions. • Communicate openly and thoughtfully. @@ -23,12 +23,12 @@ All members of Web3 Infrastructure Foundation are expected to: • Respect personal space and property. • Refrain from demeaning, discriminatory, or harassing behavior, speech, and imagery. • Be considerate in your use of space and resources. For example, avoid excessive noise from conversations, laptops, and other electronic devices. Be courteous when taking up shared space such as tables and walkways. - • Follow the instructions of Web3 Infrastructure Foundation staff and security. + • Follow the instructions of Libra Tools staff and security. • Avoid using language that reinforces social and cultural structures of domination related to gender, gender identity and expression, sexual orientation, disability, physical appearance, body size, race, age, religion, or other personal characteristics. ## Consequences -Failure to comply with this Code of Conduct may result in disciplinary action, including removal from Web3 Infrastructure Foundation spaces and events and prohibition from future participation. +Failure to comply with this Code of Conduct may result in disciplinary action, including removal from Libra Tools spaces and events and prohibition from future participation. ## Contact Information @@ -36,10 +36,10 @@ If you have questions or concerns about this Code of Conduct, contact Eli Ma or ## Enforcement -Web3 Infrastructure Foundation prioritizes creating a safe and positive experience for everyone. We do not tolerate harassment or discrimination of any kind. +Libra Tools prioritizes creating a safe and positive experience for everyone. We do not tolerate harassment or discrimination of any kind. -We expect participants to follow these rules at all Web3 Infrastructure Foundation venues and events. Web3 Infrastructure Foundation staff will enforce this Code of Conduct. +We expect participants to follow these rules at all Libra Tools venues and events. Libra Tools staff will enforce this Code of Conduct. -If a participant engages in harassing or discriminatory behavior, Web3 Infrastructure Foundation staff will take reasonable action they deem appropriate, including warning the offender, expulsion from an event, or banning them from future events. +If a participant engages in harassing or discriminatory behavior, Libra Tools staff will take reasonable action they deem appropriate, including warning the offender, expulsion from an event, or banning them from future events. -At their discretion, Web3 Infrastructure Foundation staff may report offenders to local law enforcement. Web3 Infrastructure Foundation staff may take action against participants for other behaviors that violate this Code of Conduct or negatively impact the safety and inclusion of event participants. \ No newline at end of file +At their discretion, Libra Tools staff may report offenders to local law enforcement. Libra Tools staff may take action against participants for other behaviors that violate this Code of Conduct or negatively impact the safety and inclusion of event participants. \ No newline at end of file diff --git a/docs/commands/open.md b/docs/commands/open.md index 7999d05bf..28c2a5c71 100644 --- a/docs/commands/open.md +++ b/docs/commands/open.md @@ -41,14 +41,14 @@ On macOS the command uses `open`, on Linux `xdg-open`, and on Windows `cmd /C st ```bash libra open libra open origin -libra open https://github.com/web3infra-foundation/libra +libra open https://github.com/libra-tools/libra libra open --json ``` ## Human Output ```text -Opening https://github.com/web3infra-foundation/libra +Opening https://github.com/libra-tools/libra ``` `--quiet` suppresses `stdout`. @@ -61,8 +61,8 @@ Opening https://github.com/web3infra-foundation/libra "command": "open", "data": { "remote": "origin", - "remote_url": "git@github.com:web3infra-foundation/libra.git", - "web_url": "https://github.com/web3infra-foundation/libra", + "remote_url": "git@github.com:libra-tools/libra.git", + "web_url": "https://github.com/libra-tools/libra", "launched": false } } @@ -76,8 +76,8 @@ When the argument is a direct URL instead of a remote name, `remote` is `null`: "command": "open", "data": { "remote": null, - "remote_url": "https://github.com/web3infra-foundation/libra", - "web_url": "https://github.com/web3infra-foundation/libra", + "remote_url": "https://github.com/libra-tools/libra", + "web_url": "https://github.com/libra-tools/libra", "launched": false } } diff --git a/docs/commands/package.md b/docs/commands/package.md index 9c1baed00..bb90e86a7 100644 --- a/docs/commands/package.md +++ b/docs/commands/package.md @@ -80,5 +80,5 @@ capabilities (overlap-safe) disappear at the next session start. - [`COMPATIBILITY.md`](../../COMPATIBILITY.md) — `package` is a Libra-only extension (no Git equivalent). -- `docs/development/commands/agent.md` Step 2.7 (CEX-S2-17) — the capability-package / +- `docs/development/tracing/agent.md` Step 2.7 (CEX-S2-17) — the capability-package / plugin-trust design. diff --git a/docs/commands/zh-CN/open.md b/docs/commands/zh-CN/open.md index f1512520d..d3e2c1c52 100644 --- a/docs/commands/zh-CN/open.md +++ b/docs/commands/zh-CN/open.md @@ -35,14 +35,14 @@ libra open [] ```bash libra open libra open origin -libra open https://github.com/web3infra-foundation/libra +libra open https://github.com/libra-tools/libra libra open --json ``` ## 人类可读输出 ```text -Opening https://github.com/web3infra-foundation/libra +Opening https://github.com/libra-tools/libra ``` `--quiet` 会抑制 `stdout`。 @@ -55,8 +55,8 @@ Opening https://github.com/web3infra-foundation/libra "command": "open", "data": { "remote": "origin", - "remote_url": "git@github.com:web3infra-foundation/libra.git", - "web_url": "https://github.com/web3infra-foundation/libra", + "remote_url": "git@github.com:libra-tools/libra.git", + "web_url": "https://github.com/libra-tools/libra", "launched": false } } @@ -70,8 +70,8 @@ Opening https://github.com/web3infra-foundation/libra "command": "open", "data": { "remote": null, - "remote_url": "https://github.com/web3infra-foundation/libra", - "web_url": "https://github.com/web3infra-foundation/libra", + "remote_url": "https://github.com/libra-tools/libra", + "web_url": "https://github.com/libra-tools/libra", "launched": false } } diff --git a/docs/development/account.md b/docs/development/account.md index 2e2802329..901233b72 100644 --- a/docs/development/account.md +++ b/docs/development/account.md @@ -278,7 +278,7 @@ Libra 当前有两套秘密存储,账号登录态必须走 **global** 路径 - `save_token()` / `load_token()` / `delete_token()` 必须显式打开 **global** DB(`global_config_path()`),不得经 `read_cascaded_config_value` 落到 repo-local。 - 在任意 repo 目录内执行 `libra login` 时,登录态仍只进 `~/.libra/config.db`;`cwd` 下的 `.libra/libra.db` 不得出现 `vault.account.*` 键(集成测试必须断言)。 -- 测试必须使用隔离 `LIBRA_CONFIG_GLOBAL_DB` + `LIBRA_TEST_HOME`(与 `docs/development/integration-test-plan.md` §3 一致),不得读写开发者真实 `~/.libra/config.db`。 +- 测试必须使用隔离 `LIBRA_CONFIG_GLOBAL_DB` + `LIBRA_TEST_HOME`(与 `docs/development/integration/integration-test-plan.md` §3 一致),不得读写开发者真实 `~/.libra/config.db`。 建议键名: diff --git a/docs/development/cli-error-contract-design.md b/docs/development/cli-error-contract-design.md index 306ba91be..0cc22539e 100644 --- a/docs/development/cli-error-contract-design.md +++ b/docs/development/cli-error-contract-design.md @@ -1,7 +1,7 @@ # Libra CLI Error Contract Design -This document consolidates the design intent from RFC [#301](https://github.com/web3infra-foundation/libra/issues/301) -and the initial implementation scope from [#302](https://github.com/web3infra-foundation/libra/issues/302). +This document consolidates the design intent from RFC [#301](https://github.com/libra-tools/libra/issues/301) +and the initial implementation scope from [#302](https://github.com/libra-tools/libra/issues/302). It is a development-facing design note. The user-facing reference remains [`docs/error-codes.md`](../error-codes.md). @@ -331,7 +331,7 @@ These questions do not block the base contract introduced here. ## Canonical References -- RFC: [#301](https://github.com/web3infra-foundation/libra/issues/301) -- Initial implementation: [#302](https://github.com/web3infra-foundation/libra/issues/302) +- RFC: [#301](https://github.com/libra-tools/libra/issues/301) +- Initial implementation: [#302](https://github.com/libra-tools/libra/issues/302) - User-facing error-code reference: [`docs/error-codes.md`](../error-codes.md) - Shared implementation entrypoint: [`src/utils/error.rs`](../../src/utils/error.rs) diff --git a/docs/development/commands/_general.md b/docs/development/commands/_general.md index 224257d3c..1726dc908 100644 --- a/docs/development/commands/_general.md +++ b/docs/development/commands/_general.md @@ -6,7 +6,7 @@ ## 对比 Git 与兼容性 -- Git 兼容命令以 `COMPATIBILITY.md` 为用户承诺,以 `docs/development/commands/.md` 记录实现细节和未完成项,并以 `docs/development/integration-test-plan.md` / `docs/development/integration-scenarios.yaml` 作为集成验证方案的事实源。 +- Git 兼容命令以 `COMPATIBILITY.md` 为用户承诺,以 `docs/development/commands/.md` 记录实现细节和未完成项,并以 `docs/development/integration/integration-test-plan.md` / `docs/development/integration-scenarios.yaml` 作为集成验证方案的事实源。 - Libra 扩展命令如 `code`、`agent`、`cloud`、`publish`、`usage`、`sandbox` 不追求 Git 同形,必须解释差异和替代工作流。 - 全局参数 `--json`、`--machine`、`--no-pager`、`--color`、`--progress`、`--quiet`、`--exit-code-on-warning` 是 Agent 驱动 CLI 的基础契约。 - 全局耐久性参数 `--sync-data`(`lore.md` §0.5):对本地对象写强制 fsync(临时文件与父目录)换取抗断电耐久性,代价是写吞吐;recovery-critical 的 sequencer 状态恒 fsync 不受此开关影响。等价于 `LIBRA_SYNC_DATA=1`,经 `utils::atomic_write` 收口。 @@ -17,7 +17,7 @@ - 入口与分发:本总览不对应单个 CLI 子命令;它以 `src/cli.rs::Commands` 作为公开命令入口事实源,以 `src/command/mod.rs` 作为命令模块导出事实源。 - 源码分层:文档结构固定维护六段内容:实现目标、Git 兼容性、设计方案、实现历史、当前状态、未完成项;`README.md` 只作为索引,不承载单命令设计。 -- 执行路径:维护者新增或修改命令文档时,先核对 `src/cli.rs`、`src/command/.rs` 和必要的子模块,再把入口、参数类型、输出/错误类型、执行流程和测试证据写回对应文档;若修改的是 Git 兼容命令,还必须同步 `COMPATIBILITY.md`、`docs/development/integration-test-plan.md`、`docs/development/integration-scenarios.yaml` 和对应集成测试证据,保证命令文档、用户可见承诺和集成测试方案一致。 +- 执行路径:维护者新增或修改命令文档时,先核对 `src/cli.rs`、`src/command/.rs` 和必要的子模块,再把入口、参数类型、输出/错误类型、执行流程和测试证据写回对应文档;若修改的是 Git 兼容命令,还必须同步 `COMPATIBILITY.md`、`docs/development/integration/integration-test-plan.md`、`docs/development/integration-scenarios.yaml` 和对应集成测试证据,保证命令文档、用户可见承诺和集成测试方案一致。 - 流程图:以下流程图展示跨命令文档治理的事实源、写入位置和验证闭环。 @@ -65,5 +65,5 @@ flowchart TD - 本文件是改进命令的强制前置规范;改进任何命令前必须先阅读并遵循 [docs/development/commands/_general.md](_general.md)。 - 新增命令:先补 `src/cli.rs`、`src/command/mod.rs`、实现模块、用户文档、开发文档、`COMPATIBILITY.md` 和测试。 - 新增参数:必须说明 Git 对齐情况、JSON/机器输出影响、错误码和回归测试。 -- 修改 Git 兼容命令时,必须把新增、删除或语义变化的场景加入 `docs/development/integration-test-plan.md` 和 `docs/development/integration-scenarios.yaml`,并保持对应 runner、Wave、执行命令和验收证据一致。 +- 修改 Git 兼容命令时,必须把新增、删除或语义变化的场景加入 `docs/development/integration/integration-test-plan.md` 和 `docs/development/integration-scenarios.yaml`,并保持对应 runner、Wave、执行命令和验收证据一致。 - 移除或延后能力:必须在命令文档和 `_compatibility.md` 中明确用户影响和重启条件。 diff --git a/docs/development/commands/agent.md b/docs/development/commands/agent.md new file mode 100644 index 000000000..a8fef0efa --- /dev/null +++ b/docs/development/commands/agent.md @@ -0,0 +1,9 @@ +# Agent Command Development + +`libra agent` is an intentionally different external-agent capture extension, +not a Git-compatible command. + +The active development contract, backlog, and compatibility guardrails live in +[`../tracing/agent.md`](../tracing/agent.md). Keep this file as the command +development index entry so `docs/development/commands/README.md` can list every +public CLI command without duplicating the Agent planning document. diff --git a/docs/development/commands/code.md b/docs/development/commands/code.md new file mode 100644 index 000000000..4af7b4a4d --- /dev/null +++ b/docs/development/commands/code.md @@ -0,0 +1,9 @@ +# Code Command Development + +`libra code` is an intentionally different Libra AI extension, not a +Git-compatible command. + +The active development contract, backlog, and compatibility guardrails live in +[`../tracing/code.md`](../tracing/code.md). Keep this file as the command +development index entry so `docs/development/commands/README.md` can list every +public CLI command without duplicating the Code planning document. diff --git a/docs/development/commands/commit-tree.md b/docs/development/commands/commit-tree.md index daead331e..31d05f920 100644 --- a/docs/development/commands/commit-tree.md +++ b/docs/development/commands/commit-tree.md @@ -23,7 +23,7 @@ GIT_INDEX_FILE 等价物)合成离线修订组合环路。发布走 `update-re ## 设计要点 -- **消息序列化陷阱(审阅 must-fix)**:git-internal 0.7.6 的 +- **消息序列化陷阱(审阅 must-fix)**:git-internal 的 `Commit::to_data()` 在 committer 行后**不加分隔符**——头/体空行分隔必须是 message 字段自身的前导 `\n`。与 porcelain 同走 `format_commit_msg(msg, None)`(= `"\n{msg}"`)。cat-file 断言钉住。潜在同源坑: diff --git a/docs/development/gap/agent-trace.md b/docs/development/gap/agent-trace.md index 411c3ff66..7497e9bde 100644 --- a/docs/development/gap/agent-trace.md +++ b/docs/development/gap/agent-trace.md @@ -171,7 +171,7 @@ Claude Code `PostToolUse` hook(`reference/trace-hook.ts:94`)对 `Write` 和 > 以下结论经对实际代码核验,推翻了若干“听起来很美”的直觉做法。照着直觉做会撞墙。 -1. **`git-internal` 是外部 pinned crate**(`Cargo.toml`:`git-internal = "0.7.4"`,无 `[patch]` 覆盖)。改其 `TouchedFile` / `Provenance` **不是 Libra 仓内 PR**,需发上游版本再升级依赖;且二者都带 `#[serde(deny_unknown_fields)]`,加字段对旧版本读者是**前向不兼容**。 +1. **`git-internal` 是外部 pinned crate**(`Cargo.toml`:`git-internal = "0.8.1"`,无 `[patch]` 覆盖)。改其 `TouchedFile` / `Provenance` **不是 Libra 仓内 PR**,需发上游版本再升级依赖;且二者都带 `#[serde(deny_unknown_fields)]`,加字段对旧版本读者是**前向不兼容**。 → **行区间类型必须在 Libra 仓内自定义,不要动 `git-internal`。** 2. **Libra 的 `notes` 不是 git 原生 `refs/notes/*` 树**,而是 SQLite 表(`sql/migrations/2026061401_notes.sql`)+ 对象库里的 blob 哈希。它**不随 push/fetch 传输,外部工具发现不了**;`idx_notes_ref` 只建在 `(notes_ref)` 上。 → 用 notes 做“跨工具可发现的互操作后端”**不成立**;notes 只适合**本地**存储。真正的互操作只能在 **`publish/ai_export` 边界**导出标准 JSON。 @@ -187,7 +187,7 @@ Claude Code `PostToolUse` hook(`reference/trace-hook.ts:94`)对 `Write` 和 **利好**: -- `content_hash` 别引 murmur3——**`git-internal::IntegrityHash::compute`** 可用 SHA-256 计算字节哈希,序列化为 `integrity:sha256:`。⚠️ 此 API 在**外部 pinned crate `git-internal 0.7.4`** 内(非 Libra 仓内可 grep 到),是本文档全部锚点中**唯一无法在本仓树内核验**的一条——落地 P0-0/P1-4 前必须先对照已 vendored 的 crate 源确认其确切签名与序列化语义,不要凭本文档直接调用。 +- `content_hash` 别引 murmur3——**`git-internal::IntegrityHash::compute`** 可用 SHA-256 计算字节哈希,序列化为 `integrity:sha256:`。⚠️ 此 API 在**外部 pinned crate `git-internal 0.8.1`** 内(非 Libra 仓内可 grep 到),是本文档全部锚点中**唯一无法在本仓树内核验**的一条——落地 P0-0/P1-4 前必须先对照已 vendored 的 crate 源确认其确切签名与序列化语义,不要凭本文档直接调用。 - `model_id` 规范化几乎免费——**`ModelBinding::to_canonical_string()`**(`src/internal/ai/agent/profile/spec.rs:129`)已能产出 `provider/model[@variant]`,且 `AgentRunEvent::Spawned`、`agent_usage_stats`、`UsageContext` 都已把 provider 与 model **拆开存**,只差在序列化边界拼接。 - **唯一被低估的资产**:`apply_patch` 已经算好行区间。`src/internal/ai/tools/apply_patch/core.rs` 的 `compute_replacements` 产出精确的 `(start_index, old_len, new_lines)`,handler 还建了 `FileDiff` + unified diff,然后**仅用于 TUI 显示就丢弃**。这是全系统唯一对“AI 写了哪些行”有完美、无竞态认知的地方。 diff --git a/docs/development/gap/grit-gap.md b/docs/development/gap/grit-gap.md index bbe9eb8ab..6d8fadc12 100644 --- a/docs/development/gap/grit-gap.md +++ b/docs/development/gap/grit-gap.md @@ -689,7 +689,7 @@ gantt - `docs/commands/remote.md` - `COMPATIBILITY.md` - `docs/development/integration-scenarios.yaml` -- `docs/development/integration-test-plan.md` +- `docs/development/integration/integration-test-plan.md` **验收标准**: - [ ] `libra fetch -p` / `--prune` 删除已不存在于远端的 `refs/remotes//*`,并记录可审计输出。 diff --git a/docs/development/internal/code-agent-runtime.md b/docs/development/internal/code-agent-runtime.md index 2769f46cf..366d0f282 100644 --- a/docs/development/internal/code-agent-runtime.md +++ b/docs/development/internal/code-agent-runtime.md @@ -2,7 +2,7 @@ > Status: agent-executable(可交给 Agent 按卡执行;本文档已包含自举清单、执行协议、代码漂移防护、测试骨架、接口契约与常见陷阱,具备直接开发条件) > Scope: 先设计并落地独立 Agent framework,再迁移 TUI-owned 行为,最后移除 Code TUI 并让 Web Code UI 成为唯一交互面;新增 entireio/cli 对齐轨道只扩展外部 Agent 能力、checkpoint/export 与 review/investigate 工作流,不改变 MCP 与内部 AgentRuntime 的边界。 -> Companion docs: Web/runtime 现状见 [`docs/development/commands/_general.md`](commands/_general.md);控制面契约见 [`docs/commands/code-control.md`](../commands/code-control.md);`libra agent` 外部捕获公共 CLI/API、E1-E9 wire 契约、AG-16~AG-24 任务卡与验收命令见 [`docs/development/commands/agent.md`](commands/agent.md);MCP stdio 独立命令拆分见 [`mcp.md`](mcp.md)。 +> Companion docs: Web/runtime 现状见 [`docs/development/commands/_general.md`](commands/_general.md);控制面契约见 [`docs/commands/code-control.md`](../commands/code-control.md);`libra agent` 外部捕获公共 CLI/API、E1-E9 wire 契约、AG-16~AG-24 任务卡与验收命令见 [`docs/development/tracing/agent.md`](commands/agent.md);MCP stdio 独立命令拆分见 [`mcp.md`](mcp.md)。 > Supersedes: 旧 `docs/development/web-only.md` 草案已并入本文并删除;旧 `docs/development/agent.md` 也已由本文取代。后续内部 AgentRuntime / Web-only 迁移只维护本文件,不得重新创建或链接旧文档。 > Control-plane correction (2026-06-04): 停止把 TUI/MCP 作为 Agent 操作入口;`libra code` 只保留 Web Code UI,外部调度参考 Codex 通过 WebSocket 进入 AgentRuntime。 > 术语消歧(跨文档):本文的"外部 Agent / 外部调度"指经 WebSocket/Web API **驱动 Libra 内部 runtime** 的外部调用方;[`docs/development/commands/_general.md`](commands/_general.md) 的"外部 Agent"则指**被观测捕获的第三方编码 Agent**(第一批为 Claude Code/Codex/OpenCode),二者同词反义。本文的"session/checkpoint"指内部 runtime 的 session 与 orchestrator/dagrs 执行 checkpoint;entire.md 的"`agent_session`/`agent_checkpoint`"指外部会话表与 `refs/libra/traces` 上的 transcript 检查点。两者的表 / 类型 / ref / 命令命名空间均不重叠。 @@ -17,7 +17,7 @@ 6. 2026-06-05 agent-bootstrap: 添加 Agent 开发前置检查清单、Source Anchor Refresh Protocol、Mid-card Checkpoint、Rollback & Recovery Protocol、测试骨架模板与命名约定、核心接口骨架(Rust 类型契约)、常见陷阱与纠偏、AG-00 执行模板,增强 AG-01/AG-02 验收的可量化指标;修复两处格式断裂;使文档达到可直接交给 Agent 开发的程度。 7. 2026-06-05 entire-alignment: 与 [`docs/development/commands/_general.md`](commands/_general.md)(外部 Agent 会话捕获子系统)对齐,消除共享耦合处的隐患——Companion docs 增列 entire.md 并标注正交;新增跨文档「外部 Agent / session / checkpoint」术语消歧;在「事件日志、游标与投影」节固化 `SessionEvent` schema 主权归 AG-01 + 向后兼容/unknown-event-safe 承诺(entire.md 复用同一 `SessionStore` JSONL,仅以 `.libra/sessions/{code,agent}/` 子目录隔离)。本计划删除 Code TUI 后,entire.md 的会话展示面统一改走 Web Code UI。 8. 2026-06-05 claude-execution-hardening: 清理会误导 Claude 直接复制的伪代码(`TaskInvocation.instruction`、`TaskFailure::from`、不存在的 budget 构造器),把绝对 `file://` 链接改为仓库相对路径,并把执行协议中的非 Libra-native VCS 命令替换为 Libra-native 工作流。 -9. 2026-06-16 entireio-cli-parity: 基于 `/Volumes/Data/entireio/cli`、`/Volumes/Data/entireio/cli-checkpoints` 与已被覆盖的 [d0a714887c9f2fbe0e0df51057c0952a7867f174](https://github.com/web3infra-foundation/libra/commit/d0a714887c9f2fbe0e0df51057c0952a7867f174) 分析版,补齐 entireio/cli 对齐差距、Gate 8、Phase 14-17 与 AG-16~AG-24,使外部 Agent 捕获、能力声明、checkpoint/export、review/investigate 与 skill 事件可作为落地任务执行;同时明确旧分析中的 `claudecode` provider 不得复活。 +9. 2026-06-16 entireio-cli-parity: 基于 `/Volumes/Data/entireio/cli`、`/Volumes/Data/entireio/cli-checkpoints` 与已被覆盖的 [d0a714887c9f2fbe0e0df51057c0952a7867f174](https://github.com/libra-tools/libra/commit/d0a714887c9f2fbe0e0df51057c0952a7867f174) 分析版,补齐 entireio/cli 对齐差距、Gate 8、Phase 14-17 与 AG-16~AG-24,使外部 Agent 捕获、能力声明、checkpoint/export、review/investigate 与 skill 事件可作为落地任务执行;同时明确旧分析中的 `claudecode` provider 不得复活。 10. 2026-06-17 persistence-grounding: 新增独立「持久化与数据库结构」章节,按当前 SQL 迁移和写入代码确认内部 `libra code` runtime、外部 `libra agent` 捕获、SQLite 表、Libra 对象与 `refs/libra/traces` 的数据流边界。 11. 2026-06-17 review-analysis: 对全文进行 11 维度评审(方案合理性、可行性、完整性、安全性、功能正确性与接口兼容性、数据流与控制流正确性、性能与效率、可靠性与容错性、兼容性与互操作性、可扩展性与可维护性、合规性与标准符合性)。发现并修复 P0 级 `dispatch_batch` API 虚构(该 trait 方法当前不存在,全文误标为"既有")、P1 级 dagrs 使用范围高估(实际仅 2 文件而非 9 文件)、锚点行号漂移超阈值(SubAgentDispatcher 偏移 109 行、execute_stdio/validate_mode_args 偏移 129 行)等问题;补充 worker crash recovery、SSE backpressure、rate limiting、event log 损坏恢复等缺失考量。 12. 2026-06-17 guard-template-hardening: 复核 §3.1–3.3 失效模式守卫示例,发现“可编译断言规范”表述仍过强:`ToolLoopTurn` 真实字段为 `final_text: String` + `history: Vec`,无 `Default` / `has_completed_claim` / `system_evidence`;`GoalSupervisor::step` 是实例方法且接收 `GoalTurnOutcome` / `GoalVerifierContext` / `GoalEventClock`,不是静态方法;`GoalEventEnvelope` 无 `Compaction` variant。已把 §3.1–3.3 改为“真实 API 对齐模板”,明确哪些 helper/fixture 需 AG-14 抽取,避免 Agent 直接复制失败。 @@ -895,11 +895,11 @@ Dynamic Workflows 设计的核心洞察——用**确定性控制流**协调多 ### entireio/cli 对齐分析(2026-06-16) -`libra agent` 需要的 public CLI/API、E1-E9 wire 契约、AG-16~AG-24 任务卡、checkpoint/export、review/investigate 和验收命令已经拆入 [`docs/development/commands/agent.md`](commands/agent.md)。本节只保留 Gate 8 与内部 AgentRuntime/Web-only 迁移的边界关系、来源证据摘要和历史背景,避免 `libra agent` 的外部捕获契约继续散落在两个文档。 +`libra agent` 需要的 public CLI/API、E1-E9 wire 契约、AG-16~AG-24 任务卡、checkpoint/export、review/investigate 和验收命令已经拆入 [`docs/development/tracing/agent.md`](commands/agent.md)。本节只保留 Gate 8 与内部 AgentRuntime/Web-only 迁移的边界关系、来源证据摘要和历史背景,避免 `libra agent` 的外部捕获契约继续散落在两个文档。 -> **AG-16~AG-21 落地实施计划(统一 Provider 接口 trait 草图 + 逐文件改动清单)** 见 [`docs/development/commands/agent.md`](commands/agent.md) 的「AG-16~AG-21 统一 Provider 接口落地实施计划」节。要点:删除 dead 的 `ObservedAgentHooks`、hook 能力收敛到 `ObservedAgent::as_hooks() -> Option<&dyn HookProvider>` 并以 `AgentKind` 取代字符串 `find_provider`、落地 `DeclaredAgentCaps`/`CapabilityDeclarer` 8-bool(E1)、补齐 E1 可选能力 trait、`LifecycleEventKind` 补 `SubagentStart`/`SubagentEnd`。**该计划只动 provider/capability/lifecycle 轴,与存储对象模型解耦**:外部捕获保持 raw SQL `agent_*` + `refs/libra/traces` 的 `blob/tree/commit`,**不**迁到 git-internal AI 对象模型(`is_ai_object()` 族;该模型仅内部 AgentRuntime 使用)。该节与本节为同一计划的两侧镜像,任一处改动须同 PR 同步。 +> **AG-16~AG-21 落地实施计划(统一 Provider 接口 trait 草图 + 逐文件改动清单)** 见 [`docs/development/tracing/agent.md`](commands/agent.md) 的「AG-16~AG-21 统一 Provider 接口落地实施计划」节。要点:删除 dead 的 `ObservedAgentHooks`、hook 能力收敛到 `ObservedAgent::as_hooks() -> Option<&dyn HookProvider>` 并以 `AgentKind` 取代字符串 `find_provider`、落地 `DeclaredAgentCaps`/`CapabilityDeclarer` 8-bool(E1)、补齐 E1 可选能力 trait、`LifecycleEventKind` 补 `SubagentStart`/`SubagentEnd`。**该计划只动 provider/capability/lifecycle 轴,与存储对象模型解耦**:外部捕获保持 raw SQL `agent_*` + `refs/libra/traces` 的 `blob/tree/commit`,**不**迁到 git-internal AI 对象模型(`is_ai_object()` 族;该模型仅内部 AgentRuntime 使用)。该节与本节为同一计划的两侧镜像,任一处改动须同 PR 同步。 -本节把 `/Volumes/Data/entireio/cli`、`/Volumes/Data/entireio/cli-checkpoints` 与旧分析版提交 [d0a714887c9f2fbe0e0df51057c0952a7867f174](https://github.com/web3infra-foundation/libra/commit/d0a714887c9f2fbe0e0df51057c0952a7867f174) 的结论重新落到**当前** Libra 源码。旧提交改的是 `docs/development/commands/agent.md`,其中提出的 `ObservedAgent` 能力模型、external `libra-agent-`、checkpoint/session 结构、hook lifecycle dispatcher、review/investigate 轨道仍有价值;但当前仓库已经删除 `claudecode` provider,并把 `libra agent` 定位为外部 Agent 捕获子系统,所以旧分析不能原样恢复。 +本节把 `/Volumes/Data/entireio/cli`、`/Volumes/Data/entireio/cli-checkpoints` 与旧分析版提交 [d0a714887c9f2fbe0e0df51057c0952a7867f174](https://github.com/libra-tools/libra/commit/d0a714887c9f2fbe0e0df51057c0952a7867f174) 的结论重新落到**当前** Libra 源码。旧提交改的是 `docs/development/tracing/agent.md`,其中提出的 `ObservedAgent` 能力模型、external `libra-agent-`、checkpoint/session 结构、hook lifecycle dispatcher、review/investigate 轨道仍有价值;但当前仓库已经删除 `claudecode` provider,并把 `libra agent` 定位为外部 Agent 捕获子系统,所以旧分析不能原样恢复。 #### 对齐边界(必须先读) @@ -925,11 +925,11 @@ Dynamic Workflows 设计的核心洞察——用**确定性控制流**协调多 #### 已验证 wire 契约(E1–E9;2026-06-16 对 `/Volumes/Data/entireio/cli` 源码 + `/Volumes/Data/entireio/cli-checkpoints` 归档逐项核对) -> 本块保留为 Gate 8 的内部计划镜像;`libra agent` public contract 的权威副本在 [`docs/development/commands/agent.md`](commands/agent.md) 的「已验证 wire 契约(E1-E9)」节。凡卡正文/Phase 出现「命名可调整」「至少包含」「等价 contract」,都以 commands/agent.md 的精确标识符为准;本节变化必须同步到 commands/agent.md,反之亦然。 +> 本块保留为 Gate 8 的内部计划镜像;`libra agent` public contract 的权威副本在 [`docs/development/tracing/agent.md`](commands/agent.md) 的「已验证 wire 契约(E1-E9)」节。凡卡正文/Phase 出现「命名可调整」「至少包含」「等价 contract」,都以 commands/agent.md 的精确标识符为准;本节变化必须同步到 commands/agent.md,反之亦然。 > > **验证审计(2026-06-16)**:本节统计数字来自对 `/Volumes/Data/entireio/cli-checkpoints` 的实地遍历——根 `metadata.json` 4,377 个、per-session `metadata.json` 5,949 个、最大单 checkpoint 61 session、最大 `full.jsonl` 48,774,003 bytes(46.51 MiB)、唯一 `.zst` 路径 `e6/5d9bec561a/0/full.jsonl.zst`。计数方法见下方「快速刷新协议」第 2 步;未来复跑若数字变化,必须在本段打补丁并同步 `commands/agent.md`。 > -> **与被覆盖分析版 d0a714887c9f2fbe0e0df51057c0952a7867f174 的关系**:d0a714 是最早把 10 节差距表(命令面、能力模型、hook 生命周期、transcript/checkpoint、external plugin、review/investigate、skill discovery、agent 覆盖、测试架构、cli-checkpoints 数据模型)写入 `docs/development/commands/agent.md` 的提交,路径当时为 `/run/media/eli/...`。当前 `commands/agent.md` 与本节已演进为 source-grounded 形态(更新了 7-agent 矩阵、AG-16~AG-24 映射、E1-E9 精确 wire key、真实归档统计 **4377** checkpoints / 5949 sessions / 46.5 MiB max full.jsonl / 61-session 单 checkpoint 等)。本计划的 Gate 8 / AG 卡直接继承并细化 d0a714 的结论,同时明确:(a) `claudecode` provider 硬删除约束永不复活;(b) Libra 外部捕获与内部 AgentRuntime 正交(session 目录 `.libra/sessions/{code,agent}/` 隔离);(c) 保留 Libra-native 存储(refs/libra/traces + raw SQL agent_* 表),只对齐能力 contract、event 模型、export payload、review/investigate workflow。 +> **与被覆盖分析版 d0a714887c9f2fbe0e0df51057c0952a7867f174 的关系**:d0a714 是最早把 10 节差距表(命令面、能力模型、hook 生命周期、transcript/checkpoint、external plugin、review/investigate、skill discovery、agent 覆盖、测试架构、cli-checkpoints 数据模型)写入 `docs/development/tracing/agent.md` 的提交,路径当时为 `/run/media/eli/...`。当前 `commands/agent.md` 与本节已演进为 source-grounded 形态(更新了 7-agent 矩阵、AG-16~AG-24 映射、E1-E9 精确 wire key、真实归档统计 **4377** checkpoints / 5949 sessions / 46.5 MiB max full.jsonl / 61-session 单 checkpoint 等)。本计划的 Gate 8 / AG 卡直接继承并细化 d0a714 的结论,同时明确:(a) `claudecode` provider 硬删除约束永不复活;(b) Libra 外部捕获与内部 AgentRuntime 正交(session 目录 `.libra/sessions/{code,agent}/` 隔离);(c) 保留 Libra-native 存储(refs/libra/traces + raw SQL agent_* 表),只对齐能力 contract、event 模型、export payload、review/investigate workflow。 **快速刷新协议(AG-16+ 开工或 review 前必跑,5-10 min)**: ```bash @@ -955,7 +955,7 @@ find /Volumes/Data/entireio/cli-checkpoints -name 'full.jsonl' -o -name 'metadat # 3. Libra 当前锚点复核(必须与上文“当前基线”表一致) rg -n 'AgentKind|ObservedAgentHooks|Transcript(Truncator|Chunker)|LifecycleEventKind|STABLE_AGENT_SLUGS|append_checkpoint_commit' src/internal/ai/observed_agents src/internal/ai/hooks src/internal/ai/history.rs src/command/agent/mod.rs # 4. 两份文档一致性 -rg -n "AG-1[6-9]|AG-2[0-4]|claudecode|SubagentStart|content_hash|DeclaredCaps|libra-agent-.*info" docs/development/code-agent-runtime.md docs/development/commands/agent.md | sort | uniq -c | awk '$1!=2{print "DRIFT:",$0}' +rg -n "AG-1[6-9]|AG-2[0-4]|claudecode|SubagentStart|content_hash|DeclaredCaps|libra-agent-.*info" docs/development/code-agent-runtime.md docs/development/tracing/agent.md | sort | uniq -c | awk '$1!=2{print "DRIFT:",$0}' ``` 任何刷新后发现 E 契约或归档形态变化,必须先在 `commands/agent.md` 的“已验证 wire 契约”打补丁,再同步本节摘要与 Gate 8 追踪,再开工 AG 卡。 @@ -1049,7 +1049,7 @@ schema pin test(AG-16/AG-24)必须断言序列化后的 8 个 snake_case key #### 旧 d0a714 分析关闭表 -旧提交 [d0a714887c9f2fbe0e0df51057c0952a7867f174](https://github.com/web3infra-foundation/libra/commit/d0a714887c9f2fbe0e0df51057c0952a7867f174) 只更新过 `docs/development/commands/agent.md`,且是差距分析而非可执行任务计划。下表把旧版 10 节结论收敛到当前 AG 卡,防止实现者把旧表原样恢复或重复规划。 +旧提交 [d0a714887c9f2fbe0e0df51057c0952a7867f174](https://github.com/libra-tools/libra/commit/d0a714887c9f2fbe0e0df51057c0952a7867f174) 只更新过 `docs/development/tracing/agent.md`,且是差距分析而非可执行任务计划。下表把旧版 10 节结论收敛到当前 AG 卡,防止实现者把旧表原样恢复或重复规划。 | 旧 d0a714 结论 | 当前处理 | 执行卡 | |---|---|---| @@ -1075,7 +1075,7 @@ schema pin test(AG-16/AG-24)必须断言序列化后的 8 个 snake_case key | checkpoint/export | `refs/libra/traces` + SQLite 表是 Libra-native 存储 | 缺 entire-style root/session export contract、multi-session stable index、content_hash/prompt/context/token_usage、large transcript lazy show | AG-20 | | transcript intelligence | 有 raw transcript reader、redaction、preview stubs、初步 chunker trait | 缺 prepare/analyze/prompt/model/token/subagent/skill extraction 和 fail-open semantics | AG-21 | | review/investigate | 内部 workflow pattern 规划到 AG-13~AG-15;外部 capture 子系统无 review/investigate command/workflow | 缺 entire-style review multi-agent run、investigate quorum loop、findings manifest、fix launch boundary | AG-22、AG-23 | -| docs/tests | `docs/development/commands/agent.md` 已承接外部 capture public contract、E1-E9、AG-16~AG-24 和验收命令;本文描述内部 runtime 与 Gate 8 总览 | 实现 AG-16+ 时仍需同步 `docs/commands/agent.md`、`COMPATIBILITY.md`、`tests/INDEX.md` 与本文 Gate 8 摘要 | AG-24 | +| docs/tests | `docs/development/tracing/agent.md` 已承接外部 capture public contract、E1-E9、AG-16~AG-24 和验收命令;本文描述内部 runtime 与 Gate 8 总览 | 实现 AG-16+ 时仍需同步 `docs/commands/agent.md`、`COMPATIBILITY.md`、`tests/INDEX.md` 与本文 Gate 8 摘要 | AG-24 | #### 落地原则 @@ -1086,9 +1086,9 @@ schema pin test(AG-16/AG-24)必须断言序列化后的 8 个 snake_case key - **每张 AG-16+ 卡都必须带 fixture。** 至少包含一个 built-in adapter fixture、一个 external binary fake fixture、一个 multi-session checkpoint fixture、一个缺失 optional 文件 fixture、一个 malicious/path traversal fixture。**推荐直接复用或转换 `/Volumes/Data/entireio/cli-checkpoints` 的真实样本**(采样命令见上文“快速刷新协议”);在 `tests/fixtures/agent_capture/` 下放置最小化子集(1-session.jsonl、multi61.tar.zst 摘要、missing_prompt/ 目录、gemini_whole_json/ 等),并在测试中用 `include_bytes!` 或 `testcontainers` 风格挂载,避免测试依赖外部大归档。 - **Source Anchor Refresh Protocol 必须在每张 AG 卡开工和 review 时执行**(见上文完整 bash 脚本)。Anchor 漂移(entire 演进或 Libra 重构)是 AG-24 的 P1 风险。 -### 与 `docs/development/commands/agent.md` 的关系 +### 与 `docs/development/tracing/agent.md` 的关系 -[`docs/development/commands/agent.md`](commands/agent.md) 是 `libra agent` 外部捕获的 public CLI/API source-of-truth:它承接从本文拆出的 E1-E9 wire 契约、AG-16~AG-24 任务卡、checkpoint/export、review/investigate、验收命令和“还未实现”表。 +[`docs/development/tracing/agent.md`](commands/agent.md) 是 `libra agent` 外部捕获的 public CLI/API source-of-truth:它承接从本文拆出的 E1-E9 wire 契约、AG-16~AG-24 任务卡、checkpoint/export、review/investigate、验收命令和“还未实现”表。 - 本文继续负责内部 `libra code` AgentRuntime / Web-only 迁移主线,以及 Gate 8 是否与内部 runtime、MCP、control-plane 边界保持正交。 - Gate 8 仍可在本文的总体门禁、任务拆分矩阵和完成定义中出现,但实现者领取 AG-16~AG-24 时,必须先读 `commands/agent.md` 的 public contract。 @@ -2083,13 +2083,13 @@ cargo test --test compat_matrix_alignment ### Phase 17: entireio/cli 对齐 - docs、compat、release closeout -目标:把 Gate 8 的 public behavior、JSON schema、fixtures 和用户文档收敛,避免 `docs/development/code-agent-runtime.md`、`docs/development/commands/agent.md`、`docs/commands/agent.md` 三处继续漂移。 +目标:把 Gate 8 的 public behavior、JSON schema、fixtures 和用户文档收敛,避免 `docs/development/code-agent-runtime.md`、`docs/development/tracing/agent.md`、`docs/commands/agent.md` 三处继续漂移。 依赖:AG-16~AG-23。 任务: -- 更新 [`docs/development/commands/agent.md`](commands/agent.md):成为 `libra agent` 外部捕获的权威 CLI/API 文档,包含 capability matrix、list/add/remove alias、RPC protocol、lifecycle event、checkpoint/export shape、review/investigate 用户流。 +- 更新 [`docs/development/tracing/agent.md`](commands/agent.md):成为 `libra agent` 外部捕获的权威 CLI/API 文档,包含 capability matrix、list/add/remove alias、RPC protocol、lifecycle event、checkpoint/export shape、review/investigate 用户流。 - 更新 `docs/commands/agent.md` / zh-CN 文档(若存在)与 `COMPATIBILITY.md`,把 `libra agent` 标记为 Libra-only extension,并列出 JSON output compatibility guarantees。 - 更新 `tests/INDEX.md`:新增/重命名的 `agent_*`、`observed_agents_*`、`agent_review_*`、`agent_investigate_*` 测试目标必须有 wave、purpose、source mapping。 - 增加旧分析差距关闭表:d0a714 旧版每个 phase 的结论要么映射到 AG-16~AG-24,要么说明因当前架构变化废弃(例如 `claudecode` provider)。 @@ -2098,13 +2098,13 @@ cargo test --test compat_matrix_alignment 验收: - `rg -n "entireio|d0a714887c9f2fbe0e0df51057c0952a7867f174|claudecode|list/add/remove|libra-agent-" docs/development docs/commands COMPATIBILITY.md tests/INDEX.md` 输出可解释,无旧 provider 复活。 -- `docs/development/code-agent-runtime.md` 与 `docs/development/commands/agent.md` 的边界说明一致:前者是内部 AgentRuntime + Web-only 迁移 + Gate 8 总览追踪,后者是 `libra agent` public CLI/API/source-of-truth(含 E1-E9、AG-16~AG-24、checkpoint/export、review/investigate、验收命令和“还未实现”表)。**任何一处修改 AG 卡、E 契约、已知差距或有意差异,必须同一 PR 内双向同步**(见 AG-24 强制同步规则)。 +- `docs/development/code-agent-runtime.md` 与 `docs/development/tracing/agent.md` 的边界说明一致:前者是内部 AgentRuntime + Web-only 迁移 + Gate 8 总览追踪,后者是 `libra agent` public CLI/API/source-of-truth(含 E1-E9、AG-16~AG-24、checkpoint/export、review/investigate、验收命令和“还未实现”表)。**任何一处修改 AG 卡、E 契约、已知差距或有意差异,必须同一 PR 内双向同步**(见 AG-24 强制同步规则)。 - 旧 d0a714 分析版已并入本节(见上文“与被覆盖分析版...”段落);d0a714 提出的所有 10 节结论均已映射到 AG-16~AG-24 或标记因架构变化废弃(claudecode、存储路径照搬等)。 - 所有新增 public JSON schema 都有 Display/schema pin test 或 snapshot test;用户可见错误包含原因、资源、下一步。 ## 任务卡 -> **如何读一张卡(每张卡的完整执行规格分布在四处,按此拼装):** ①本节卡正文 = 目标 + 验收要点;②「任务拆分矩阵」= 该卡的 Dependencies / Scope / Evidence / Must-not;③同名「实施阶段 Phase N」= 详细任务步骤与逐条验收(卡号↔Phase 对应:AG-00→Phase 0,AG-01→Phase 1,AG-02→Phase 2,AG-03→Phase 3,AG-04→Phase 4,AG-05→Phase 5,AG-06→Phase 6,AG-07→Phase 7,AG-08→Phase 8,AG-09→Phase 9,AG-10→Phase 10,AG-11→Phase 11,AG-12→Phase 12,AG-13/14/15→Phase 13,AG-16/17/18→Phase 14,AG-19/20→Phase 15,AG-21/22/23→Phase 16,AG-24→Phase 17);④「验收命令」节 = 必跑命令。AG-16~AG-24 还必须先读 [`docs/development/commands/agent.md`](commands/agent.md) 的 E1-E9、任务卡、验收命令和 public surface,因为那是 `libra agent` 外部捕获的事实来源。开工前先按「Agent 执行协议 → Task card fields」把这些输入合并成六字段,再按「Agent 开发前置检查清单」过一遍。锚点一律以「当前基线 → 已核对锚点表」为准 + 现场 `rg` 复核。 +> **如何读一张卡(每张卡的完整执行规格分布在四处,按此拼装):** ①本节卡正文 = 目标 + 验收要点;②「任务拆分矩阵」= 该卡的 Dependencies / Scope / Evidence / Must-not;③同名「实施阶段 Phase N」= 详细任务步骤与逐条验收(卡号↔Phase 对应:AG-00→Phase 0,AG-01→Phase 1,AG-02→Phase 2,AG-03→Phase 3,AG-04→Phase 4,AG-05→Phase 5,AG-06→Phase 6,AG-07→Phase 7,AG-08→Phase 8,AG-09→Phase 9,AG-10→Phase 10,AG-11→Phase 11,AG-12→Phase 12,AG-13/14/15→Phase 13,AG-16/17/18→Phase 14,AG-19/20→Phase 15,AG-21/22/23→Phase 16,AG-24→Phase 17);④「验收命令」节 = 必跑命令。AG-16~AG-24 还必须先读 [`docs/development/tracing/agent.md`](commands/agent.md) 的 E1-E9、任务卡、验收命令和 public surface,因为那是 `libra agent` 外部捕获的事实来源。开工前先按「Agent 执行协议 → Task card fields」把这些输入合并成六字段,再按「Agent 开发前置检查清单」过一遍。锚点一律以「当前基线 → 已核对锚点表」为准 + 现场 `rg` 复核。 ### AG-00: 校准清单(Agent 领取的第一张卡) @@ -2155,7 +2155,7 @@ rg "handle_builtin_command|handle_tui_control_command|/\w+" src/internal/tui/app ```bash cargo +nightly fmt --all --check # 虽然不改 Rust,确认基线能通过 ``` -检查本文档内部链接是否有效(`mcp.md`、`docs/development/commands/agent.md` 等)。 +检查本文档内部链接是否有效(`mcp.md`、`docs/development/tracing/agent.md` 等)。 验收: @@ -2493,7 +2493,7 @@ impl BudgetTracker { * CLI parser: [`src/command/agent/mod.rs`](../../src/command/agent/mod.rs) * Status/list: [`src/command/agent/status.rs`](../../src/command/agent/status.rs) * Hook enable/disable: [`src/internal/ai/hooks/providers/`](../../src/internal/ai/hooks/providers/) -* Public docs: `docs/commands/agent.md`、[`docs/development/commands/agent.md`](commands/agent.md) +* Public docs: `docs/commands/agent.md`、[`docs/development/tracing/agent.md`](commands/agent.md) ##### 2. 语义 - `libra agent list [--json]`:列出 known agents + capability/stability/install state;不等价于 full doctor。 @@ -2513,7 +2513,7 @@ impl BudgetTracker { ##### 1. 涉及文件 * RPC runtime: [`src/internal/ai/observed_agents/rpc.rs`](../../src/internal/ai/observed_agents/rpc.rs) * CLI surface: [`src/command/agent/rpc.rs`](../../src/command/agent/rpc.rs) -* Protocol docs: [`docs/development/commands/agent.md`](commands/agent.md) +* Protocol docs: [`docs/development/tracing/agent.md`](commands/agent.md) * Tests/fixtures: `tests/command/agent_rpc*_test.rs` 或等价 target ##### 2. 协议要求 @@ -2670,18 +2670,18 @@ Libra 对应(AG-16 + AG-24 必须落地): AG-24 交付时必须把上表逐行核对:没有 target、没有具体 test function、没有 `Cargo.toml` / `tests/INDEX.md` 注册、或 `## 验收命令` 未覆盖,均视为测试方案未闭环。 #### 两个开发文档的强制同步规则 -- `docs/development/commands/agent.md`(公共 CLI/API/行为 source-of-truth + E1-E9 + AG-16~AG-24 + 差距表 + “还未实现的功能”表)是 `libra agent` 外部捕获 public contract 的事实来源。 +- `docs/development/tracing/agent.md`(公共 CLI/API/行为 source-of-truth + E1-E9 + AG-16~AG-24 + 差距表 + “还未实现的功能”表)是 `libra agent` 外部捕获 public contract 的事实来源。 - `docs/development/code-agent-runtime.md`(本文件,内部 runtime + Web-only 迁移 + Gate 8 总览追踪)必须与 `commands/agent.md` 双向同步:任一处更新 AG-16~AG-24 描述、E 契约、已知差距或有意差异,必须在同一 commit 刷新另一处,并更新 `docs/commands/agent.md` 用户文档 + `COMPATIBILITY.md` + `tests/INDEX.md`。 -- 验收时跑 `rg -n "AG-1[6-9]|AG-2[0-4]|DeclaredAgentCaps|libra-agent-.*info|SubagentStart|CheckpointSummary|content_hash" docs/development/code-agent-runtime.md docs/development/commands/agent.md` 确保一致;任何漂移视为 AG-24 未完成。 +- 验收时跑 `rg -n "AG-1[6-9]|AG-2[0-4]|DeclaredAgentCaps|libra-agent-.*info|SubagentStart|CheckpointSummary|content_hash" docs/development/code-agent-runtime.md docs/development/tracing/agent.md` 确保一致;任何漂移视为 AG-24 未完成。 ##### 1. 涉及文件 * 本文件: [`docs/development/code-agent-runtime.md`](code-agent-runtime.md) -* Public agent plan: [`docs/development/commands/agent.md`](commands/agent.md) +* Public agent plan: [`docs/development/tracing/agent.md`](commands/agent.md) * Command docs: `docs/commands/agent.md`(及 zh-CN 如存在) * Compatibility/docs index: `COMPATIBILITY.md`、`docs/commands/README.md`、`tests/INDEX.md` ##### 2. 实施要求 -- `docs/development/commands/agent.md` 成为 `libra agent` public behavior source-of-truth;本文只保留 Gate 8 追踪和内部 AgentRuntime 边界。 +- `docs/development/tracing/agent.md` 成为 `libra agent` public behavior source-of-truth;本文只保留 Gate 8 追踪和内部 AgentRuntime 边界。 - 为 external protocol、checkpoint export、review/investigate JSON schema 加 snapshot/schema tests。 - 旧 d0a714 分析关闭表:每个旧 phase 映射到 AG-16~AG-24 或标记“因当前架构废弃”。 - 文档全局检查 `claudecode`:只能出现在“不得复活/历史迁移”语境,不能作为 provider 推荐。 @@ -2785,12 +2785,12 @@ cargo test --test agent_lifecycle_event_test cargo test --test agent_checkpoint_export_test cargo test --test agent_checkpoint_redaction_test cargo test --test agent_transcript_intelligence_test -rg -n "LifecycleEvent|LifecycleEventKind|refs/libra/traces|content_hash|full.jsonl|full.jsonl.zst" src/internal/ai src/command/agent docs/development/commands/agent.md +rg -n "LifecycleEvent|LifecycleEventKind|refs/libra/traces|content_hash|full.jsonl|full.jsonl.zst" src/internal/ai src/command/agent docs/development/tracing/agent.md # review / investigate workflow parity cargo test --test agent_review_workflow_test cargo test --test agent_investigate_workflow_test -rg -n "AgentReviewWorkflow|AgentInvestigateWorkflow|findings_doc|pending_turn|quorum|round-robin" src/internal/ai src/command docs/development/commands/agent.md +rg -n "AgentReviewWorkflow|AgentInvestigateWorkflow|findings_doc|pending_turn|quorum|round-robin" src/internal/ai src/command docs/development/tracing/agent.md # docs/source-of-truth drift rg -n "claudecode|claude-code|libra-agent-|agent list|agent add|agent remove|LifecycleEvent|refs/libra/traces" docs/development docs/commands COMPATIBILITY.md tests/INDEX.md diff --git a/docs/development/tracing/memory.md b/docs/development/tracing/memory.md index a0c8aeb87..4f9259c01 100644 --- a/docs/development/tracing/memory.md +++ b/docs/development/tracing/memory.md @@ -15,7 +15,7 @@ Libra 在 [`agent.md`](../ai/object-model.md) 与 [`ai-object-model-reference.md`](../ai/object-model-reference.md) 中所记录的三层对象模型。 -如果本文档与 `agent.md` 或 `agent-workflow.md`(见 [`agent-workflow.md`](../ai/workflow.md))有冲突,以那两份文档为准。此外,外部 agent 捕获契约(见 [`docs/development/commands/agent.md`](./commands/agent.md))在其覆盖的存储与捕获面、MCP 边界(见 [`docs/development/mcp.md`](./mcp.md))在其覆盖的 MCP 命令面,亦各自为准。 +如果本文档与 `agent.md` 或 `agent-workflow.md`(见 [`agent-workflow.md`](../ai/workflow.md))有冲突,以那两份文档为准。此外,外部 agent 捕获契约(见 [`docs/development/tracing/agent.md`](./commands/agent.md))在其覆盖的存储与捕获面、MCP 边界(见 [`docs/development/mcp.md`](./mcp.md))在其覆盖的 MCP 命令面,亦各自为准。 ### 0.1 方案审查结论 @@ -192,7 +192,7 @@ episodic.findings.context-window-too-small Memory 遵循与 Libra 其余部分**相同的快照(Snapshot)/ 事件(Event)/ 投影(Projection)三层模型**(见 `../ai/object-model-reference.md`)。三层缺一不可;省略其中任何一层都会重新引入 CLAUDE.md 那种反模式。 -不过这里有一条至关重要的存储边界必须先讲清楚。Memory 采用的存储策略与**外部 agent 捕获完全相同**(参见 `docs/development/commands/agent.md` 的「持久化与对象边界」):把自定义 JSON 序列化为**普通的 git blob**,组织进 tree,提交到专用的 `libra/memory*` ref,再叠加一层**可重建的 SQLite 投影**。Memory **不**向 git-internal 新增任何 `ObjectType` 变体。git-internal 的 typed AI 对象族(`ObjectType::is_ai_object()` 这个闭合枚举:Intent/Plan/Task/Run/PatchSet/ContextSnapshot/Provenance 等快照,以及 RunEvent/TaskEvent/IntentEvent/PlanStepEvent/RunUsage/ToolInvocation/Evidence/Decision/ContextFrame 等事件)**专属于内部 AgentRuntime**,落在孤儿分支 `libra/intent` 上。Memory 借用的只是 git-internal 对象模型在快照/事件/投影上的那套**「设计纪律」**(见 `../ai/object-model-reference.md`)——但 `MemoryNote`/`MemoryEvent` 的字节是**自定义 JSON blob**(与 `traces` 上的 checkpoint 同构),并不是 git-internal 的一等对象。因此,下文中 `evidence_refs` 所指向的 git-internal `Evidence`/`Run`/`Decision` 对象(它们位于 `libra/intent` 平面)与 commit OID 之间,构成的是**跨平面引用**:Memory 平面的 blob 引用了 AgentRuntime 平面的一等对象,但两者各自独立存储、各自版本化。 +不过这里有一条至关重要的存储边界必须先讲清楚。Memory 采用的存储策略与**外部 agent 捕获完全相同**(参见 `docs/development/tracing/agent.md` 的「持久化与对象边界」):把自定义 JSON 序列化为**普通的 git blob**,组织进 tree,提交到专用的 `libra/memory*` ref,再叠加一层**可重建的 SQLite 投影**。Memory **不**向 git-internal 新增任何 `ObjectType` 变体。git-internal 的 typed AI 对象族(`ObjectType::is_ai_object()` 这个闭合枚举:Intent/Plan/Task/Run/PatchSet/ContextSnapshot/Provenance 等快照,以及 RunEvent/TaskEvent/IntentEvent/PlanStepEvent/RunUsage/ToolInvocation/Evidence/Decision/ContextFrame 等事件)**专属于内部 AgentRuntime**,落在孤儿分支 `libra/intent` 上。Memory 借用的只是 git-internal 对象模型在快照/事件/投影上的那套**「设计纪律」**(见 `../ai/object-model-reference.md`)——但 `MemoryNote`/`MemoryEvent` 的字节是**自定义 JSON blob**(与 `traces` 上的 checkpoint 同构),并不是 git-internal 的一等对象。因此,下文中 `evidence_refs` 所指向的 git-internal `Evidence`/`Run`/`Decision` 对象(它们位于 `libra/intent` 平面)与 commit OID 之间,构成的是**跨平面引用**:Memory 平面的 blob 引用了 AgentRuntime 平面的一等对象,但两者各自独立存储、各自版本化。 ### 4.1 `MemoryNote` —— 快照 [S] @@ -894,9 +894,9 @@ PreToolUse / PostToolUse → ToolUse(pre / post 由事件元数据区分, 记忆消费的是**归一化之后**的 `LifecycleEvent`,因此与平面(plane)无关: 无论该 turn 来自内部 `libra code` 的 AgentRuntime,还是来自 -`docs/development/commands/agent.md` 所描述的外部 observed-agent 捕获, +`docs/development/tracing/agent.md` 所描述的外部 observed-agent 捕获, 集成都同样适用。由生命周期事件触发的 memory 写入同样遵守 -`docs/development/commands/agent.md` AG-19 的「持久化前先编辑」 +`docs/development/tracing/agent.md` AG-19 的「持久化前先编辑」 (redaction-before-persist)与「按 owner 过滤」(owner-filtering)纪律。 ### 11.1 SessionStart diff --git a/docs/development/tracing/plan.md b/docs/development/tracing/plan.md index 1ce2daa18..14811a21e 100644 --- a/docs/development/tracing/plan.md +++ b/docs/development/tracing/plan.md @@ -127,7 +127,7 @@ Agent 第一期本地采集验收规则: 复核发现的剩余落地阻断与处理: -1. `docs/development/internal/code-agent-runtime.md` 仍包含旧 `docs/development/commands/agent.md`、旧 `docs/development/code-agent-runtime.md`、失效 `commands/_general.md` / `mcp.md` 相对链接和旧 drift 命令。由于本文把该文件声明为 Code 阶段事实源,若不先清理,会在 A1/C1 前继续污染执行者判断。新增 Task 0.4 承接该 source-of-truth drift 清理。 +1. `docs/development/internal/code-agent-runtime.md` 仍包含旧 `docs/development/tracing/agent.md`、旧 `docs/development/code-agent-runtime.md`、失效 `commands/_general.md` / `mcp.md` 相对链接和旧 drift 命令。由于本文把该文件声明为 Code 阶段事实源,若不先清理,会在 A1/C1 前继续污染执行者判断。新增 Task 0.4 承接该 source-of-truth drift 清理。 2. 当前可执行起点不是 A1,而是 `0.1 -> 0.3 -> 0.4 -> 0.2 -> A1`。0.3 修测试/索引断链;0.4 修事实源文档断链;0.2 才能把跨命令规则作为可运行守卫。 3. “落地完成”的唯一判据是 §10 进度表 + `libra log` + 对应源码/测试证据。任务卡 checkbox 只能表示覆盖项,不表示功能已完成;任何 checklist 勾选都必须附带 commit、验证命令和 blocked/deferred 说明。 4. 所有历史路径文字必须分为两类:可点击/可执行事实源必须指向当前存在路径;历史说明允许保留旧路径,但必须明确标注“旧/已删除/不得恢复/历史提交”,不能作为任务依赖或验证命令输入。 @@ -143,7 +143,7 @@ Agent 第一期本地采集验收规则: 5. feature 门控口径修正:`code_ui_remote_*`、`code_mcp_dual_entry_test`、`code_resume_test` 实为**逐项** `#[cfg(feature = "test-provider")]` 门控 + 1 个 `#[cfg(not(...))]` 的 `*_requires_test_provider_feature` 跳过占位测试;裸跑编译并通过 1 个占位测试(并非 0 个),假通过警示不变——C4/C5/C6/C7/§9 与 code.md 注记已同步改写。 6. 措辞精确化:`docs/commands/zh-CN/agent.md` 现存(A2/A9 的"若存在"改为必须同步);C4 的 `/api/code/*` 路由注册在 `src/internal/ai/web/mod.rs`(code_router),`code_ui.rs` 承载状态/读模型(§0 已同步);C2 两个 lib 测试 fn 已存在于 `src/command/code.rs:4342`/`:4712`(hedge 收紧);`COMPATIBILITY.md` 旧路径链接仅 agent.md 一处(:138,无 code.md 链接)。 7. 环境核查(本机 darwin arm64):三个真实 agent CLI 均在位且登录态通过 §0.3.2 只读检查(codex 0.142.5 已登录;claude 2.1.200 已认证且 `claude auth status` 确需 redact;opencode 1.17.11 命中 `~/.opencode/bin` 且存在凭证——Homebrew 1.17.10 被影子,印证 §0 记录 PATH 的要求);§0.3.4 全部 smoke flag 均存在于已装版本 `--help`;`origin` remote、`.env.test`、nightly rustfmt、pnpm 就绪。**A6.5 的环境前置当前不构成 blocked**。web/worker package.json 仍为 0.17.1758(滞后口径不变)。 -8. codex review 第一轮(2026-07-04,实跑 `cargo test`)补充两项,已修入:`compat_matrix_alignment` 除 agent.md 外还在运行时读取已迁移的 `docs/development/integration-test-plan.md`(现 `docs/development/integration/integration-test-plan.md`,同属 `932c3a0` 重组提交;仅重指 agent.md 该守卫仍失败)→ Task 0.3 已并入该第三处断链重指与 15 处/9 文件旧引用清理;§9 的 `ai_code_ui_headless_test` 为**整文件** `#![cfg(feature = "test-provider")]` 门控(`tests/ai_code_ui_headless_test.rs:10` 内部属性,裸跑编译 0 个测试、无占位测试,与第 5 条的逐项门控模式不同)→ §9 命令已改为带 `--features test-provider -- --test-threads=1`(带 feature 实跑 13 个用例)。 +8. codex review 第一轮(2026-07-04,实跑 `cargo test`)补充两项,已修入:`compat_matrix_alignment` 除 agent.md 外还在运行时读取已迁移的 `docs/development/integration/integration-test-plan.md`(现 `docs/development/integration/integration-test-plan.md`,同属 `932c3a0` 重组提交;仅重指 agent.md 该守卫仍失败)→ Task 0.3 已并入该第三处断链重指与 15 处/9 文件旧引用清理;§9 的 `ai_code_ui_headless_test` 为**整文件** `#![cfg(feature = "test-provider")]` 门控(`tests/ai_code_ui_headless_test.rs:10` 内部属性,裸跑编译 0 个测试、无占位测试,与第 5 条的逐项门控模式不同)→ §9 命令已改为带 `--features test-provider -- --test-threads=1`(带 feature 实跑 13 个用例)。 9. codex review 第二轮(2026-07-04)补充,已修入:同源 `integration-scenarios` 家族(`integration-scenarios.yaml` + `integration-scenarios/.md` 场景文档目录)迁移在 Task 0.3 原只覆盖 `_general.md` 同行引用一处且无守卫断言,而 `tools/integration-runner` 有 4 处功能性路径拼接(`manifest.rs:24`、`plan.rs:53/:118/:177`)按旧路径必失败,两个已迁移文件自身(`integration/integration-test-plan.md:84`、`integration/integration-scenarios/integration-scenarios.yaml:6/:13`)也残留旧根路径 → Task 0.3 补全 21 处/10 文件清单并新增家族否定断言(模式 `docs/development/integration-scenarios` 同时覆盖两种旧形态,不误伤新路径)。 10. codex review 第三轮(2026-07-04)补充,已修入:全路径家族模式测不到的 **bare/相对简写**残留——`integration/integration-scenarios/README.md:3` 的 `../integration-scenarios.yaml` 父级相对链接(yaml 迁移后已并入目录内,该链接必然断链)、`account.md:924/:968`、`_general.md:29`(mermaid 标签)、`tools/integration-runner/README.md:14` 的 bare 简写 → Task 0.3 新增简写规范化验收(13 行/4 文件 + README 断链 1 处)与两条配套否定断言(README 断链断言 + prose 文档行级"必须携带完整新路径"断言,后者已注明 `-v` 同行混写局限、以逐点位核对为准)。 @@ -518,19 +518,19 @@ harness 硬性要求: ### Task 0.3:文档搬迁接线(tracing/ 新路径守卫与索引重指) -**描述**:`docs/development/commands/{agent,code}.md` 已迁移为 `docs/development/tracing/{agent,code}.md`,但守卫与索引仍指旧路径,导致 `cargo test --all` 在当前树上直接编译失败(`include_str!` 指向不存在的文件)。同一次重组提交(`932c3a0` docs(development): reorganize planning docs)还把 `docs/development/integration-test-plan.md` 迁至 `docs/development/integration/integration-test-plan.md`,而 `compat_matrix_alignment` 在运行时同样读取该旧路径——仅重指 agent.md 无法让该守卫跑绿(2026-07-04 codex review 实跑 `cargo test` 确认),本卡一并重指。本卡必须在任何 Agent/Code 实现任务开工前完成,否则 §9 与各任务卡引用的 `compat_agent_docs_contract`、`cargo test --all` 均不可执行。 +**描述**:`docs/development/commands/{agent,code}.md` 已迁移为 `docs/development/tracing/{agent,code}.md`,但守卫与索引仍指旧路径,导致 `cargo test --all` 在当前树上直接编译失败(`include_str!` 指向不存在的文件)。同一次重组提交(`932c3a0` docs(development): reorganize planning docs)还把 `docs/development/integration/integration-test-plan.md` 迁至 `docs/development/integration/integration-test-plan.md`,而 `compat_matrix_alignment` 在运行时同样读取该旧路径——仅重指 agent.md 无法让该守卫跑绿(2026-07-04 codex review 实跑 `cargo test` 确认),本卡一并重指。本卡必须在任何 Agent/Code 实现任务开工前完成,否则 §9 与各任务卡引用的 `compat_agent_docs_contract`、`cargo test --all` 均不可执行。 **关联设计文档**:[`agent.md`](agent.md)、[`code.md`](code.md)。只做路径重指与索引同步,不改变守卫断言语义。 **验收标准**: -- [ ] `tests/compat/agent_docs_contract.rs` 的 `include_str!("../../docs/development/commands/agent.md")` 重指 `../../docs/development/tracing/agent.md`;守卫内其它路径引用同步核对。 -- [ ] `tests/compat/matrix_alignment.rs` 的**运行时**旧路径读取同步重指:`read_repo_file("docs/development/commands/agent.md")`(约 :104)与相关 context 字符串(约 :171)——该守卫对缺失文件直接 panic,是 `include_str!` 之外的第二处断链,`cargo check` 抓不到。 -- [ ] `compat_matrix_alignment` 的**第三处**断链(旧 `docs/development/integration-test-plan.md`,已迁至 `docs/development/integration/integration-test-plan.md`)同步重指:`tests/compat/matrix_alignment.rs`(:103 运行时读取、:161/:166 context 字符串)、`tests/compat/matrix_alignment_support.rs`(:174/:181 运行时读取)、`tools/integration-runner/src/plan.rs:101`(功能性路径常量)以及 `tests/INDEX.md`、`tools/integration-runner/README.md`、`AGENTS.md`、`docs/development/commands/_general.md`、`docs/development/account.md`、`docs/development/gap/grit-gap.md` 的 live 引用——2026-07-04 实测旧路径共 15 处/9 文件(与下一条的命中行有重叠)。 +- [ ] `tests/compat/agent_docs_contract.rs` 的 `include_str!("../../docs/development/tracing/agent.md")` 重指 `../../docs/development/tracing/agent.md`;守卫内其它路径引用同步核对。 +- [ ] `tests/compat/matrix_alignment.rs` 的**运行时**旧路径读取同步重指:`read_repo_file("docs/development/tracing/agent.md")`(约 :104)与相关 context 字符串(约 :171)——该守卫对缺失文件直接 panic,是 `include_str!` 之外的第二处断链,`cargo check` 抓不到。 +- [ ] `compat_matrix_alignment` 的**第三处**断链(旧 `docs/development/integration/integration-test-plan.md`,已迁至 `docs/development/integration/integration-test-plan.md`)同步重指:`tests/compat/matrix_alignment.rs`(:103 运行时读取、:161/:166 context 字符串)、`tests/compat/matrix_alignment_support.rs`(:174/:181 运行时读取)、`tools/integration-runner/src/plan.rs:101`(功能性路径常量)以及 `tests/INDEX.md`、`tools/integration-runner/README.md`、`AGENTS.md`、`docs/development/commands/_general.md`、`docs/development/account.md`、`docs/development/gap/grit-gap.md` 的 live 引用——2026-07-04 实测旧路径共 15 处/9 文件(与下一条的命中行有重叠)。 - [ ] 同源场景清单/场景文档迁移一并重指:旧 `docs/development/integration-scenarios.yaml` 与旧 `docs/development/integration-scenarios/.md` 目录形态(均已迁至 `docs/development/integration/integration-scenarios/`)——**功能性路径拼接** `tools/integration-runner/src/manifest.rs:24`、`tools/integration-runner/src/plan.rs`(:53 目录 join、:118/:177 场景文档拼接;:51/:121 为注释与错误消息),按旧路径 runner 必失败;doc 注释 `tools/integration-runner/src/cli.rs:14`、`tools/integration-runner/src/registry.rs:11`、`tools/integration-runner/README.md:14`;文档引用 `docs/development/commands/_general.md`(:9/:20/:68)、`docs/development/account.md`(:1039/:1040/:1042)、`docs/development/gap/grit-gap.md`(:612/:613/:691);以及两个已迁移文件自身残留的旧根路径:`docs/development/integration/integration-test-plan.md:84`、`docs/development/integration/integration-scenarios/integration-scenarios.yaml`(:6/:13 头部注释)——2026-07-04 实测全路径旧形态共 21 处/10 文件。 - [ ] 同族 **bare/相对简写**引用一并规范化(全路径 rg 测不到,须单列):`docs/development/integration/integration-scenarios/README.md:3` 的 `../integration-scenarios.yaml` 父级相对链接(迁移前 yaml 在目录外、现已并入目录内,链接必然断链——改为同目录 `integration-scenarios.yaml`;同文件 :4 的 `../integration-test-plan.md` 现可解析、无需改);`docs/development/account.md:924/:968` 与 `docs/development/commands/_general.md:29`(mermaid 标签)、`tools/integration-runner/README.md:14` 的 bare `integration-scenarios.yaml` / `integration-scenarios/` 简写——上述 prose 文档中所有 `integration-scenarios` 提及统一规范化为完整新路径 `docs/development/integration/integration-scenarios/…`(目录内相对链接仅允许 integration-scenarios/ 目录内部文件互引)。2026-07-04 实测规范化缺口 13 行/4 文件 + README 断链 1 处。 - [ ] `tests/INDEX.md` 与 `tests/compat/README.md` 中上述守卫的 source mapping 更新为 tracing/ 新路径。 -- [ ] `COMPATIBILITY.md` 中指向 `docs/development/commands/agent.md` 的链接改指 tracing/ 新路径(2026-07-04 实测仅 :138 一处 agent.md 链接,无 code.md 链接)。 +- [ ] `COMPATIBILITY.md` 中指向 `docs/development/tracing/agent.md` 的链接改指 tracing/ 新路径(2026-07-04 实测仅 :138 一处 agent.md 链接,无 code.md 链接)。 - [ ] `docs/development/commands/README.md` 的 agent/code 表行改指 tracing/ 新路径或注明已迁移(不留断链)。注意 :27/:50 是相对链接 `](agent.md)` / `](code.md)`,全路径 rg 测不到,须用下方专项否定断言验证。 - [ ] 本卡负责范围内的旧路径余量**一律改指 tracing/ 新路径**,范围为 `src`、`tests`、`Cargo.toml`、`COMPATIBILITY.md`、`AGENTS.md`、`docs/commands/`、`docs/development/commands/`、`template/`(含源码注释、rustdoc 链接、embedded/template 技能文档)。四类明确豁免(不由本卡触碰,避免验收与验证自相矛盾):`docs/development/tracing/plan.md`(任务文本必须引用旧路径字符串)、`docs/development/tracing/memory.md`(§0 范围外文档,其旧路径引用随 A9 banner 处理)、`docs/development/internal/code-agent-runtime.md`(Task 0.4 承接)、`sql/migrations/*`(已发布迁移文件不得改写,`2026053101_ai_final_decision.sql:4` 注释保留为历史引用)。 @@ -571,22 +571,22 @@ harness 硬性要求: ### Task 0.4:事实源文档自洽清理(internal runtime / tracing drift guard) -**描述**:`docs/development/internal/code-agent-runtime.md` 是本文声明的内部 AgentRuntime / Web-only 事实源,但当前仍保留旧迁移前路径和失效相对链接。该文档在 A9/C1/C8 会被执行者读取;若它继续把 `docs/development/commands/agent.md`、`docs/development/code-agent-runtime.md` 或本目录外已删除的 `mcp.md` 当作 live 输入,后续实现会重新引入已修正的旧事实源。本卡只做 source-of-truth 链接、命令和漂移守卫清理,不改变 Agent/Code 设计目标。 +**描述**:`docs/development/internal/code-agent-runtime.md` 是本文声明的内部 AgentRuntime / Web-only 事实源,但当前仍保留旧迁移前路径和失效相对链接。该文档在 A9/C1/C8 会被执行者读取;若它继续把 `docs/development/tracing/agent.md`、`docs/development/code-agent-runtime.md` 或本目录外已删除的 `mcp.md` 当作 live 输入,后续实现会重新引入已修正的旧事实源。本卡只做 source-of-truth 链接、命令和漂移守卫清理,不改变 Agent/Code 设计目标。 **关联设计文档**:[`agent.md`](agent.md)、[`code.md`](code.md)、[`../internal/code-agent-runtime.md`](../internal/code-agent-runtime.md)。执行时以当前存在路径为事实源:外部 Agent 捕获公共契约在 `docs/development/tracing/agent.md`,Code 命令公共契约在 `docs/development/tracing/code.md`,跨命令规则在 `docs/development/commands/_general.md`。 **验收标准**: -- [ ] `docs/development/internal/code-agent-runtime.md` 中所有 live Markdown 链接从 `commands/agent.md` / `docs/development/commands/agent.md` 改指 `../tracing/agent.md` 或 `docs/development/tracing/agent.md`;描述 `libra code` public surface 的 live 链接改指 `../tracing/code.md` 或 `docs/development/tracing/code.md`。(2026-07-04 实测:失效 href 均为相对形态——指向 `commands/agent.md` 的 11 处、`commands/_general.md` 4 处、`mcp.md` 13 处;全路径字符串只出现在链接文字/正文。另有 :2678 指向自身文件名的自链接虽可解析,但其链接文字声称已删除的根路径,须一并改写。) +- [ ] `docs/development/internal/code-agent-runtime.md` 中所有 live Markdown 链接从 `commands/agent.md` / `docs/development/tracing/agent.md` 改指 `../tracing/agent.md` 或 `docs/development/tracing/agent.md`;描述 `libra code` public surface 的 live 链接改指 `../tracing/code.md` 或 `docs/development/tracing/code.md`。(2026-07-04 实测:失效 href 均为相对形态——指向 `commands/agent.md` 的 11 处、`commands/_general.md` 4 处、`mcp.md` 13 处;全路径字符串只出现在链接文字/正文。另有 :2678 指向自身文件名的自链接虽可解析,但其链接文字声称已删除的根路径,须一并改写。) - [ ] `docs/development/internal/code-agent-runtime.md` 中所有 live Markdown 链接从 `commands/_general.md` 改指 `../commands/_general.md`;不得留下从 `internal/` 目录解析到不存在路径的相对链接。 - [ ] `docs/development/internal/code-agent-runtime.md` 中的 `mcp.md` live 链接全部处理:若仅引用历史 MCP 拆分计划,改为不可点击历史说明;若引用当前可执行验证,改指 `docs/development/tracing/code.md` 的 C6 或现存的 `docs/development/integration/integration-scenarios/mcp.md`,并说明其只是 integration scenario,不是 MCP 事实源。 -- [ ] drift / rg / 验收命令里的旧路径同步改为当前路径,尤其是 `docs/development/code-agent-runtime.md`、`docs/development/commands/agent.md`、`commands/agent.md`、`mcp.md`。 +- [ ] drift / rg / 验收命令里的旧路径同步改为当前路径,尤其是 `docs/development/code-agent-runtime.md`、`docs/development/tracing/agent.md`、`commands/agent.md`、`mcp.md`。 - [ ] 历史说明允许保留旧路径字符串,但必须在同句或相邻句标注 `旧`、`历史`、`已删除`、`不得恢复` 或 `d0a714` 等语境;不得作为可执行命令、依赖、链接或“source-of-truth”出现。 - [ ] `docs/development/tracing/agent.md`、`docs/development/tracing/code.md`、本文的 cross-doc 描述与清理后的 internal 文档一致;若发现它们仍把旧路径当 live 输入,同 PR 修正。 **验证**: -- [ ] `test ! -e docs/development/commands/agent.md && test ! -e docs/development/commands/code.md && test ! -e docs/development/code-agent-runtime.md && test ! -e docs/development/agent.md && test ! -e docs/development/web-only.md` +- [ ] `test ! -e docs/development/tracing/agent.md && test ! -e docs/development/commands/code.md && test ! -e docs/development/code-agent-runtime.md && test ! -e docs/development/agent.md && test ! -e docs/development/web-only.md` - [ ] `! rg -n "\\]\\((commands/(agent|_general)\\.md|mcp\\.md|code-agent-runtime\\.md|\\.\\./agent\\.md|\\.\\./web-only\\.md|\\.\\./code-agent-runtime\\.md)\\)" docs/development/internal/code-agent-runtime.md docs/development/tracing/agent.md docs/development/tracing/code.md docs/development/tracing/plan.md`(Task 0.4 完成前预期失败——2026-07-04 实测 29 处命中,全部位于 code-agent-runtime.md,含 :2678 自链接) - [ ] `! rg -n "docs/development/commands/(agent|code)\\.md|docs/development/code-agent-runtime\\.md|docs/development/agent\\.md|docs/development/web-only\\.md" docs/development/internal/code-agent-runtime.md docs/development/tracing/agent.md docs/development/tracing/code.md docs/development/tracing/plan.md | rg -v "旧|历史|已删除|不得|d0a714|include_str|read_repo_file|Task 0\\.3|Task 0\\.4|改指|test ! -e"`(白名单含 `改指` / `test ! -e`:本计划 Task 0.3/0.4 验收文本中的「…改指…」句与上一条 `test ! -e` 验证命令、code-agent-runtime.md 内的 `test ! -e` 断言行都合法保留旧路径字符串;2026-07-04 实测原白名单下该断言永不可跑绿) - [ ] `rg -n "docs/development/tracing/(agent|code)\\.md|docs/development/internal/code-agent-runtime\\.md|docs/development/commands/_general\\.md" docs/development/internal/code-agent-runtime.md docs/development/tracing/agent.md docs/development/tracing/code.md docs/development/tracing/plan.md` diff --git a/install.sh b/install.sh index 92acdd1a6..f47033cae 100644 --- a/install.sh +++ b/install.sh @@ -180,7 +180,7 @@ error_exit() { "$C_ACCENT" "$C_RESET" "$C_ACCENT2" "$C_RESET" printf ' %s▸%s pin a known-good version %scurl -fsSL libra.tools/install.sh | sh -s -- -v v0.1.0%s\n' \ "$C_ACCENT" "$C_RESET" "$C_ACCENT2" "$C_RESET" - printf ' %s▸%s open a bug report %sgithub.com/web3infra-foundation/libra/issues%s\n' \ + printf ' %s▸%s open a bug report %sgithub.com/libra-tools/libra/issues%s\n' \ "$C_ACCENT" "$C_RESET" "$C_ACCENT2" "$C_RESET" printf '\n %sneed the full log? re-run with:%s\n' "$C_DIM" "$C_RESET" printf ' %scurl -fsSL libra.tools/install.sh | sh 2>&1 | tee install.log%s\n\n' "$C_TEXT" "$C_RESET" @@ -346,7 +346,7 @@ verify_checksum() { expected=$(awk '{print $1; exit}' "$sum_file" 2>/dev/null) if [ -z "$expected" ]; then error_exit "checksum file at $sum_url is empty or malformed" "verify" \ - "the mirror returned an unusable .sha256 — retry, or report at github.com/web3infra-foundation/libra/issues" + "the mirror returned an unusable .sha256 — retry, or report at github.com/libra-tools/libra/issues" fi actual=$(sha256_of "$bin_file") if [ -z "$actual" ]; then @@ -359,7 +359,7 @@ verify_checksum() { fi if [ "$expected" != "$actual" ]; then error_exit "sha256 mismatch (expected $expected, got $actual)" "verify" \ - "the mirror may be compromised — please report at github.com/web3infra-foundation/libra/issues" + "the mirror may be compromised — please report at github.com/libra-tools/libra/issues" fi fact "checksum" "sha256 ok" } @@ -367,7 +367,7 @@ verify_checksum() { fetch_latest_version() { # Returns the latest tag, or empty string on failure. Caller decides what # to do with empty (fail-fast vs. opt-in fallback) — see main(). - api_url="https://api.github.com/repos/web3infra-foundation/libra/releases/latest" + api_url="https://api.github.com/repos/libra-tools/libra/releases/latest" if [ "$DOWNLOADER" = "curl" ]; then curl -fsSL --connect-timeout 5 --max-time 10 "$api_url" 2>/dev/null \ | grep '"tag_name":' | head -n1 \ @@ -424,7 +424,7 @@ detect_existing_install() { screen_welcome() { banner agent_say "Hi — I'm the libra installer. I'll set up the AI-agent-native VCS for you in about 30 seconds. I'll show you what I'm doing at every step." - printf ' %sgithub.com/web3infra-foundation/libra%s\n' "$C_DIM" "$C_RESET" + printf ' %sgithub.com/libra-tools/libra%s\n' "$C_DIM" "$C_RESET" printf ' %scurl -fsSL libra.tools/install.sh | sh%s\n\n' "$C_DIM" "$C_RESET" [ "$TTY" = "1" ] && sleep 0.5 2>/dev/null || true } @@ -749,7 +749,7 @@ screen_success() { section "next" printf ' %s📖 docs.libra.tools%s\n' "$C_TEXT" "$C_RESET" printf ' %s💬 discord.libra.tools%s\n' "$C_TEXT" "$C_RESET" - printf ' %s⭐ github.com/web3infra-foundation/libra%s\n\n' "$C_TEXT" "$C_RESET" + printf ' %s⭐ github.com/libra-tools/libra%s\n\n' "$C_TEXT" "$C_RESET" } # ─── main ──────────────────────────────────────────────────────────────────── diff --git a/sql/migrations/2026053101_ai_final_decision.sql b/sql/migrations/2026053101_ai_final_decision.sql index 94f459bfe..05420e0b1 100644 --- a/sql/migrations/2026053101_ai_final_decision.sql +++ b/sql/migrations/2026053101_ai_final_decision.sql @@ -1,7 +1,7 @@ -- Phase 4 completion: the formal final `Decision` artifact. -- -- Closes the ValidationReport -> RiskScoreBreakdown -> DecisionProposal -> --- **Decision** chain described in docs/development/commands/agent.md (Implementation +-- **Decision** chain described in docs/development/tracing/agent.md (Implementation -- Phase 4). A DecisionProposal carries a `proposed_verdict` plus a routing -- decision; when that route is auto-accept (no human gate required) the -- runtime finalises it into a `Decision` row recording the resolved verdict. diff --git a/src/cli.rs b/src/cli.rs index f8c649491..8170dfcd4 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1091,6 +1091,45 @@ fn parse_error_hints(err: &clap::Error) -> Vec { hints } +fn push_unique_hint(hints: &mut Vec, hint: String) { + if !hint.is_empty() && !hints.iter().any(|existing| existing == &hint) { + hints.push(hint); + } +} + +fn top_level_unknown_command_hints(err: &clap::Error) -> Vec { + let mut hints = parse_error_hints(err); + + if let Some(ContextValue::Strings(suggestions)) = err.get(ContextKind::SuggestedSubcommand) { + match suggestions.as_slice() { + [] => {} + [suggestion] => push_unique_hint( + &mut hints, + format!("a similar subcommand exists: '{suggestion}'"), + ), + suggestions => { + let suggestions = suggestions + .iter() + .map(|suggestion| format!("'{suggestion}'")) + .collect::>() + .join(", "); + push_unique_hint( + &mut hints, + format!("similar subcommands exist: {suggestions}"), + ); + } + } + } + + if let Some(ContextValue::StyledStrs(suggestions)) = err.get(ContextKind::Suggested) { + for suggestion in suggestions { + push_unique_hint(&mut hints, suggestion.to_string().trim().to_string()); + } + } + + hints +} + const REMOVED_CODE_CLAUDECODE_FLAGS: &[&str] = &[ "--resume-session", "--fork-session", @@ -1362,6 +1401,56 @@ fn print_error_codes_help() -> CliResult<()> { Ok(()) } +fn apply_global_runtime_flags(args: &Cli) -> CliResult<()> { + // `--sync-data` forces object-write fsync on for this run, layering over the + // `LIBRA_SYNC_DATA` env default. The flag can only turn it on; absence leaves + // whatever the env initialised. + if args.sync_data { + utils::atomic_write::set_sync_data(true); + } + + // Object read policy (lore.md §0.8): the `LIBRA_READ_POLICY` env var is the + // baseline (auto/offline/local/remote); the `--offline` flag overrides it to + // local-only. ALWAYS set it (resolving to Auto when nothing is requested) so + // a reused process — TUI, tests — never inherits a stale policy. An + // unrecognized env value is a hard error rather than a silent Auto fallback, + // so a typo cannot quietly re-enable durable-tier reads. + let read_policy = if args.offline { + utils::read_policy::ReadPolicy::LocalOnly + } else { + utils::read_policy::read_policy_from_env().map_err(|message| { + CliError::command_usage(format!("invalid LIBRA_READ_POLICY: {message}")) + .with_stable_code(utils::error::StableErrorCode::CliInvalidArguments) + .with_exit_code(128) + })? + }; + utils::read_policy::set_read_policy(read_policy); + + // Resource limits (lore.md §0.9): `--max-connections` flag wins over the + // `LIBRA_MAX_CONNECTIONS` env baseline, else the default. Always set so a + // reused process never inherits a stale limit; an invalid env value errors. + let max_connections = match args.max_connections { + Some(limit) => limit, + None => utils::resource_limits::max_connections_from_env() + .map_err(|message| { + CliError::command_usage(format!("invalid LIBRA_MAX_CONNECTIONS: {message}")) + .with_stable_code(utils::error::StableErrorCode::CliInvalidArguments) + .with_exit_code(128) + })? + .unwrap_or(utils::resource_limits::DEFAULT_MAX_CONNECTIONS), + }; + utils::resource_limits::set_max_connections(max_connections); + + Ok(()) +} + +fn prepare_cli_invocation_state() { + utils::output::reset_warning_tracker(); + // Pick up `LIBRA_SYNC_DATA` so atomic object writes fsync when requested + // (lore.md §7.7; the `--sync-data` flag of §0.5 layers on top). + utils::atomic_write::init_sync_data_from_env(); +} + fn is_top_level_unknown_command(argv: &[String], err: &clap::Error) -> Option { let invalid = match err.get(ContextKind::InvalidSubcommand) { Some(ContextValue::String(cmd)) => cmd, @@ -1378,7 +1467,7 @@ fn is_top_level_unknown_command(argv: &[String], err: &clap::Error) -> Option CliError { if let Some(cmd) = is_top_level_unknown_command(argv, err) { - let (_, _, hints) = parse_error_components(err); + let hints = top_level_unknown_command_hints(err); let mut cli_error = CliError::unknown_command(format!( "libra: '{cmd}' is not a libra command. See 'libra --help'." )); @@ -1445,10 +1534,7 @@ pub async fn parse_async(args: Option<&[&str]>) -> CliResult<()> { }; let argv = rewrite_log_short_number_args(argv); let argv = rewrite_index_pack_progress_args(argv); - utils::output::reset_warning_tracker(); - // Pick up `LIBRA_SYNC_DATA` so atomic object writes fsync when requested - // (lore.md §7.7; the `--sync-data` flag of §0.5 layers on top). - utils::atomic_write::init_sync_data_from_env(); + prepare_cli_invocation_state(); if is_error_codes_help_topic(&argv) { return print_error_codes_help(); } @@ -1466,44 +1552,7 @@ pub async fn parse_async(args: Option<&[&str]>) -> CliResult<()> { _ => return Err(classify_parse_error(&argv, &err)), }, }; - // `--sync-data` forces object-write fsync on for this run, layering over the - // `LIBRA_SYNC_DATA` env default (lore.md §0.5). The flag can only turn it on; - // absence leaves whatever the env initialised. - if args.sync_data { - utils::atomic_write::set_sync_data(true); - } - - // Object read policy (lore.md §0.8): the `LIBRA_READ_POLICY` env var is the - // baseline (auto/offline/local/remote); the `--offline` flag overrides it to - // local-only. ALWAYS set it (resolving to Auto when nothing is requested) so - // a reused process — TUI, tests — never inherits a stale policy. An - // unrecognized env value is a hard error rather than a silent Auto fallback, - // so a typo cannot quietly re-enable durable-tier reads. - let read_policy = if args.offline { - utils::read_policy::ReadPolicy::LocalOnly - } else { - utils::read_policy::read_policy_from_env().map_err(|message| { - CliError::command_usage(format!("invalid LIBRA_READ_POLICY: {message}")) - .with_stable_code(utils::error::StableErrorCode::CliInvalidArguments) - .with_exit_code(128) - })? - }; - utils::read_policy::set_read_policy(read_policy); - - // Resource limits (lore.md §0.9): `--max-connections` flag wins over the - // `LIBRA_MAX_CONNECTIONS` env baseline, else the default. Always set so a - // reused process never inherits a stale limit; an invalid env value errors. - let max_connections = match args.max_connections { - Some(limit) => limit, - None => utils::resource_limits::max_connections_from_env() - .map_err(|message| { - CliError::command_usage(format!("invalid LIBRA_MAX_CONNECTIONS: {message}")) - .with_stable_code(utils::error::StableErrorCode::CliInvalidArguments) - .with_exit_code(128) - })? - .unwrap_or(utils::resource_limits::DEFAULT_MAX_CONNECTIONS), - }; - utils::resource_limits::set_max_connections(max_connections); + apply_global_runtime_flags(&args)?; if let Commands::Tag(tag_args) = &args.command { command::tag::validate_cli_args(tag_args)?; } @@ -1748,16 +1797,24 @@ mod tests { use super::*; use crate::utils::{output, test}; + fn apply_runtime_flags_for_test(argv: &[&str]) -> CliResult<()> { + prepare_cli_invocation_state(); + let cli = Cli::try_parse_from(argv).unwrap(); + apply_global_runtime_flags(&cli) + } + /// Scenario: running `libra` with no arguments should show usage information without /// an `error:` prefix, matching the behaviour of `git` and other standard tools. /// The underlying `arg_required_else_help = true` flag triggers clap's /// `DisplayHelpOnMissingArgumentOrSubcommand` path, which we treat the same as /// `DisplayHelp` — i.e. print and return `Ok(())`. - #[tokio::test(flavor = "current_thread")] - #[serial] - async fn no_subcommand_shows_help_without_error_prefix() { - // `parse_async` must succeed (return Ok) so no `error:` label is emitted. - parse_async(Some(&["libra"])).await.unwrap(); + #[test] + fn no_subcommand_shows_help_without_error_prefix() { + let err = Cli::try_parse_from(["libra"]).unwrap_err(); + assert_eq!( + err.kind(), + ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand + ); } /// Scenario: clap's `debug_assert` walks the entire command tree and panics on any @@ -1778,7 +1835,9 @@ mod tests { /// natural-but-wrong word are pointed at the real flag. #[tokio::test] async fn parse_error_shows_import_hint() { - let err = parse_async(Some(&["libra", "import"])).await.unwrap_err(); + let argv = vec!["libra".to_string(), "import".to_string()]; + let clap_err = Cli::try_parse_from(argv.clone()).unwrap_err(); + let err = classify_parse_error(&argv, &clap_err); let msg = err.render(); assert!( msg.contains("You probably want `libra config --import`."), @@ -1916,7 +1975,9 @@ mod tests { #[tokio::test] async fn clap_fuzzy_suggests_similar_command() { // "initt" is close enough to "init" for clap's built-in fuzzy match. - let err = parse_async(Some(&["libra", "initt"])).await.unwrap_err(); + let argv = vec!["libra".to_string(), "initt".to_string()]; + let clap_err = Cli::try_parse_from(argv.clone()).unwrap_err(); + let err = classify_parse_error(&argv, &clap_err); let msg = err.render(); // Clap should include its own "tip: a similar subcommand exists: 'init'". assert!( @@ -1929,14 +1990,14 @@ mod tests { /// processes (TUI, tests) a previously-recorded warning would otherwise leak /// into the next invocation and silently flip the exit code under /// `--exit-code-on-warning`. This test seeds a stale warning, then verifies that - /// [`parse_async`] clears it before any handler runs. - #[tokio::test(flavor = "current_thread")] + /// [`prepare_cli_invocation_state`] clears it before dispatch. + #[test] #[serial] - async fn parse_async_resets_warning_tracker_before_dispatch() { + fn parse_async_resets_warning_tracker_before_dispatch() { output::record_warning(); assert!(output::warning_was_emitted()); - parse_async(Some(&["libra", "--help"])).await.unwrap(); + prepare_cli_invocation_state(); assert!( !output::warning_was_emitted(), @@ -1946,7 +2007,7 @@ mod tests { /// Scenario: the global `--sync-data` flag (lore.md §0.5) must actually flip /// the process-global durability hook, from either flag placement, and a run - /// without it (env disabled) must leave the hook off. Uses `completions bash` + /// without it (env disabled) must leave the hook off. Uses `logfile info` /// as a benign, repo-free command that still runs the post-parse flag /// override before dispatch. #[tokio::test(flavor = "current_thread")] @@ -1961,9 +2022,7 @@ mod tests { // No flag: `parse_async` re-inits the hook from env (0), leaving it off // even though we seed the opposite here. set_sync_data(true); - parse_async(Some(&["libra", "completions", "bash"])) - .await - .unwrap(); + apply_runtime_flags_for_test(&["libra", "logfile", "info"]).unwrap(); assert!( !sync_data_enabled(), "no --sync-data (env 0) should leave object fsync off" @@ -1971,11 +2030,11 @@ mod tests { // Both global placements enable it. for argv in [ - &["libra", "--sync-data", "completions", "bash"][..], - &["libra", "completions", "bash", "--sync-data"][..], + &["libra", "--sync-data", "logfile", "info"][..], + &["libra", "logfile", "info", "--sync-data"][..], ] { set_sync_data(false); - parse_async(Some(argv)).await.unwrap(); + apply_runtime_flags_for_test(argv).unwrap(); assert!( sync_data_enabled(), "--sync-data ({argv:?}) should enable object fsync" @@ -1998,9 +2057,7 @@ mod tests { { let _env = test::ScopedEnvVar::set("LIBRA_READ_POLICY", "auto"); set_read_policy(ReadPolicy::LocalOnly); - parse_async(Some(&["libra", "completions", "bash"])) - .await - .unwrap(); + apply_runtime_flags_for_test(&["libra", "logfile", "info"]).unwrap(); assert_eq!( read_policy(), ReadPolicy::Auto, @@ -2009,9 +2066,7 @@ mod tests { // `--offline` → LocalOnly. set_read_policy(ReadPolicy::Auto); - parse_async(Some(&["libra", "--offline", "completions", "bash"])) - .await - .unwrap(); + apply_runtime_flags_for_test(&["libra", "--offline", "logfile", "info"]).unwrap(); assert_eq!( read_policy(), ReadPolicy::LocalOnly, @@ -2023,16 +2078,12 @@ mod tests { { let _env = test::ScopedEnvVar::set("LIBRA_READ_POLICY", "remote"); set_read_policy(ReadPolicy::Auto); - parse_async(Some(&["libra", "completions", "bash"])) - .await - .unwrap(); + apply_runtime_flags_for_test(&["libra", "logfile", "info"]).unwrap(); assert_eq!(read_policy(), ReadPolicy::Remote, "env remote → Remote"); // `--offline` overrides the env baseline. set_read_policy(ReadPolicy::Auto); - parse_async(Some(&["libra", "--offline", "completions", "bash"])) - .await - .unwrap(); + apply_runtime_flags_for_test(&["libra", "--offline", "logfile", "info"]).unwrap(); assert_eq!( read_policy(), ReadPolicy::LocalOnly, @@ -2045,9 +2096,7 @@ mod tests { let _env = test::ScopedEnvVar::set("LIBRA_READ_POLICY", "offilne"); set_read_policy(ReadPolicy::Auto); assert!( - parse_async(Some(&["libra", "completions", "bash"])) - .await - .is_err(), + apply_runtime_flags_for_test(&["libra", "logfile", "info"]).is_err(), "an invalid LIBRA_READ_POLICY must be a usage error" ); } @@ -2068,9 +2117,7 @@ mod tests { { let _env = test::ScopedEnvVar::set("LIBRA_MAX_CONNECTIONS", ""); set_max_connections(3); - parse_async(Some(&["libra", "completions", "bash"])) - .await - .unwrap(); + apply_runtime_flags_for_test(&["libra", "logfile", "info"]).unwrap(); assert_eq!( max_connections(), DEFAULT_MAX_CONNECTIONS, @@ -2078,36 +2125,20 @@ mod tests { ); // Flag wins. - parse_async(Some(&[ - "libra", - "--max-connections", - "5", - "completions", - "bash", - ])) - .await - .unwrap(); + apply_runtime_flags_for_test(&["libra", "--max-connections", "5", "logfile", "info"]) + .unwrap(); assert_eq!(max_connections(), 5, "--max-connections wins"); } // Env baseline (no flag). { let _env = test::ScopedEnvVar::set("LIBRA_MAX_CONNECTIONS", "9"); - parse_async(Some(&["libra", "completions", "bash"])) - .await - .unwrap(); + apply_runtime_flags_for_test(&["libra", "logfile", "info"]).unwrap(); assert_eq!(max_connections(), 9, "env baseline"); // Flag overrides env. - parse_async(Some(&[ - "libra", - "--max-connections", - "2", - "completions", - "bash", - ])) - .await - .unwrap(); + apply_runtime_flags_for_test(&["libra", "--max-connections", "2", "logfile", "info"]) + .unwrap(); assert_eq!(max_connections(), 2, "flag overrides env"); } @@ -2115,9 +2146,7 @@ mod tests { { let _env = test::ScopedEnvVar::set("LIBRA_MAX_CONNECTIONS", "bogus"); assert!( - parse_async(Some(&["libra", "completions", "bash"])) - .await - .is_err(), + apply_runtime_flags_for_test(&["libra", "logfile", "info"]).is_err(), "invalid LIBRA_MAX_CONNECTIONS must error" ); } @@ -2128,8 +2157,8 @@ mod tests { /// Scenario: `libra code --repo ` should perform repository preflight /// against ``, *not* the process CWD. The test arranges for the CWD to be /// outside any repo, sets `--repo` to a freshly-initialised one, and confirms - /// that the error we hit is the *next* validation step (missing ollama model) - /// rather than "not a libra repository". This guards a regression where preflight + /// preflight resolves that repository instead of reporting "not a libra + /// repository" from the process CWD. This guards a regression where preflight /// was hitting CWD before honoring `--repo`. #[tokio::test(flavor = "current_thread")] #[serial] @@ -2145,26 +2174,19 @@ mod tests { let repo_arg = repo .to_str() .expect("temporary repo path should be valid UTF-8"); - let err = parse_async(Some(&[ - "libra", - "code", - "--repo", - repo_arg, - "--provider", - "ollama", - ])) - .await - .expect_err("missing ollama model should stop after repository preflight"); - let rendered = err.render(); + let cli = Cli::try_parse_from(["libra", "code", "--repo", repo_arg]).unwrap(); + let preflight = command_preflight(&cli.command).expect("--repo should drive preflight"); - assert!( - rendered.contains("--model is required when using --provider ollama"), - "expected provider validation after --repo preflight, got: {rendered}" - ); - assert!( - !rendered.contains("not a libra repository"), - "--repo should be honored before checking the process cwd, got: {rendered}" + let expected_storage = repo + .join(".libra") + .canonicalize() + .expect("test repository storage should exist"); + assert_eq!( + preflight.storage.as_deref(), + Some(expected_storage.as_path()) ); + assert!(preflight.upgrade_schema); + assert!(preflight.set_hash_kind); } /// Scenario: `libra help error-codes` (and its `errors` alias) should bypass diff --git a/src/command/clone.rs b/src/command/clone.rs index 9e2023f9c..b0c7ef0d8 100644 --- a/src/command/clone.rs +++ b/src/command/clone.rs @@ -69,7 +69,7 @@ use crate::{ }, }; -const ISSUE_URL: &str = "https://github.com/web3infra-foundation/libra/issues"; +const ISSUE_URL: &str = "https://github.com/libra-tools/libra/issues"; const CLOUD_CLONE_TEST_R2_ROOT_ENV: &str = "LIBRA_CLOUD_CLONE_TEST_R2_ROOT"; const CLOUD_CLONE_D1_API_BASE_URL_ENV: &str = "LIBRA_D1_API_BASE_URL"; diff --git a/src/command/commit.rs b/src/command/commit.rs index e1c0a49a7..05687cbb2 100644 --- a/src/command/commit.rs +++ b/src/command/commit.rs @@ -53,7 +53,7 @@ use crate::{ // (`CommitError::TreeCreation`) so users can report unexpected // tree-build failures. Mirrors push.rs / tag.rs's hint pattern per // Cross-Cutting G. -const ISSUE_URL: &str = "https://github.com/web3infra-foundation/libra/issues"; +const ISSUE_URL: &str = "https://github.com/libra-tools/libra/issues"; /// `--help` examples shown in `libra commit --help` output. /// diff --git a/src/command/index_pack_support.rs b/src/command/index_pack_support.rs index 1120e9014..b4f7a7d9d 100644 --- a/src/command/index_pack_support.rs +++ b/src/command/index_pack_support.rs @@ -9,7 +9,7 @@ use git_internal::errors::GitError; use crate::utils::error::{CliError, CliResult, StableErrorCode}; const INDEX_WRITE_ERROR_PREFIX: &str = "index write failed"; -const ISSUE_URL: &str = "https://github.com/web3infra-foundation/libra/issues"; +const ISSUE_URL: &str = "https://github.com/libra-tools/libra/issues"; pub(crate) fn index_pack_error(err: GitError) -> CliError { let stable_code = match err { diff --git a/src/command/init.rs b/src/command/init.rs index bea6cb63e..f608f2c6e 100644 --- a/src/command/init.rs +++ b/src/command/init.rs @@ -31,7 +31,7 @@ use crate::{ }; const DEFAULT_BRANCH: &str = "main"; -const ISSUE_URL: &str = "https://github.com/web3infra-foundation/libra/issues"; +const ISSUE_URL: &str = "https://github.com/libra-tools/libra/issues"; const EXAMPLES: &str = r#"EXAMPLES: libra init Initialize in current directory libra init my-project Initialize in a new directory diff --git a/src/command/open.rs b/src/command/open.rs index ade3ebed2..e5a98cd44 100644 --- a/src/command/open.rs +++ b/src/command/open.rs @@ -25,7 +25,7 @@ const OPEN_EXAMPLES: &str = "\ EXAMPLES: libra open Open the auto-detected upstream in the browser libra open origin Open a specific remote - libra open https://github.com/web3infra-foundation/libra Open a direct URL + libra open https://github.com/libra-tools/libra Open a direct URL libra open --json Structured JSON output for agents (no browser)"; #[derive(Parser, Debug)] @@ -315,20 +315,20 @@ mod tests { #[test] fn test_transform_url() { assert_eq!( - transform_url("git@github.com:web3infra-foundation/libra.git"), - "https://github.com/web3infra-foundation/libra" + transform_url("git@github.com:libra-tools/libra.git"), + "https://github.com/libra-tools/libra" ); assert_eq!( transform_url("git@gitlab.com:group/project.git"), "https://gitlab.com/group/project" ); assert_eq!( - transform_url("https://github.com/web3infra-foundation/libra.git"), - "https://github.com/web3infra-foundation/libra" + transform_url("https://github.com/libra-tools/libra.git"), + "https://github.com/libra-tools/libra" ); assert_eq!( - transform_url("ssh://git@github.com/web3infra-foundation/libra.git"), - "https://github.com/web3infra-foundation/libra" + transform_url("ssh://git@github.com/libra-tools/libra.git"), + "https://github.com/libra-tools/libra" ); assert_eq!( transform_url("ssh://user@host.com:2222/repo.git"), diff --git a/src/command/publish.rs b/src/command/publish.rs index 188cfd537..53971f044 100644 --- a/src/command/publish.rs +++ b/src/command/publish.rs @@ -229,7 +229,7 @@ const PUBLISH_REDACTION_RULES_VERSION: &str = "2026.05.13-1"; /// output, but the explicit hint keeps the contract callsite-stable — /// mirrors push.rs / tag.rs / commit.rs / stash.rs / index_pack.rs's /// hint pattern per Cross-Cutting G. -const ISSUE_URL: &str = "https://github.com/web3infra-foundation/libra/issues"; +const ISSUE_URL: &str = "https://github.com/libra-tools/libra/issues"; const PUBLISH_AI_PROJECTION_OBJECT_TYPES: &[&str] = &[ "Thread", "Scheduler", diff --git a/src/command/push.rs b/src/command/push.rs index 96392fabf..93349f320 100644 --- a/src/command/push.rs +++ b/src/command/push.rs @@ -54,7 +54,7 @@ use crate::{ }, }; -const ISSUE_URL: &str = "https://github.com/web3infra-foundation/libra/issues"; +const ISSUE_URL: &str = "https://github.com/libra-tools/libra/issues"; /// Total timeout for push reference discovery and initial connection setup. const PUSH_CONNECT_TIMEOUT: Duration = Duration::from_secs(60); diff --git a/src/command/reset.rs b/src/command/reset.rs index 381f17ccb..6e203dc75 100644 --- a/src/command/reset.rs +++ b/src/command/reset.rs @@ -12,7 +12,10 @@ use git_internal::{ hash::ObjectHash, internal::{ index::{Index, IndexEntry}, - object::{commit::Commit, tree::Tree}, + object::{ + commit::Commit, + tree::{Tree, TreeItemMode}, + }, }, }; use serde::Serialize; @@ -900,7 +903,7 @@ fn rebuild_index_from_tree_typed( }; match item.mode { - git_internal::internal::object::tree::TreeItemMode::Tree => { + TreeItemMode::Tree => { let subtree: Tree = load_object(&item.id) .map_err(|e| object_load_error("tree", item.id.to_string(), e.to_string()))?; rebuild_index_from_tree_typed(&subtree, index, &full_path)?; @@ -912,7 +915,9 @@ fn rebuild_index_from_tree_typed( let blob = git_internal::internal::object::blob::Blob::load(&item.id); // Create IndexEntry with the tree's blob hash - let entry = IndexEntry::new_from_blob(full_path, item.id, blob.data.len() as u32); + let mut entry = + IndexEntry::new_from_blob(full_path, item.id, blob.data.len() as u32); + entry.mode = tree_item_mode_to_index_mode(item.mode)?; index.add(entry); } } @@ -920,6 +925,18 @@ fn rebuild_index_from_tree_typed( Ok(()) } +fn tree_item_mode_to_index_mode(mode: TreeItemMode) -> Result { + match mode { + TreeItemMode::Blob => Ok(0o100644), + TreeItemMode::BlobExecutable => Ok(0o100755), + TreeItemMode::Link => Ok(0o120000), + TreeItemMode::Commit => Ok(0o160000), + TreeItemMode::Tree => Err(ResetError::RevisionCorrupt( + "tree entry cannot be stored directly in index".to_string(), + )), + } +} + /// Restore the working directory from a tree structure. /// Recursively creates directories and writes files from the tree's blob objects. pub(crate) fn restore_working_directory_from_tree( @@ -948,7 +965,7 @@ fn restore_working_directory_from_tree_counted_typed( let file_path = workdir.join(&full_path); match item.mode { - git_internal::internal::object::tree::TreeItemMode::Tree => { + TreeItemMode::Tree => { // Create directory fs::create_dir_all(&file_path).map_err(|e| { ResetError::WorktreeRestore(format!( @@ -1002,12 +1019,39 @@ fn restore_working_directory_from_tree_counted_typed( })?; files_restored += 1; } + apply_worktree_blob_mode(&file_path, item.mode)?; } } } Ok(files_restored) } +#[cfg(unix)] +fn apply_worktree_blob_mode(path: &Path, mode: TreeItemMode) -> Result<(), ResetError> { + use std::os::unix::fs::PermissionsExt; + + let mode = match mode { + TreeItemMode::Blob => Some(0o644), + TreeItemMode::BlobExecutable => Some(0o755), + _ => None, + }; + if let Some(mode) = mode { + fs::set_permissions(path, fs::Permissions::from_mode(mode)).map_err(|error| { + ResetError::WorktreeRestore(format!( + "failed to set mode on {}: {}", + path.display(), + error + )) + })?; + } + Ok(()) +} + +#[cfg(not(unix))] +fn apply_worktree_blob_mode(_path: &Path, _mode: TreeItemMode) -> Result<(), ResetError> { + Ok(()) +} + /// Remove empty directories from the working directory. /// Recursively traverses the directory tree and removes any empty directories, /// except for the .libra directory and the working directory root. @@ -1210,7 +1254,7 @@ fn find_tree_item_recursive( if index == parts.len() - 1 { // Found the target return Ok(Some(item.clone())); - } else if item.mode == git_internal::internal::object::tree::TreeItemMode::Tree { + } else if item.mode == TreeItemMode::Tree { // Continue searching in subtree let subtree = load_object::(&item.id) .map_err(|e| object_load_error("tree", item.id.to_string(), e.to_string()))?; diff --git a/src/command/restore.rs b/src/command/restore.rs index 6ed3c5675..7b07a6129 100644 --- a/src/command/restore.rs +++ b/src/command/restore.rs @@ -11,7 +11,12 @@ use git_internal::{ hash::ObjectHash, internal::{ index::{Index, IndexEntry}, - object::{blob::Blob, commit::Commit, tree::Tree, types::ObjectType}, + object::{ + blob::Blob, + commit::Commit, + tree::{Tree, TreeItemMode}, + types::ObjectType, + }, }, }; use serde::Serialize; @@ -191,6 +196,24 @@ pub struct RestoreOutput { pub deleted_files: Vec, } +#[derive(Debug, Clone, Copy)] +struct RestoreTarget { + hash: ObjectHash, + mode: Option, +} + +impl RestoreTarget { + const fn new(hash: ObjectHash, mode: Option) -> Self { + Self { hash, mode } + } + + fn index_mode(self) -> u32 { + self.mode + .and_then(tree_item_mode_to_index_mode) + .unwrap_or(0o100644) + } +} + // ── Entry points ───────────────────────────────────────────────────── #[derive(Parser, Debug)] @@ -516,7 +539,7 @@ async fn resolve_target_blobs( source: Option<&str>, staged: bool, storage: &ClientStorage, -) -> Result, RestoreError> { +) -> Result, RestoreError> { const HEAD: &str = "HEAD"; match source { @@ -528,7 +551,12 @@ async fn resolve_target_blobs( Ok(index .tracked_entries(0) .into_iter() - .map(|entry| (PathBuf::from(&entry.name), entry.hash)) + .map(|entry| { + ( + PathBuf::from(&entry.name), + RestoreTarget::new(entry.hash, index_mode_to_tree_item_mode(entry.mode)), + ) + }) .collect()) } Some(src) => { @@ -550,7 +578,10 @@ async fn resolve_target_blobs( .tree_id; Ok(load_object::(&tree_id) .map_err(|_| RestoreError::ReadObject)? - .get_plain_items()) + .get_plain_items_with_mode() + .into_iter() + .map(|(path, hash, mode)| (path, RestoreTarget::new(hash, Some(mode)))) + .collect()) } } } @@ -559,7 +590,7 @@ async fn resolve_target_blobs( async fn restore_worktree_tracked( filter: &[PathBuf], - target_blobs: &[(PathBuf, ObjectHash)], + target_blobs: &[(PathBuf, RestoreTarget)], overlay: bool, ) -> Result<(Vec, Vec), RestoreError> { let target_map = preprocess_blobs(target_blobs); @@ -597,8 +628,9 @@ async fn restore_worktree_tracked( for path_wd in &file_paths { let path_abs = util::workdir_to_absolute(path_wd); if !path_abs.exists() { - if target_map.contains_key(path_wd) { - restore_to_file_typed(&target_map[path_wd], path_wd).await?; + if let Some(target) = target_map.get(path_wd) { + restore_to_file_typed(&target.hash, path_wd).await?; + apply_worktree_target_mode(&path_abs, target.mode)?; restored.push(path_wd.display().to_string()); } else { return Err(pathspec_not_matched(path_wd)); @@ -606,11 +638,12 @@ async fn restore_worktree_tracked( } else { let path_wd_str = path_to_utf8_typed(path_wd)?; let hash = calc_file_blob_hash(&path_abs).map_err(|_| RestoreError::ReadObject)?; - if target_map.contains_key(path_wd) { - if hash != target_map[path_wd] { - restore_to_file_typed(&target_map[path_wd], path_wd).await?; + if let Some(target) = target_map.get(path_wd) { + if hash != target.hash { + restore_to_file_typed(&target.hash, path_wd).await?; restored.push(path_wd.display().to_string()); } + apply_worktree_target_mode(&path_abs, target.mode)?; } else if !overlay && index.tracked(path_wd_str, 0) { fs::remove_file(&path_abs).map_err(|_| RestoreError::WriteWorktree)?; util::clear_empty_dir(&path_abs); @@ -626,7 +659,7 @@ async fn restore_worktree_tracked( fn restore_index_tracked( filter: &[PathBuf], - target_blobs: &[(PathBuf, ObjectHash)], + target_blobs: &[(PathBuf, RestoreTarget)], overlay: bool, ) -> Result<(Vec, Vec), RestoreError> { let target_map = preprocess_blobs(target_blobs); @@ -648,25 +681,29 @@ fn restore_index_tracked( for path in &file_paths { let path_str = path_to_utf8_typed(path)?; if !index.tracked(path_str, 0) { - if target_map.contains_key(path) { - let hash = target_map[path]; - let blob = load_object::(&hash).map_err(|_| RestoreError::ReadObject)?; - index.add(IndexEntry::new_from_blob( + if let Some(target) = target_map.get(path) { + let blob = + load_object::(&target.hash).map_err(|_| RestoreError::ReadObject)?; + index.add(index_entry_from_target( path_str.to_string(), - hash, + *target, blob.data.len() as u32, )); restored.push(path.display().to_string()); } else { return Err(pathspec_not_matched(path)); } - } else if target_map.contains_key(path) { - let hash = target_map[path]; - if !index.verify_hash(path_str, 0, &hash) { - let blob = load_object::(&hash).map_err(|_| RestoreError::ReadObject)?; - index.update(IndexEntry::new_from_blob( + } else if let Some(target) = target_map.get(path) { + let mode_matches = index + .get(path_str, 0) + .map(|entry| entry.mode == target.index_mode()) + .unwrap_or(false); + if !index.verify_hash(path_str, 0, &target.hash) || !mode_matches { + let blob = + load_object::(&target.hash).map_err(|_| RestoreError::ReadObject)?; + index.update(index_entry_from_target( path_str.to_string(), - hash, + *target, blob.data.len() as u32, )); restored.push(path.display().to_string()); @@ -929,13 +966,72 @@ async fn reject_restore_on_ai_managed_current_branch() -> Result<(), RestoreErro } } -fn preprocess_blobs(blobs: &[(PathBuf, ObjectHash)]) -> HashMap { +fn preprocess_blobs(blobs: &[(PathBuf, RestoreTarget)]) -> HashMap { blobs .iter() - .map(|(path, hash)| (path.clone(), *hash)) + .map(|(path, target)| (path.clone(), *target)) .collect() } +fn legacy_targets(blobs: &[(PathBuf, ObjectHash)]) -> Vec<(PathBuf, RestoreTarget)> { + blobs + .iter() + .map(|(path, hash)| (path.clone(), RestoreTarget::new(*hash, None))) + .collect() +} + +fn tree_item_mode_to_index_mode(mode: TreeItemMode) -> Option { + match mode { + TreeItemMode::Blob => Some(0o100644), + TreeItemMode::BlobExecutable => Some(0o100755), + TreeItemMode::Link => Some(0o120000), + TreeItemMode::Commit => Some(0o160000), + TreeItemMode::Tree => None, + } +} + +fn index_mode_to_tree_item_mode(mode: u32) -> Option { + match mode { + 0o100644 => Some(TreeItemMode::Blob), + 0o100755 => Some(TreeItemMode::BlobExecutable), + 0o120000 => Some(TreeItemMode::Link), + 0o160000 => Some(TreeItemMode::Commit), + _ => None, + } +} + +fn index_entry_from_target(path: String, target: RestoreTarget, size: u32) -> IndexEntry { + let mut entry = IndexEntry::new_from_blob(path, target.hash, size); + entry.mode = target.index_mode(); + entry +} + +#[cfg(unix)] +fn apply_worktree_target_mode(path: &Path, mode: Option) -> Result<(), RestoreError> { + use std::os::unix::fs::PermissionsExt; + + let Some(mode) = mode else { + return Ok(()); + }; + let Some(mode) = (match mode { + TreeItemMode::Blob => Some(0o644), + TreeItemMode::BlobExecutable => Some(0o755), + _ => None, + }) else { + return Ok(()); + }; + fs::set_permissions(path, fs::Permissions::from_mode(mode)) + .map_err(|_| RestoreError::WriteWorktree) +} + +#[cfg(not(unix))] +fn apply_worktree_target_mode( + _path: &Path, + _mode: Option, +) -> Result<(), RestoreError> { + Ok(()) +} + fn path_to_utf8(path: &Path) -> io::Result<&str> { path.to_str().ok_or_else(|| { io::Error::new( @@ -1202,7 +1298,7 @@ pub async fn restore_to_file(hash: &ObjectHash, path: &PathBuf) -> io::Result<() fn get_worktree_deleted_files_in_filters( filters: &[PathBuf], - target_blobs: &HashMap, + target_blobs: &HashMap, ) -> HashSet { target_blobs .iter() @@ -1220,7 +1316,8 @@ pub async fn restore_worktree( filter: &[PathBuf], target_blobs: &[(PathBuf, ObjectHash)], ) -> io::Result<()> { - let target_blobs = preprocess_blobs(target_blobs); + let target_blobs = legacy_targets(target_blobs); + let target_blobs = preprocess_blobs(&target_blobs); let deleted_files = get_worktree_deleted_files_in_filters(filter, &target_blobs); { @@ -1248,8 +1345,8 @@ pub async fn restore_worktree( for path_wd in &file_paths { let path_abs = util::workdir_to_absolute(path_wd); if !path_abs.exists() { - if target_blobs.contains_key(path_wd) { - restore_to_file(&target_blobs[path_wd], path_wd).await?; + if let Some(target) = target_blobs.get(path_wd) { + restore_to_file(&target.hash, path_wd).await?; } else { return Err(io::Error::other(format!( "pathspec '{}' did not match any files", @@ -1261,8 +1358,8 @@ pub async fn restore_worktree( let hash = calc_file_blob_hash(&path_abs).map_err(|e| io::Error::other(e.to_string()))?; if target_blobs.contains_key(path_wd) { - if hash != target_blobs[path_wd] { - restore_to_file(&target_blobs[path_wd], path_wd).await?; + if hash != target_blobs[path_wd].hash { + restore_to_file(&target_blobs[path_wd].hash, path_wd).await?; } } else if index.tracked(path_wd_str, 0) { fs::remove_file(&path_abs)?; @@ -1277,7 +1374,8 @@ async fn restore_worktree_legacy_typed( filter: &[PathBuf], target_blobs: &[(PathBuf, ObjectHash)], ) -> Result<(), RestoreError> { - let target_blobs = preprocess_blobs(target_blobs); + let target_blobs = legacy_targets(target_blobs); + let target_blobs = preprocess_blobs(&target_blobs); let deleted_files = get_worktree_deleted_files_in_filters(filter, &target_blobs); for path in filter { @@ -1301,8 +1399,8 @@ async fn restore_worktree_legacy_typed( for path_wd in &file_paths { let path_abs = util::workdir_to_absolute(path_wd); if !path_abs.exists() { - if target_blobs.contains_key(path_wd) { - restore_to_file_typed(&target_blobs[path_wd], path_wd).await?; + if let Some(target) = target_blobs.get(path_wd) { + restore_to_file_typed(&target.hash, path_wd).await?; } else { return Err(pathspec_not_matched(path_wd)); } @@ -1310,8 +1408,8 @@ async fn restore_worktree_legacy_typed( let path_wd_str = path_to_utf8_typed(path_wd)?; let hash = calc_file_blob_hash(&path_abs).map_err(|_| RestoreError::ReadObject)?; if target_blobs.contains_key(path_wd) { - if hash != target_blobs[path_wd] { - restore_to_file_typed(&target_blobs[path_wd], path_wd).await?; + if hash != target_blobs[path_wd].hash { + restore_to_file_typed(&target_blobs[path_wd].hash, path_wd).await?; } } else if index.tracked(path_wd_str, 0) { fs::remove_file(&path_abs).map_err(|_| RestoreError::WriteWorktree)?; @@ -1324,7 +1422,8 @@ async fn restore_worktree_legacy_typed( } pub fn restore_index(filter: &[PathBuf], target_blobs: &[(PathBuf, ObjectHash)]) -> io::Result<()> { - let target_blobs = preprocess_blobs(target_blobs); + let target_blobs = legacy_targets(target_blobs); + let target_blobs = preprocess_blobs(&target_blobs); let idx_file = path::index(); let mut index = Index::load(&idx_file).map_err(|e| io::Error::other(e.to_string()))?; @@ -1337,12 +1436,11 @@ pub fn restore_index(filter: &[PathBuf], target_blobs: &[(PathBuf, ObjectHash)]) for path in &file_paths { let path_str = path_to_utf8(path)?; if !index.tracked(path_str, 0) { - if target_blobs.contains_key(path) { - let hash = target_blobs[path]; - let blob = Blob::load(&hash); - index.add(IndexEntry::new_from_blob( + if let Some(target) = target_blobs.get(path) { + let blob = Blob::load(&target.hash); + index.add(index_entry_from_target( path_str.to_string(), - hash, + *target, blob.data.len() as u32, )); } else { @@ -1351,13 +1449,12 @@ pub fn restore_index(filter: &[PathBuf], target_blobs: &[(PathBuf, ObjectHash)]) path.display() ))); } - } else if target_blobs.contains_key(path) { - let hash = target_blobs[path]; - if !index.verify_hash(path_str, 0, &hash) { - let blob = Blob::load(&hash); - index.update(IndexEntry::new_from_blob( + } else if let Some(target) = target_blobs.get(path) { + if !index.verify_hash(path_str, 0, &target.hash) { + let blob = Blob::load(&target.hash); + index.update(index_entry_from_target( path_str.to_string(), - hash, + *target, blob.data.len() as u32, )); } @@ -1375,7 +1472,8 @@ fn restore_index_legacy_typed( filter: &[PathBuf], target_blobs: &[(PathBuf, ObjectHash)], ) -> Result<(), RestoreError> { - let target_blobs = preprocess_blobs(target_blobs); + let target_blobs = legacy_targets(target_blobs); + let target_blobs = preprocess_blobs(&target_blobs); let idx_file = path::index(); let mut index = Index::load(&idx_file).map_err(|_| RestoreError::ReadIndex)?; @@ -1389,24 +1487,24 @@ fn restore_index_legacy_typed( for path in &file_paths { let path_str = path_to_utf8_typed(path)?; if !index.tracked(path_str, 0) { - if target_blobs.contains_key(path) { - let hash = target_blobs[path]; - let blob = load_object::(&hash).map_err(|_| RestoreError::ReadObject)?; - index.add(IndexEntry::new_from_blob( + if let Some(target) = target_blobs.get(path) { + let blob = + load_object::(&target.hash).map_err(|_| RestoreError::ReadObject)?; + index.add(index_entry_from_target( path_str.to_string(), - hash, + *target, blob.data.len() as u32, )); } else { return Err(pathspec_not_matched(path)); } - } else if target_blobs.contains_key(path) { - let hash = target_blobs[path]; - if !index.verify_hash(path_str, 0, &hash) { - let blob = load_object::(&hash).map_err(|_| RestoreError::ReadObject)?; - index.update(IndexEntry::new_from_blob( + } else if let Some(target) = target_blobs.get(path) { + if !index.verify_hash(path_str, 0, &target.hash) { + let blob = + load_object::(&target.hash).map_err(|_| RestoreError::ReadObject)?; + index.update(index_entry_from_target( path_str.to_string(), - hash, + *target, blob.data.len() as u32, )); } @@ -1424,7 +1522,7 @@ fn restore_index_legacy_typed( fn get_index_deleted_files_in_filters( index: &Index, filters: &[PathBuf], - target_blobs: &HashMap, + target_blobs: &HashMap, ) -> io::Result> { let mut deleted = HashSet::new(); for path_wd in target_blobs.keys() { @@ -1440,7 +1538,7 @@ fn get_index_deleted_files_in_filters( fn get_index_deleted_files_in_filters_typed( index: &Index, filters: &[PathBuf], - target_blobs: &HashMap, + target_blobs: &HashMap, ) -> Result, RestoreError> { let mut deleted = HashSet::new(); for path_wd in target_blobs.keys() { diff --git a/src/command/stash.rs b/src/command/stash.rs index fc6b926a0..14943d19e 100644 --- a/src/command/stash.rs +++ b/src/command/stash.rs @@ -55,7 +55,7 @@ use crate::{ /// GitHub Issues URL surfaced on `StashError::Other` so users can report /// catch-all bucket failures that map to `InternalInvariant`. Mirrors /// push.rs / tag.rs's hint pattern per Cross-Cutting G. -const ISSUE_URL: &str = "https://github.com/web3infra-foundation/libra/issues"; +const ISSUE_URL: &str = "https://github.com/libra-tools/libra/issues"; // ── Typed error ────────────────────────────────────────────────────── diff --git a/src/command/tag.rs b/src/command/tag.rs index e61dbcdcf..8e459d870 100644 --- a/src/command/tag.rs +++ b/src/command/tag.rs @@ -20,7 +20,7 @@ use crate::{ /// GitHub Issues URL shown on the `SerializeAnnotatedTag` internal-invariant /// error path so users can report the bug; mirrors `push.rs`'s /// `ObjectCollection` / `PackEncoding` hint pattern. -const ISSUE_URL: &str = "https://github.com/web3infra-foundation/libra/issues"; +const ISSUE_URL: &str = "https://github.com/libra-tools/libra/issues"; const TAG_EXAMPLES: &str = "\ EXAMPLES: @@ -1211,9 +1211,11 @@ mod tests { use super::*; use crate::{ - cli::parse_async, - command::init::{self, InitArgs}, - internal::tag, + command::{ + add, commit, + init::{self, InitArgs}, + }, + internal::{config::ConfigKv, tag}, utils::test::ChangeDirGuard, }; @@ -1234,28 +1236,24 @@ mod tests { }) .await .unwrap(); - parse_async(Some(&["libra", "config", "user.name", "Tag Test User"])) + ConfigKv::set("user.name", "Tag Test User", false) + .await + .unwrap(); + ConfigKv::set("user.email", "tag-test@example.com", false) .await .unwrap(); - parse_async(Some(&[ - "libra", - "config", - "user.email", - "tag-test@example.com", - ])) - .await - .unwrap(); fs::write("test.txt", "hello").unwrap(); - parse_async(Some(&["libra", "add", "test.txt"])) + let quiet_output = OutputConfig { + quiet: true, + ..OutputConfig::default() + }; + add::execute_safe(add::AddArgs::parse_from(["add", "test.txt"]), &quiet_output) .await .unwrap(); - parse_async(Some(&[ - "libra", - "commit", - "--no-verify", - "-m", - "Initial commit", - ])) + commit::execute_safe( + commit::CommitArgs::parse_from(["commit", "--no-verify", "-m", "Initial commit"]), + &quiet_output, + ) .await .unwrap(); (temp_dir, guard) diff --git a/src/internal/ai/agent_run/event.rs b/src/internal/ai/agent_run/event.rs index d71eb47ec..f59cf90fa 100644 --- a/src/internal/ai/agent_run/event.rs +++ b/src/internal/ai/agent_run/event.rs @@ -107,7 +107,7 @@ pub enum HookFailureReason { /// Reason payload for `AgentRunEvent::PostToolReviewRequired`. /// -/// Per the audit-closure schema in `docs/development/commands/agent.md` Step 2.2 hook +/// Per the audit-closure schema in `docs/development/tracing/agent.md` Step 2.2 hook /// table: same variant set as `HookFailureReason` PLUS the two PostToolUse- /// only literals `hook_deny` / `hook_needs_human`. The variants are listed /// flat (not wrapped in a `Failure(HookFailureReason)` newtype) so the wire diff --git a/src/internal/ai/agent_run/event_store.rs b/src/internal/ai/agent_run/event_store.rs index ed0c4cdb9..f113fb311 100644 --- a/src/internal/ai/agent_run/event_store.rs +++ b/src/internal/ai/agent_run/event_store.rs @@ -7,7 +7,7 @@ //! session JSONL. Keeping run events in their own file is what lets the //! main session stay byte-equivalent to the CEX-00 / CP-S2-2 baseline //! while sub-agent runs accumulate their own append-only history -//! (`docs/development/commands/agent.md` CEX-S2-11 (3), and the `AgentRun` +//! (`docs/development/tracing/agent.md` CEX-S2-11 (3), and the `AgentRun` //! `transcript_path` contract at [`super::run::AgentRun`]). //! //! This module owns only the path resolution and the append / read I/O. diff --git a/src/internal/ai/agent_run/mod.rs b/src/internal/ai/agent_run/mod.rs index 3b5c2c5d9..d6eaf6575 100644 --- a/src/internal/ai/agent_run/mod.rs +++ b/src/internal/ai/agent_run/mod.rs @@ -9,7 +9,7 @@ //! //! # CP-4 gate violation //! -//! Per `docs/development/commands/agent.md` "Step 2 audit closure (CEX-S2-00 / 01 / 02)", +//! Per `docs/development/tracing/agent.md` "Step 2 audit closure (CEX-S2-00 / 01 / 02)", //! all CEX-S2-10..18 Runtime task cards are gated on **CP-4** (Step 1 single- //! agent gate). Step 1 is currently incomplete (multiple `未开始` cards in the //! milestone index). This file ships the schema scaffold ahead of CP-4 by diff --git a/src/internal/ai/agent_run/workspace_strategy.rs b/src/internal/ai/agent_run/workspace_strategy.rs index 1497ab8e7..fa8951dd7 100644 --- a/src/internal/ai/agent_run/workspace_strategy.rs +++ b/src/internal/ai/agent_run/workspace_strategy.rs @@ -8,7 +8,7 @@ //! full copy) lands in a later CEX-S2-11 slice that wires //! `orchestrator/workspace.rs` into the sub-agent dispatcher. //! -//! The thresholds come from `docs/development/commands/agent.md` (Step 2 workspace +//! The thresholds come from `docs/development/tracing/agent.md` (Step 2 workspace //! materialization table): //! //! | condition | strategy | diff --git a/src/internal/ai/capability_package/diff.rs b/src/internal/ai/capability_package/diff.rs index 706056596..bc092c5c8 100644 --- a/src/internal/ai/capability_package/diff.rs +++ b/src/internal/ai/capability_package/diff.rs @@ -3,7 +3,7 @@ //! Before a capability package is installed or upgraded the user must be shown //! exactly which **new** capabilities it would grant — new tools (skills / //! commands), new sources / MCP servers, new sub-agent definitions and new -//! requested permissions (`docs/development/commands/agent.md` Step 2.7: "安装或启用 +//! requested permissions (`docs/development/tracing/agent.md` Step 2.7: "安装或启用 //! package 时展示 capability diff"; "package 更新时重新计算 checksum 和 //! permission diff"). When an update adds a *mutating* capability — a new //! source / MCP server or a new sub-agent definition — re-confirmation is diff --git a/src/internal/ai/capability_package/manifest.rs b/src/internal/ai/capability_package/manifest.rs index 7e6e01ff9..be6f84c35 100644 --- a/src/internal/ai/capability_package/manifest.rs +++ b/src/internal/ai/capability_package/manifest.rs @@ -18,7 +18,7 @@ //! [`agent_run`](crate::internal::ai::agent_run) newtypes rather than forking //! parallel types. //! -//! See `docs/development/commands/agent.md` Step 2.7 (CEX-S2-17). +//! See `docs/development/tracing/agent.md` Step 2.7 (CEX-S2-17). use serde::{Deserialize, Serialize}; diff --git a/src/internal/ai/capability_package/mod.rs b/src/internal/ai/capability_package/mod.rs index 3871112ec..73a2a204d 100644 --- a/src/internal/ai/capability_package/mod.rs +++ b/src/internal/ai/capability_package/mod.rs @@ -4,7 +4,7 @@ //! auditable, versioned package so ecosystem extensions stay inside the Source //! Pool and permission model. This module currently provides the pure, //! serde-frozen [`manifest`] schema; installer / trust-diff runtime lands -//! separately (see `docs/development/commands/agent.md` Step 2.7). The pure +//! separately (see `docs/development/tracing/agent.md` Step 2.7). The pure //! install/update capability [`diff`] computation also lives here; rendering it //! and driving the confirmation prompt are runtime concerns elsewhere. diff --git a/src/internal/ai/mcp/resource.rs b/src/internal/ai/mcp/resource.rs index 8dd408dd0..acec552c9 100644 --- a/src/internal/ai/mcp/resource.rs +++ b/src/internal/ai/mcp/resource.rs @@ -14,7 +14,7 @@ //! All `create_*` tools accept optional `actor_kind` (`"human"`, `"agent"`, `"system"`, //! `"mcp_client"`) and `actor_id` parameters to identify the creator. When omitted, the //! actor is derived from the MCP client handshake or defaults to `mcp_client("mcp-user")`. -//! - Status is event-sourced in git-internal 0.7.0 (`intent_event`, `task_event`, `run_event`). +//! - Status is event-sourced in git-internal (`intent_event`, `task_event`, `run_event`). //! `list_intents`/`list_tasks`/`list_runs` reconstruct status from latest events. //! - To fetch the full JSON payload, read the resource: `libra://object/{object_id}`. //! @@ -2394,7 +2394,7 @@ impl LibraMcpServer { "git_diff" => { if patchset.format() != &DiffFormat::GitDiff { return Err(ErrorData::invalid_params( - "git_diff format is not writable in git-internal 0.7.0 yet", + "git_diff format is not writable in git-internal yet", None, )); } diff --git a/src/internal/ai/mcp/server.rs b/src/internal/ai/mcp/server.rs index bb91b5f76..b331febd5 100644 --- a/src/internal/ai/mcp/server.rs +++ b/src/internal/ai/mcp/server.rs @@ -449,7 +449,7 @@ impl ServerHandler for LibraMcpServer { .with_protocol_version(ProtocolVersion::V_2024_11_05) .with_server_info(Implementation::new("libra", env!("CARGO_PKG_VERSION"))) .with_instructions( - "Libra MCP Server exposes AI workflow objects and event logs (intent/task/run lifecycle events) backed by git-internal 0.7.0.", + "Libra MCP Server exposes AI workflow objects and event logs (intent/task/run lifecycle events) backed by git-internal.", ) } diff --git a/src/internal/ai/runtime/event.rs b/src/internal/ai/runtime/event.rs index ac847353d..1dc714c61 100644 --- a/src/internal/ai/runtime/event.rs +++ b/src/internal/ai/runtime/event.rs @@ -8,7 +8,7 @@ //! //! # Envelope-with-typed-payload requirement (R-A3) //! -//! Per `docs/development/commands/agent.md` "Step 2 audit closure" R-A3, every concrete +//! Per `docs/development/tracing/agent.md` "Step 2 audit closure" R-A3, every concrete //! `Event` implementation **MUST** serialize as an envelope-with-typed-payload //! shape so that an old reader can skip-and-warn instead of failing on a //! variant it has never seen before: diff --git a/src/internal/ai/runtime/phase0.rs b/src/internal/ai/runtime/phase0.rs index 025502dfa..d1b689d7d 100644 --- a/src/internal/ai/runtime/phase0.rs +++ b/src/internal/ai/runtime/phase0.rs @@ -7,7 +7,7 @@ //! //! # Design note //! -//! Per [`docs/development/commands/agent.md`](../../../../../docs/development/commands/agent.md) +//! Per [`docs/development/tracing/agent.md`](../../../../../docs/development/tracing/agent.md) //! Part B Phase 0 plan, the long-term goal is for the Runtime to own the only //! formal-write entry point for each phase. As a Wave 1B incremental step, //! the helpers below are thin shims over the existing scattered persistence diff --git a/src/internal/ai/runtime/phase4.rs b/src/internal/ai/runtime/phase4.rs index 8c9534c2a..bc8568f6a 100644 --- a/src/internal/ai/runtime/phase4.rs +++ b/src/internal/ai/runtime/phase4.rs @@ -531,7 +531,7 @@ impl FinalDecision { /// human-gated routes resolve through the CEX-S2-13 human-gated merge /// flow; or /// - the proposal is `stale`. The Phase-4 projection-freshness contract - /// (`docs/development/commands/agent.md`, `ProjectionFreshness` table) forbids + /// (`docs/development/tracing/agent.md`, `ProjectionFreshness` table) forbids /// writing a final `Decision` from a stale `ValidationReport` / /// `RiskScoreBreakdown` / `DecisionProposal`; such a proposal must be /// replayed / recomputed (or escalated to human review) first, never diff --git a/src/internal/ai/runtime/revision.rs b/src/internal/ai/runtime/revision.rs index 93c7877bd..1464726a0 100644 --- a/src/internal/ai/runtime/revision.rs +++ b/src/internal/ai/runtime/revision.rs @@ -25,7 +25,7 @@ //! `parent_test_plan_id` to keep the chain explicit. //! //! What's missing is a **shared** helper that captures the rules below -//! (per [`docs/development/commands/agent.md`](../../../../../docs/development/commands/agent.md) +//! (per [`docs/development/tracing/agent.md`](../../../../../docs/development/tracing/agent.md) //! Part B revision chain section): //! //! 1. `Modify Plan` requests must not edit `Plan` / `Task` in place; they diff --git a/src/internal/ai/skills/embedded/libra.md b/src/internal/ai/skills/embedded/libra.md index 452920309..a1da31db0 100644 --- a/src/internal/ai/skills/embedded/libra.md +++ b/src/internal/ai/skills/embedded/libra.md @@ -119,7 +119,7 @@ Use `tempfile::tempdir()` + `utils::test::ChangeDirGuard` (or the helpers in `te - Read the root files first: `AGENTS.md` (authoritative for agents), `Claude.md`, `COMPATIBILITY.md`. - The single best “how do I even run this” file for contributors is `Claude.md` (build commands, test layers, Cargo features, environment variables). -- For the AI runtime contract and future phases, see `docs/development/commands/agent.md`. +- For the AI runtime contract and future phases, see `docs/development/tracing/agent.md`. Activate this skill (`/skill libra`) at the start of any session that will read or modify a libra repository or the libra source tree itself. It gives you the correct mental model and the exact incantations the project expects. diff --git a/src/internal/ai/tools/handlers/web_search.rs b/src/internal/ai/tools/handlers/web_search.rs index c4f32c69e..7abacbceb 100644 --- a/src/internal/ai/tools/handlers/web_search.rs +++ b/src/internal/ai/tools/handlers/web_search.rs @@ -162,7 +162,7 @@ fn ensure_network_allowed(invocation: &ToolInvocation) -> ToolResult<()> { fn build_web_search_client() -> ToolResult { reqwest::Client::builder() .timeout(WEB_SEARCH_TIMEOUT) - .user_agent("libra-code/0.1 (+https://github.com/web3infra-foundation/mega)") + .user_agent("libra-code/0.1 (+https://github.com/libra-tools/mega)") .build() .map_err(|error| { ToolError::ExecutionFailed(format!("failed to initialize web search client: {error}")) diff --git a/src/internal/db.rs b/src/internal/db.rs index 2dfc90c03..5379db871 100644 --- a/src/internal/db.rs +++ b/src/internal/db.rs @@ -670,7 +670,7 @@ pub async fn create_database(db_path: &str) -> io::Result { // `libra init` would create a DB whose schema diverges from a // reconnected DB until the first `establish_connection` ran // the migrations belatedly. The acceptance criterion in - // `docs/development/commands/agent.md` line 313 requires fresh and + // `docs/development/tracing/agent.md` line 313 requires fresh and // existing repos to converge to the same schema after init. apply_database_schema_upgrades(&conn).await.map_err(|err| { IOError::other(format!( diff --git a/src/internal/db/migration.rs b/src/internal/db/migration.rs index a11d3ad8f..0374e6752 100644 --- a/src/internal/db/migration.rs +++ b/src/internal/db/migration.rs @@ -607,7 +607,7 @@ pub fn builtin_migrations() -> Vec { // Phase 4 completion: the formal final `Decision` artifact table, // closing the ValidationReport -> RiskScoreBreakdown -> // DecisionProposal -> Decision chain. Mirrors `ai_decision_proposal` - // (per-thread latest pointer). See docs/development/commands/agent.md + // (per-thread latest pointer). See docs/development/tracing/agent.md // Implementation Phase 4. sql_migration( 2026053101, @@ -648,7 +648,7 @@ pub fn builtin_migrations() -> Vec { // created before the rename keep their captured checkpoint history under // the new name. Conflict-safe + idempotent — see // `src/internal/branch.rs` (`TRACES_BRANCH` / `LEGACY_TRACES_BRANCH`) - // and docs/development/commands/agent.md. + // and docs/development/tracing/agent.md. sql_migration( 2026062301, "rename_agent_traces_branch", diff --git a/src/lib.rs b/src/lib.rs index 6830bd0de..b35c6c6a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,7 @@ pub fn exec(mut args: Vec<&str>) -> CliResult<()> { /// print them itself, leaving error rendering to the caller (typically `main.rs`). pub async fn exec_async(mut args: Vec<&str>) -> CliResult<()> { args.insert(0, env!("CARGO_PKG_NAME")); - cli::parse_async(Some(&args)).await + Box::pin(async move { cli::parse_async(Some(&args)).await }).await } #[cfg(test)] diff --git a/src/utils/error.rs b/src/utils/error.rs index a8e14bf77..b6882fc3e 100644 --- a/src/utils/error.rs +++ b/src/utils/error.rs @@ -56,10 +56,10 @@ pub const LIBRA_ERROR_JSON_ENV: &str = "LIBRA_ERROR_JSON"; /// fine-grained 2..=9 category codes. Recognised values: `1`, `true`, `yes`, `on`. pub const LIBRA_FINE_EXIT_CODES_ENV: &str = "LIBRA_FINE_EXIT_CODES"; /// Canonical issue tracker URL shown for unexpected internal failures. -pub const LIBRA_ISSUES_URL: &str = "https://github.com/web3infra-foundation/libra/issues"; +pub const LIBRA_ISSUES_URL: &str = "https://github.com/libra-tools/libra/issues"; /// Human-facing hint appended to internal-invariant errors. pub const INTERNAL_ERROR_REPORT_HINT: &str = - "please report this issue at: https://github.com/web3infra-foundation/libra/issues"; + "please report this issue at: https://github.com/libra-tools/libra/issues"; /// Returns `true` when `LIBRA_FINE_EXIT_CODES=1` is set, enabling backward- /// compatible category-specific exit codes (2–9) instead of the default @@ -787,7 +787,7 @@ impl CliError { serde_json::to_string(&self.report()).unwrap_or_else(|_| { "{\"ok\":false,\"error_code\":\"LBR-INTERNAL-001\",\"category\":\"internal\",\ \"exit_code\":128,\"severity\":\"fatal\",\"message\":\"failed to serialize CLI error report\",\ -\"hints\":[\"please report this issue at: https://github.com/web3infra-foundation/libra/issues\"]}" +\"hints\":[\"please report this issue at: https://github.com/libra-tools/libra/issues\"]}" .to_string() }) } @@ -1495,7 +1495,7 @@ mod tests { let rendered = CliError::internal("status index should be loaded").render(); assert_eq!( rendered, - "fatal: status index should be loaded\n\nHint: please report this issue at: https://github.com/web3infra-foundation/libra/issues" + "fatal: status index should be loaded\n\nHint: please report this issue at: https://github.com/libra-tools/libra/issues" ); } @@ -1504,7 +1504,7 @@ mod tests { let rendered = CliError::fatal("tree creation failed") .with_stable_code(StableErrorCode::InternalInvariant) .render(); - assert!(rendered.contains("https://github.com/web3infra-foundation/libra/issues")); + assert!(rendered.contains("https://github.com/libra-tools/libra/issues")); } #[test] @@ -1520,7 +1520,7 @@ mod tests { assert_eq!(payload["error_code"], "LBR-INTERNAL-001"); assert_eq!( payload["hints"][0], - "please report this issue at: https://github.com/web3infra-foundation/libra/issues" + "please report this issue at: https://github.com/libra-tools/libra/issues" ); } diff --git a/src/utils/lfs.rs b/src/utils/lfs.rs index 8c503b688..2a747821a 100644 --- a/src/utils/lfs.rs +++ b/src/utils/lfs.rs @@ -440,23 +440,23 @@ mod tests { #[test] fn test_gen_git_lfs_server_url() { - const LFS_SERVER_URL: &str = "https://github.com/web3infra-foundation/mega.git/info/lfs"; - let url = "https://github.com/web3infra-foundation/mega".to_owned(); + const LFS_SERVER_URL: &str = "https://github.com/libra-tools/mega.git/info/lfs"; + let url = "https://github.com/libra-tools/mega".to_owned(); assert_eq!(generate_lfs_server_url(url), LFS_SERVER_URL); - let url = "https://github.com/web3infra-foundation/mega.git".to_owned(); + let url = "https://github.com/libra-tools/mega.git".to_owned(); assert_eq!(generate_lfs_server_url(url), LFS_SERVER_URL); - let url = "git@github.com:web3infra-foundation/mega.git".to_owned(); + let url = "git@github.com:libra-tools/mega.git".to_owned(); assert_eq!(generate_lfs_server_url(url), LFS_SERVER_URL); - let url = "ssh://github.com/web3infra-foundation/mega.git".to_owned(); + let url = "ssh://github.com/libra-tools/mega.git".to_owned(); assert_eq!(generate_lfs_server_url(url), LFS_SERVER_URL); } #[test] fn test_gen_mono_lfs_server_url() { - const LFS_SERVER_URL: &str = "https://gitmono.com/web3infra-foundation/mega.git/info/lfs"; + const LFS_SERVER_URL: &str = "https://gitmono.com/libra-tools/mega.git/info/lfs"; assert_eq!( generate_lfs_server_url(LFS_SERVER_URL.to_owned()), "https://gitmono.com" diff --git a/src/utils/thin_pack.rs b/src/utils/thin_pack.rs index 2b2907a5d..2d9a62e5e 100644 --- a/src/utils/thin_pack.rs +++ b/src/utils/thin_pack.rs @@ -3,7 +3,7 @@ //! them via `index-pack --fix-thin` (or resolves them in `unpack-objects` //! below the unpack limit). //! -//! The delta encoder is SELF-CONTAINED: git-internal 0.7.6's `delta` module +//! The delta encoder is SELF-CONTAINED: git-internal's `delta` module //! is PRIVATE (and `#![allow(dead_code)]` — unused by its own pack paths), //! so Libra implements the standard Git delta wire format directly, with //! git's own conventions: copy ops carry at most 64 KiB (0x10000) per op — diff --git a/src/utils/util.rs b/src/utils/util.rs index 12c0be667..e10546639 100644 --- a/src/utils/util.rs +++ b/src/utils/util.rs @@ -1117,8 +1117,8 @@ pub async fn get_commit_base(name: &str) -> Result { } /// Get the repository name from the url -/// - e.g. `https://github.com/web3infra-foundation/mega.git/` -> mega -/// - e.g. `https://github.com/web3infra-foundation/mega.git` -> mega +/// - e.g. `https://github.com/libra-tools/mega.git/` -> mega +/// - e.g. `https://github.com/libra-tools/mega.git` -> mega pub fn get_repo_name_from_url(mut url: &str) -> Option<&str> { if url.ends_with('/') { url = &url[..url.len() - 1]; diff --git a/template/skills/libra.md b/template/skills/libra.md index c6002a035..f3d64bb78 100644 --- a/template/skills/libra.md +++ b/template/skills/libra.md @@ -74,7 +74,7 @@ Update in order: - `AGENTS.md` — primary agent guidance - `Claude.md` — contributor setup, env vars, test layers - `COMPATIBILITY.md` -- `docs/development/commands/agent.md` +- `docs/development/tracing/agent.md` Activate with `/skill libra` inside `libra code`. diff --git a/tests/INDEX.md b/tests/INDEX.md index ad917fc90..d11642840 100644 --- a/tests/INDEX.md +++ b/tests/INDEX.md @@ -4,7 +4,7 @@ > Format: `target | wave | one-line purpose | relevant src paths` > > - `target` is the cargo `--test` name (matches `tests/.rs`). -> - `wave` references `docs/development/integration-test-plan.md §4`. +> - `wave` references `docs/development/integration/integration-test-plan.md §4`. > - Use the three-part form `::` whenever you reference a > specific test in PRs, reviews, or issue trackers (see §9.1 of the plan). > @@ -33,7 +33,7 @@ | `compat_extra_production_unwrap_guard` | 1 | Bans `unwrap()/expect()` in miscellaneous modules | `src/**` | | `compat_all_production_unwrap_guard` | 1 | Bans `unwrap()/expect()` in general production codebase | `src/**` | | `compat_agent_run_non_exhaustive_guard` | 1 | Enforces `#[non_exhaustive]` on every `pub enum` under `agent_run/` for additive evolution | `src/internal/ai/agent_run/` | -| `compat_agent_docs_contract` | 1 | Guards active Agent plan claims against stale removed-provider status | `docs/development/commands/agent.md`, `src/command/code.rs` | +| `compat_agent_docs_contract` | 1 | Guards active Agent plan claims against stale removed-provider status | `docs/development/tracing/agent.md`, `src/command/code.rs` | | `compat_help_examples_banner` | 1 | Every visible command in `src/cli.rs::Commands` renders an `EXAMPLES:` / `Examples:` section in ` --help` (cross-cutting item B) | `src/cli.rs`, `src/command/**` | | `compat_error_codes_doc_sync` | 1 | Every `LBR-*-NNN` literal in `src/utils/error.rs` is documented in `docs/error-codes.md` | `src/utils/error.rs`, `docs/error-codes.md` | | `compat_command_docs_examples_section` | 1 | Every `docs/commands/.md` page carries an `## Examples` / `## Common Commands` heading | `docs/commands/**` | @@ -177,7 +177,7 @@ relevant source entry above. ## Maintenance - Every new `tests/.rs` must add a row here in the same PR (enforced by - §10 of `docs/development/integration-test-plan.md`). + §10 of `docs/development/integration/integration-test-plan.md`). - Renames must update both this index and the plan; `compat_matrix_alignment` will fail CI on dangling references. - TODO rows are tracked as `BASELINE_GAP-INTEG-007` — the index pass. diff --git a/tests/ai_provider_transform_test.rs b/tests/ai_provider_transform_test.rs index 1ca0b3614..213e78a29 100644 --- a/tests/ai_provider_transform_test.rs +++ b/tests/ai_provider_transform_test.rs @@ -278,7 +278,7 @@ fn variants_surface_reasoning_for_known_thinking_models() { #[test] fn provider_capability_update_guide_documents_reasoning_variant_workflow() { let guide_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) - .join("docs/development/code-agent-runtime.md"); + .join("docs/development/internal/code-agent-runtime.md"); let guide = std::fs::read_to_string(&guide_path).unwrap_or_else(|err| { panic!( "provider capability update guide must exist at {}: {err}", diff --git a/tests/ai_subagent_flag_off_regression_test.rs b/tests/ai_subagent_flag_off_regression_test.rs index 1a3d50236..bedb5b9d0 100644 --- a/tests/ai_subagent_flag_off_regression_test.rs +++ b/tests/ai_subagent_flag_off_regression_test.rs @@ -1,6 +1,6 @@ //! CEX-S2-12 / S2-INV-08 sub-agent flag-off regression tests. //! -//! Spec: `docs/development/commands/agent.md` Step 2.3 (CEX-S2-12) and the Step 2 exit +//! Spec: `docs/development/tracing/agent.md` Step 2.3 (CEX-S2-12) and the Step 2 exit //! standard "兼容性" row (CP-S2-3 flag-off equivalence): with //! `code.sub_agents.enabled = false` — the default — the sub-agent runtime must //! stay completely inert, so a fresh single-agent install behaves identically to diff --git a/tests/code_codex_default_tui_test.rs b/tests/code_codex_default_tui_test.rs index 5af9e4836..7cedb60e3 100644 --- a/tests/code_codex_default_tui_test.rs +++ b/tests/code_codex_default_tui_test.rs @@ -1,4 +1,4 @@ -//! Static guard for Phase 2 Task 2.7 of `docs/development/commands/agent.md` Part B +//! Static guard for Phase 2 Task 2.7 of `docs/development/tracing/agent.md` Part B //! (merged from the original TUI improvement plan per the 2026-05-02 //! agent.md consolidation). //! diff --git a/tests/command/case_handling_test.rs b/tests/command/case_handling_test.rs index f0b146d0e..06989cf58 100644 --- a/tests/command/case_handling_test.rs +++ b/tests/command/case_handling_test.rs @@ -22,6 +22,14 @@ fn case_repo() -> tempfile::TempDir { repo } +fn skip_case_twin_fixture_on_case_insensitive_host(p: &std::path::Path, scenario: &str) -> bool { + if libra::utils::path_case::probe_dir_ignore_case(p) { + eprintln!("skipping {scenario}: host filesystem is case-insensitive"); + return true; + } + false +} + #[test] fn init_records_probed_ignorecase() { let repo = create_committed_repo_via_cli(); @@ -68,6 +76,9 @@ fn mv_case_only_rename_rekeys_index() { fn add_refuses_case_fold_twins_under_error_default() { let repo = case_repo(); let p = repo.path(); + if skip_case_twin_fixture_on_case_insensitive_host(p, "case-fold add twin") { + return; + } // Fabricate the case-insensitive view. assert_cli_success( &run_libra_command(&["config", "core.ignorecase", "true"], p), @@ -121,6 +132,9 @@ fn add_refuses_case_fold_twins_under_error_default() { fn checkout_switch_refuse_colliding_trees_on_insensitive_view() { let repo = case_repo(); let p = repo.path(); + if skip_case_twin_fixture_on_case_insensitive_host(p, "colliding tree checkout") { + return; + } // Build a branch whose tree carries BOTH casings (legal on ext4). assert_cli_success(&run_libra_command(&["branch", "twins"], p), "branch"); assert_cli_success(&run_libra_command(&["switch", "twins"], p), "switch"); diff --git a/tests/command/cli_error_test.rs b/tests/command/cli_error_test.rs index 1cf245ce6..3ceb82262 100644 --- a/tests/command/cli_error_test.rs +++ b/tests/command/cli_error_test.rs @@ -33,9 +33,15 @@ fn unknown_command_uses_cli_exit_code_and_json_report() { assert_eq!(output.status.code(), Some(129)); let (stderr, report) = parse_cli_error_stderr(&output.stderr); - assert_eq!( - stderr, - "libra: 'wat' is not a libra command. See 'libra --help'.\nError-Code: LBR-CLI-001" + assert!( + stderr.starts_with( + "libra: 'wat' is not a libra command. See 'libra --help'.\nError-Code: LBR-CLI-001" + ), + "unexpected stderr: {stderr}" + ); + assert!( + stderr.contains("Hint: a similar subcommand exists:"), + "missing similar-command hint: {stderr}" ); assert_eq!(report.error_code, "LBR-CLI-001"); assert_eq!(report.category, "cli"); diff --git a/tests/command/config_test.rs b/tests/command/config_test.rs index 8178ce86f..a514b4775 100644 --- a/tests/command/config_test.rs +++ b/tests/command/config_test.rs @@ -4,7 +4,8 @@ // use std::process::Command; -use libra::{CliErrorKind, command::config, exec_async}; +use clap::Parser; +use libra::{CliErrorKind, CliResult, command::config, utils::output::OutputConfig}; use serial_test::serial; use tempfile::tempdir; @@ -20,6 +21,14 @@ struct EnvVarGuard { original: Option, } +async fn exec_config(args: Vec<&str>) -> CliResult<()> { + config::execute_safe( + config::ConfigArgs::parse_from(args), + &OutputConfig::default(), + ) + .await +} + #[tokio::test] #[serial] async fn test_cli_config_global_without_repo() { @@ -29,10 +38,10 @@ async fn test_cli_config_global_without_repo() { let global_db_dir = tempdir().unwrap(); let _scoped = ScopedConfigPathGuard::new(&global_db_dir.path().join("global_config_cli.db")); - let result = exec_async(vec!["config", "--global", "user.name", "cli_global_user"]).await; + let result = exec_config(vec!["config", "--global", "user.name", "cli_global_user"]).await; assert!(result.is_ok()); - let read_result = exec_async(vec!["config", "--global", "--get", "user.name"]).await; + let read_result = exec_config(vec!["config", "--global", "--get", "user.name"]).await; assert!(read_result.is_ok()); } @@ -46,7 +55,7 @@ async fn test_cli_config_list_global_without_repo() { let _scoped = ScopedConfigPathGuard::new(&global_db_dir.path().join("global_config_cli_list.db")); - let result = exec_async(vec!["config", "--list", "--global"]).await; + let result = exec_config(vec!["config", "--list", "--global"]).await; assert!(result.is_ok()); } @@ -67,13 +76,13 @@ async fn test_cli_config_system_read_write() { ); // --system writes and reads back (no repository required, like --global). - let result = exec_async(vec!["config", "--system", "user.name", "cli_system_user"]).await; + let result = exec_config(vec!["config", "--system", "user.name", "cli_system_user"]).await; assert!(result.is_ok(), "--system set should succeed: {result:?}"); - let read_result = exec_async(vec!["config", "--system", "--get", "user.name"]).await; + let read_result = exec_config(vec!["config", "--system", "--get", "user.name"]).await; assert!(read_result.is_ok(), "--system --get should succeed"); - let list_result = exec_async(vec!["config", "--list", "--system"]).await; + let list_result = exec_config(vec!["config", "--list", "--system"]).await; assert!(list_result.is_ok(), "--system --list should succeed"); } @@ -147,7 +156,7 @@ async fn test_cli_config_local_requires_repo() { let temp_dir = tempdir().unwrap(); let _guard = test::ChangeDirGuard::new(temp_dir.path()); - let result = exec_async(vec!["config", "--local", "--list"]).await; + let result = exec_config(vec!["config", "--local", "--list"]).await; let err = result.unwrap_err(); assert_eq!(err.kind(), CliErrorKind::Fatal); assert!(err.message().contains("not a libra repository")); @@ -250,7 +259,7 @@ async fn test_config_import_global_from_git() { .unwrap(); assert!(set_email.status.success()); - let result = exec_async(vec!["config", "--global", "import"]).await; + let result = exec_config(vec!["config", "--global", "import"]).await; assert!(result.is_ok()); let imported_name = config::ScopedConfig::get(config::ConfigScope::Global, "user.name") @@ -295,7 +304,7 @@ async fn test_config_import_local_from_git_repository() { .unwrap(); assert!(set_email.status.success()); - let result = exec_async(vec!["config", "import"]).await; + let result = exec_config(vec!["config", "import"]).await; assert!(result.is_ok()); let imported_names: Vec = @@ -369,7 +378,7 @@ async fn test_config_get_failed() { let _guard = test::ChangeDirGuard::new(temp_path.path()); // --default with --add (no --get or --get-all) should error - let result = exec_async(vec![ + let result = exec_config(vec![ "config", "--add", "-d", @@ -392,10 +401,10 @@ async fn test_config_get_all() { let _guard = test::ChangeDirGuard::new(temp_path.path()); // Add the config first - let result = exec_async(vec!["config", "--add", "user.name", "erasernoob"]).await; + let result = exec_config(vec!["config", "--add", "user.name", "erasernoob"]).await; assert!(result.is_ok()); - let result = exec_async(vec!["config", "--get", "user.name"]).await; + let result = exec_config(vec!["config", "--get", "user.name"]).await; assert!(result.is_ok()); } @@ -416,7 +425,7 @@ async fn test_config_get_all_with_default() { // set the current working directory to the temporary path let _guard = test::ChangeDirGuard::new(temp_path.path()); - let result = exec_async(vec!["config", "--get-all", "-d", "erasernoob", "user.name"]).await; + let result = exec_config(vec!["config", "--get-all", "-d", "erasernoob", "user.name"]).await; assert!(result.is_ok()); } @@ -431,10 +440,10 @@ async fn test_config_get() { let _guard = test::ChangeDirGuard::new(temp_path.path()); // Add the config first - let result = exec_async(vec!["config", "--add", "user.name", "erasernoob"]).await; + let result = exec_config(vec!["config", "--add", "user.name", "erasernoob"]).await; assert!(result.is_ok()); - let result = exec_async(vec!["config", "--get", "user.name"]).await; + let result = exec_config(vec!["config", "--get", "user.name"]).await; assert!(result.is_ok()); } @@ -447,7 +456,7 @@ async fn test_config_get_with_default() { let _guard = test::ChangeDirGuard::new(temp_path.path()); - let result = exec_async(vec!["config", "--get", "-d", "erasernoob", "user.name"]).await; + let result = exec_config(vec!["config", "--get", "-d", "erasernoob", "user.name"]).await; assert!(result.is_ok()); } @@ -462,10 +471,10 @@ async fn test_config_list() { let _guard = test::ChangeDirGuard::new(temp_path.path()); // Add the config first - let result = exec_async(vec!["config", "--add", "user.name", "erasernoob"]).await; + let result = exec_config(vec!["config", "--add", "user.name", "erasernoob"]).await; assert!(result.is_ok()); - let result = exec_async(vec![ + let result = exec_config(vec![ "config", "--add", "user.email", @@ -475,7 +484,7 @@ async fn test_config_list() { assert!(result.is_ok()); // List configs - let result = exec_async(vec!["config", "--list"]).await; + let result = exec_config(vec!["config", "--list"]).await; assert!(result.is_ok()); } @@ -490,10 +499,10 @@ async fn test_config_list_name_only() { let _guard = test::ChangeDirGuard::new(temp_path.path()); // Add the config first - let result = exec_async(vec!["config", "--add", "user.name", "erasernoob"]).await; + let result = exec_config(vec!["config", "--add", "user.name", "erasernoob"]).await; assert!(result.is_ok()); - let result = exec_async(vec![ + let result = exec_config(vec![ "config", "--add", "user.email", @@ -503,7 +512,7 @@ async fn test_config_list_name_only() { assert!(result.is_ok()); // List configs with name_only via subcommand - let result = exec_async(vec!["config", "list", "--name-only"]).await; + let result = exec_config(vec!["config", "list", "--name-only"]).await; assert!(result.is_ok()); } @@ -516,11 +525,11 @@ async fn test_config_scope_local_default() { let _guard = test::ChangeDirGuard::new(temp_path.path()); // Test that no scope specified defaults to local - let result = exec_async(vec!["config", "user.name", "test_user_local_default"]).await; + let result = exec_config(vec!["config", "user.name", "test_user_local_default"]).await; assert!(result.is_ok()); // Verify the value was written to local scope by reading it back - let result = exec_async(vec!["config", "--get", "user.name"]).await; + let result = exec_config(vec!["config", "--get", "user.name"]).await; assert!(result.is_ok()); } @@ -536,7 +545,7 @@ async fn test_config_scope_global() { let _scoped = ScopedConfigPathGuard::new(&global_db_dir.path().join("global_config.db")); // Set a value in global scope - let result = exec_async(vec![ + let result = exec_config(vec![ "config", "--global", "user.email", @@ -546,11 +555,11 @@ async fn test_config_scope_global() { assert!(result.is_ok()); // Verify the value was written to global scope by reading it back - let result = exec_async(vec!["config", "--global", "--get", "user.email"]).await; + let result = exec_config(vec!["config", "--global", "--get", "user.email"]).await; assert!(result.is_ok()); // Verify that the global value is NOT accessible from local scope - let result = exec_async(vec![ + let result = exec_config(vec![ "config", "--local", "--get", @@ -577,10 +586,10 @@ async fn test_config_scope_system_errors() { ); // Plain `--system` writes succeed, but vault-encrypted secrets are rejected. - let ok = exec_async(vec!["config", "--system", "user.name", "system_user"]).await; + let ok = exec_config(vec!["config", "--system", "user.name", "system_user"]).await; assert!(ok.is_ok(), "--system plain set should succeed: {ok:?}"); - let result = exec_async(vec![ + let result = exec_config(vec![ "config", "set", "--system", @@ -606,7 +615,7 @@ async fn test_config_scope_system_errors() { "Vault.signing", "VAULT.gpg.pubkey", ] { - let r = exec_async(vec!["config", "--system", key, "x"]).await; + let r = exec_config(vec!["config", "--system", key, "x"]).await; assert!(r.is_err(), "--system {key} should be rejected"); assert!( r.unwrap_err() @@ -618,7 +627,7 @@ async fn test_config_scope_system_errors() { // `config import --system` is rejected up front: import auto-encrypts // sensitive keys, which the system scope does not support. - let import = exec_async(vec!["config", "import", "--system"]).await; + let import = exec_config(vec!["config", "import", "--system"]).await; assert!(import.is_err(), "config import --system should be rejected"); assert!( import @@ -643,7 +652,7 @@ async fn test_config_system_rejected_vault_write_does_not_create_db() { // A rejected `--system --encrypt` write must short-circuit before touching // the DB, so the system config path is never created. - let result = exec_async(vec![ + let result = exec_config(vec![ "config", "set", "--system", @@ -675,10 +684,10 @@ async fn test_config_system_rename_into_vault_namespace_rejected() { // Seed a plain (non-sensitive) system key, then try to rename its section // into the vault namespace — which would smuggle a secret key past the // direct-set guard. It must be rejected. - let seed = exec_async(vec!["config", "--system", "foo.bar", "value"]).await; + let seed = exec_config(vec!["config", "--system", "foo.bar", "value"]).await; assert!(seed.is_ok(), "plain system set should succeed: {seed:?}"); - let rename = exec_async(vec![ + let rename = exec_config(vec![ "config", "--system", "--rename-section", @@ -713,7 +722,7 @@ async fn test_config_system_set_rejected_when_existing_row_is_encrypted() { let shared_db = temp_path.path().join("shared.db"); let _global = EnvVarGuard::set("LIBRA_CONFIG_GLOBAL_DB", shared_db.as_os_str()); - let seed = exec_async(vec![ + let seed = exec_config(vec![ "config", "set", "--global", @@ -728,7 +737,7 @@ async fn test_config_system_set_rejected_when_existing_row_is_encrypted() { // a `--system --plaintext` write to the same key must be rejected (it would // otherwise keep the row's encrypted flag while storing a plaintext value). let _system = EnvVarGuard::set("LIBRA_CONFIG_SYSTEM_DB", shared_db.as_os_str()); - let result = exec_async(vec![ + let result = exec_config(vec![ "config", "set", "--system", @@ -758,7 +767,7 @@ async fn test_config_scope_explicit_local() { let _guard = test::ChangeDirGuard::new(temp_path.path()); // Set a value explicitly in local scope - let result = exec_async(vec![ + let result = exec_config(vec![ "config", "--local", "user.name", @@ -768,7 +777,7 @@ async fn test_config_scope_explicit_local() { assert!(result.is_ok()); // Verify the value was written to local scope by reading it back - let result = exec_async(vec!["config", "--local", "--get", "user.name"]).await; + let result = exec_config(vec!["config", "--local", "--get", "user.name"]).await; assert!(result.is_ok()); } @@ -784,19 +793,19 @@ async fn test_config_scope_isolation() { let _scoped = ScopedConfigPathGuard::new(&global_db_dir.path().join("global_config.db")); // Set the same key with different values in different scopes - let result = exec_async(vec!["config", "--local", "test.isolation", "local_value"]).await; + let result = exec_config(vec!["config", "--local", "test.isolation", "local_value"]).await; assert!(result.is_ok()); - let result = exec_async(vec!["config", "--global", "test.isolation", "global_value"]).await; + let result = exec_config(vec!["config", "--global", "test.isolation", "global_value"]).await; assert!(result.is_ok()); // Verify that each scope returns its own value println!("Reading from local scope:"); - let result = exec_async(vec!["config", "--local", "--get", "test.isolation"]).await; + let result = exec_config(vec!["config", "--local", "--get", "test.isolation"]).await; assert!(result.is_ok()); println!("Reading from global scope:"); - let result = exec_async(vec!["config", "--global", "--get", "test.isolation"]).await; + let result = exec_config(vec!["config", "--global", "--get", "test.isolation"]).await; assert!(result.is_ok()); } @@ -814,7 +823,7 @@ async fn test_config_get_reveal_decrypt_failure_returns_error() { .await .unwrap(); - let result = exec_async(vec!["config", "get", "--reveal", "vault.env.TEST_SECRET"]).await; + let result = exec_config(vec!["config", "get", "--reveal", "vault.env.TEST_SECRET"]).await; let err = result.expect_err("decrypt failure should surface as an error"); assert_eq!(err.kind(), CliErrorKind::Fatal); assert_eq!(err.exit_code(), 128); @@ -835,7 +844,7 @@ async fn test_config_get_cascaded_global_read_failure_returns_error() { std::fs::write(&bad_global_db, "definitely-not-a-sqlite-database").unwrap(); let _scoped = ScopedConfigPathGuard::new(&bad_global_db); - let result = exec_async(vec!["config", "get", "user.missing"]).await; + let result = exec_config(vec!["config", "get", "user.missing"]).await; let err = result.expect_err("broken cascaded scope should not be ignored"); assert_eq!(err.kind(), CliErrorKind::Fatal); assert_eq!(err.exit_code(), 128); @@ -849,7 +858,7 @@ async fn test_config_add_rejects_implicit_encryption_mixed_with_existing_plainte test::setup_with_new_libra_in(temp_path.path()).await; let _guard = test::ChangeDirGuard::new(temp_path.path()); - let result = exec_async(vec![ + let result = exec_config(vec![ "config", "set", "--plaintext", @@ -859,7 +868,7 @@ async fn test_config_add_rejects_implicit_encryption_mixed_with_existing_plainte .await; assert!(result.is_ok()); - let result = exec_async(vec![ + let result = exec_config(vec![ "config", "set", "--add", @@ -995,7 +1004,7 @@ async fn test_config_set_read_failure_does_not_silently_skip_existing_state_chec let _home_guard = EnvVarGuard::set("HOME", fake_home.path().as_os_str()); let _userprofile_guard = EnvVarGuard::set("USERPROFILE", fake_home.path().as_os_str()); - let result = exec_async(vec![ + let result = exec_config(vec![ "config", "set", "--global", @@ -1032,7 +1041,7 @@ async fn test_config_set_missing_value_uses_protected_input_when_existing_key_is // Prevent rpassword::read_password() from blocking on stdin. let _test_env = EnvVarGuard::set("LIBRA_TEST", std::ffi::OsStr::new("1")); - let result = exec_async(vec![ + let result = exec_config(vec![ "config", "set", "--encrypt", @@ -1042,7 +1051,7 @@ async fn test_config_set_missing_value_uses_protected_input_when_existing_key_is .await; assert!(result.is_ok()); - let result = exec_async(vec!["config", "set", "custom.value"]).await; + let result = exec_config(vec!["config", "set", "custom.value"]).await; let err = result.expect_err("existing encrypted state should require protected input"); assert_eq!(err.exit_code(), 2); assert!( diff --git a/tests/command/open_test.rs b/tests/command/open_test.rs index 5279923b9..c5417fb30 100644 --- a/tests/command/open_test.rs +++ b/tests/command/open_test.rs @@ -29,7 +29,7 @@ async fn test_open_remote_origin() { remote::execute_safe( RemoteCmds::Add { name: "origin".into(), - url: "git@github.com:web3infra-foundation/libra.git".into(), + url: "git@github.com:libra-tools/libra.git".into(), fetch: false, track: vec![], master: None, @@ -114,7 +114,7 @@ fn test_open_json_output_uses_origin_remote() { "remote", "add", "origin", - "git@github.com:web3infra-foundation/libra.git", + "git@github.com:libra-tools/libra.git", ], repo.path(), ); @@ -128,7 +128,7 @@ fn test_open_json_output_uses_origin_remote() { assert_eq!(json["data"]["remote"], "origin"); assert_eq!( json["data"]["web_url"], - "https://github.com/web3infra-foundation/libra" + "https://github.com/libra-tools/libra" ); assert_eq!(json["data"]["launched"], false); } @@ -143,7 +143,7 @@ fn test_open_json_output_does_not_require_browser_launcher() { "remote", "add", "origin", - "git@github.com:web3infra-foundation/libra.git", + "git@github.com:libra-tools/libra.git", ], repo.path(), ); @@ -176,7 +176,7 @@ fn test_open_json_output_falls_back_to_origin_when_head_is_detached() { "remote", "add", "origin", - "git@github.com:web3infra-foundation/libra.git", + "git@github.com:libra-tools/libra.git", ], repo.path(), ); @@ -210,7 +210,7 @@ fn test_open_json_output_falls_back_to_origin_when_head_is_detached() { assert_eq!(json["data"]["remote"], "origin"); assert_eq!( json["data"]["web_url"], - "https://github.com/web3infra-foundation/libra" + "https://github.com/libra-tools/libra" ); assert_eq!(json["data"]["launched"], false); } @@ -243,7 +243,7 @@ fn test_open_json_output_transforms_explicit_ssh_url() { &[ "open", "--json", - "ssh://git@github.com/web3infra-foundation/libra.git", + "ssh://git@github.com/libra-tools/libra.git", ], temp.path(), ); @@ -253,11 +253,11 @@ fn test_open_json_output_transforms_explicit_ssh_url() { assert!(json["data"]["remote"].is_null()); assert_eq!( json["data"]["remote_url"], - "ssh://git@github.com/web3infra-foundation/libra.git" + "ssh://git@github.com/libra-tools/libra.git" ); assert_eq!( json["data"]["web_url"], - "https://github.com/web3infra-foundation/libra" + "https://github.com/libra-tools/libra" ); assert_eq!(json["data"]["launched"], false); } @@ -267,11 +267,7 @@ fn test_open_json_output_keeps_explicit_https_url() { let temp = tempdir().unwrap(); let output = run_libra_command( - &[ - "open", - "--json", - "https://github.com/web3infra-foundation/libra.git", - ], + &["open", "--json", "https://github.com/libra-tools/libra.git"], temp.path(), ); @@ -283,11 +279,11 @@ fn test_open_json_output_keeps_explicit_https_url() { assert!(json["data"]["remote"].is_null()); assert_eq!( json["data"]["remote_url"], - "https://github.com/web3infra-foundation/libra.git" + "https://github.com/libra-tools/libra.git" ); assert_eq!( json["data"]["web_url"], - "https://github.com/web3infra-foundation/libra" + "https://github.com/libra-tools/libra" ); assert_eq!(json["data"]["launched"], false); } diff --git a/tests/command/remote_test.rs b/tests/command/remote_test.rs index 91944123d..35e4bcb83 100644 --- a/tests/command/remote_test.rs +++ b/tests/command/remote_test.rs @@ -1074,7 +1074,9 @@ async fn test_remote_prune_removes_stale_branches() { .unwrap(); // Fetch all branches to create remote-tracking branches - fetch::fetch_repository( + let quiet_output = OutputConfig::resolve(None, false, true, "auto", true, false, "none"); + + fetch::fetch_repository_safe( RemoteConfig { name: "origin".to_string(), url: remote_path.clone(), @@ -1082,8 +1084,11 @@ async fn test_remote_prune_removes_stale_branches() { None, false, None, + None, + &quiet_output, ) - .await; + .await + .expect("initial remote update fixture fetch should succeed"); // Verify all remote-tracking branches exist for branch_name in &branches_to_create { @@ -1268,8 +1273,9 @@ async fn test_remote_prune_dry_run_previews_changes() { .await .unwrap(); - // Fetch to create remote-tracking branch - fetch::fetch_repository( + // Fetch to create remote-tracking branch. + let quiet_output = OutputConfig::resolve(None, false, true, "auto", true, false, "none"); + fetch::fetch_repository_safe( RemoteConfig { name: "origin".to_string(), url: remote_path.clone(), @@ -1277,8 +1283,11 @@ async fn test_remote_prune_dry_run_previews_changes() { None, false, None, + None, + &quiet_output, ) - .await; + .await + .expect("initial prune fixture fetch should succeed"); // Verify remote-tracking branch exists let tracked_branch = format!("refs/remotes/origin/{}", branch_name); @@ -2557,7 +2566,8 @@ async fn remote_update_prune_removes_stale_tracking_branches() { .await .unwrap(); - fetch::fetch_repository( + let quiet_output = OutputConfig::resolve(None, false, true, "auto", true, false, "none"); + fetch::fetch_repository_safe( RemoteConfig { name: "origin".to_string(), url: remote_path.clone(), @@ -2565,8 +2575,11 @@ async fn remote_update_prune_removes_stale_tracking_branches() { None, false, None, + None, + &quiet_output, ) - .await; + .await + .expect("initial remote update fixture fetch should succeed"); for b in &branches { assert!( @@ -2588,11 +2601,15 @@ async fn remote_update_prune_removes_stale_tracking_branches() { // `remote update -p`: fetch then prune. Drives the full Update { prune } // path, not the standalone `prune` subcommand. - remote::execute(RemoteCmds::Update { - groups: vec![], - prune: true, - }) - .await; + remote::execute_safe( + RemoteCmds::Update { + groups: vec![], + prune: true, + }, + &quiet_output, + ) + .await + .expect("remote update -p should fetch and prune"); for b in ["feature1", "feature3"] { assert!( diff --git a/tests/command/worktree_test.rs b/tests/command/worktree_test.rs index 64799d276..0320e1729 100644 --- a/tests/command/worktree_test.rs +++ b/tests/command/worktree_test.rs @@ -6,9 +6,13 @@ use std::fs; #[cfg(unix)] use std::os::unix::fs::{MetadataExt, PermissionsExt, symlink}; +use clap::Parser; use libra::{ - exec_async, - utils::{test, util}, + command::{ + commit::{self, CommitArgs}, + worktree::{self, WorktreeArgs}, + }, + utils::{output::OutputConfig, test, util}, }; use serde::{Deserialize, Serialize}; use serial_test::serial; @@ -92,6 +96,22 @@ fn worktree_paths() -> Vec { .collect() } +async fn exec_worktree(args: &[&str]) -> libra::CliResult<()> { + let argv = std::iter::once("worktree") + .chain(args.iter().copied()) + .collect::>(); + let parsed = WorktreeArgs::parse_from(argv); + worktree::execute_safe(parsed, &OutputConfig::default()).await +} + +async fn exec_commit(args: &[&str]) -> libra::CliResult<()> { + let argv = std::iter::once("commit") + .chain(args.iter().copied()) + .collect::>(); + let parsed = CommitArgs::parse_from(argv); + commit::execute_safe(parsed, &OutputConfig::default()).await +} + fn assert_worktree_error(output: &std::process::Output, error_code: &str) -> CliErrorReport { assert_ne!(output.status.code(), Some(0), "worktree command must fail"); assert!( @@ -349,7 +369,7 @@ async fn test_worktree_repair_json_reports_changed_state() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt_repair_json"]) + exec_worktree(&["add", "wt_repair_json"]) .await .expect("worktree add should succeed"); @@ -435,7 +455,7 @@ async fn test_worktree_lock_json_no_such_worktree_reports_invalid_target() { let repo_dir = tempdir().unwrap(); test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "list"]) + exec_worktree(&["list"]) .await .expect("worktree list should initialize state"); let before_paths = worktree_paths(); @@ -466,7 +486,7 @@ async fn test_worktree_remove_machine_rejects_main_with_stable_error() { let repo_dir = tempdir().unwrap(); test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "list"]) + exec_worktree(&["list"]) .await .expect("worktree list should initialize state"); let before_paths = worktree_paths(); @@ -498,10 +518,10 @@ async fn test_worktree_remove_json_rejects_locked_with_stable_error() { let repo_dir = tempdir().unwrap(); test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt_locked_error"]) + exec_worktree(&["add", "wt_locked_error"]) .await .expect("worktree add should succeed"); - exec_async(vec!["worktree", "lock", "wt_locked_error"]) + exec_worktree(&["lock", "wt_locked_error"]) .await .expect("worktree lock should succeed"); let wt_path = repo_dir.path().join("wt_locked_error"); @@ -532,7 +552,7 @@ async fn test_worktree_move_machine_destination_exists_reports_conflict() { let repo_dir = tempdir().unwrap(); test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt_move_error"]) + exec_worktree(&["add", "wt_move_error"]) .await .expect("worktree add should succeed"); let src_path = repo_dir.path().join("wt_move_error"); @@ -576,7 +596,7 @@ async fn test_worktree_add_json_rejects_storage_path_as_invalid_target() { let repo_dir = tempdir().unwrap(); test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "list"]) + exec_worktree(&["list"]) .await .expect("worktree list should initialize state"); let before_paths = worktree_paths(); @@ -613,7 +633,7 @@ async fn test_worktree_list_json_corrupt_state_reports_repo_corrupt() { let repo_dir = tempdir().unwrap(); test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "list"]) + exec_worktree(&["list"]) .await .expect("worktree list should initialize state"); let state_path = util::storage_path().join("worktrees.json"); @@ -644,7 +664,7 @@ async fn test_worktree_add_creates_linked_directory() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - let result = exec_async(vec!["worktree", "add", "wt1"]).await; + let result = exec_worktree(&["add", "wt1"]).await; assert!(result.is_ok(), "worktree add failed: {:?}", result.err()); let wt_path = repo_dir.path().join("wt1"); @@ -670,7 +690,7 @@ async fn test_worktree_add_normalizes_missing_parent_with_dotdot() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "missing_parent/../wt_norm"]) + exec_worktree(&["add", "missing_parent/../wt_norm"]) .await .expect("worktree add should succeed"); @@ -687,13 +707,13 @@ async fn test_worktree_add_normalizes_missing_parent_with_dotdot() { "stored worktree path should be canonical and normalized" ); - exec_async(vec!["worktree", "lock", "wt_norm"]) + exec_worktree(&["lock", "wt_norm"]) .await .expect("worktree lock should succeed"); - exec_async(vec!["worktree", "unlock", "wt_norm"]) + exec_worktree(&["unlock", "wt_norm"]) .await .expect("worktree unlock should succeed"); - exec_async(vec!["worktree", "remove", "wt_norm"]) + exec_worktree(&["remove", "wt_norm"]) .await .expect("worktree remove should succeed"); } @@ -708,19 +728,19 @@ async fn test_worktree_add_parent_relative_then_operate_with_dot_from_linked_wor test::setup_with_new_libra_in(&repo_path).await; let _guard_repo = test::ChangeDirGuard::new(&repo_path); - exec_async(vec!["worktree", "add", "../wt_lock_dot"]) + exec_worktree(&["add", "../wt_lock_dot"]) .await .expect("worktree add with parent-relative path should succeed"); let linked = root_dir.path().join("wt_lock_dot"); let _guard_linked = test::ChangeDirGuard::new(&linked); - exec_async(vec!["worktree", "lock", "."]) + exec_worktree(&["lock", "."]) .await .expect("worktree lock with '.' should resolve the registered entry"); - exec_async(vec!["worktree", "unlock", "."]) + exec_worktree(&["unlock", "."]) .await .expect("worktree unlock with '.' should resolve the registered entry"); - exec_async(vec!["worktree", "remove", "."]) + exec_worktree(&["remove", "."]) .await .expect("worktree remove with '.' should resolve the registered entry"); } @@ -735,14 +755,14 @@ async fn test_worktree_add_parent_relative_and_absolute_path_are_equivalent() { test::setup_with_new_libra_in(&repo_path).await; let _guard = test::ChangeDirGuard::new(&repo_path); - exec_async(vec!["worktree", "add", "../wt_rel_abs"]) + exec_worktree(&["add", "../wt_rel_abs"]) .await .expect("first worktree add should succeed"); let abs_target = root_dir.path().join("wt_rel_abs").canonicalize().unwrap(); let abs_target_str = abs_target.to_string_lossy().to_string(); - exec_async(vec!["worktree", "add", abs_target_str.as_str()]) + exec_worktree(&["add", abs_target_str.as_str()]) .await .expect("second worktree add with absolute path should succeed"); @@ -773,7 +793,7 @@ async fn test_worktree_add_symlink_path_is_canonicalized_to_real_path() { let symlink_path = repo_path.join("wt_link"); symlink(&real_target, &symlink_path).expect("failed to create symlink for test"); - exec_async(vec!["worktree", "add", "wt_link"]) + exec_worktree(&["add", "wt_link"]) .await .expect("worktree add through symlink should succeed"); @@ -792,13 +812,13 @@ async fn test_worktree_add_symlink_path_is_canonicalized_to_real_path() { "state should store canonical real path instead of symlink path" ); - exec_async(vec!["worktree", "lock", "wt_link"]) + exec_worktree(&["lock", "wt_link"]) .await .expect("lock by symlink path should resolve the registered entry"); - exec_async(vec!["worktree", "unlock", "wt_link"]) + exec_worktree(&["unlock", "wt_link"]) .await .expect("unlock by symlink path should resolve the registered entry"); - exec_async(vec!["worktree", "remove", "wt_link"]) + exec_worktree(&["remove", "wt_link"]) .await .expect("remove by symlink path should resolve the registered entry"); } @@ -824,10 +844,10 @@ async fn test_worktree_add_symlink_and_real_path_are_deduplicated() { let via_symlink_str = via_symlink.to_string_lossy().to_string(); let via_real_str = via_real.to_string_lossy().to_string(); - exec_async(vec!["worktree", "add", via_symlink_str.as_str()]) + exec_worktree(&["add", via_symlink_str.as_str()]) .await .expect("add via symlinked parent should succeed"); - exec_async(vec!["worktree", "add", via_real_str.as_str()]) + exec_worktree(&["add", via_real_str.as_str()]) .await .expect("add via real parent should not fail"); @@ -868,7 +888,7 @@ async fn test_worktree_add_rejects_existing_non_empty_directory() { ignore_missing: false, }) .await; - exec_async(vec!["commit", "-m", "initial"]) + exec_commit(&["-m", "initial"]) .await .expect("initial commit should succeed"); @@ -878,9 +898,7 @@ async fn test_worktree_add_rejects_existing_non_empty_directory() { .expect("failed to seed pre-existing target content"); assert!( - exec_async(vec!["worktree", "add", "wt_non_empty"]) - .await - .is_err(), + exec_worktree(&["add", "wt_non_empty"]).await.is_err(), "adding worktree to non-empty directory should fail" ); @@ -913,7 +931,7 @@ async fn test_worktree_add_duplicate_registered_path_does_not_create_directory() test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt_dup"]) + exec_worktree(&["add", "wt_dup"]) .await .expect("initial worktree add should succeed"); @@ -924,7 +942,7 @@ async fn test_worktree_add_duplicate_registered_path_does_not_create_directory() assert!(!wt_path.exists(), "worktree directory should be missing"); let before_paths = worktree_paths(); - exec_async(vec!["worktree", "add", "wt_dup"]) + exec_worktree(&["add", "wt_dup"]) .await .expect("duplicate worktree add command itself should not fail"); let after_paths = worktree_paths(); @@ -964,7 +982,7 @@ async fn test_worktree_add_rolls_back_link_on_restore_failure() { ignore_missing: false, }) .await; - exec_async(vec!["commit", "-m", "initial"]) + exec_commit(&["-m", "initial"]) .await .expect("initial commit should succeed"); @@ -974,9 +992,7 @@ async fn test_worktree_add_rolls_back_link_on_restore_failure() { .expect("failed to create conflicting path in target"); assert!( - exec_async(vec!["worktree", "add", "wt_restore_fail"]) - .await - .is_err(), + exec_worktree(&["add", "wt_restore_fail"]).await.is_err(), "adding worktree with conflicting file should fail" ); @@ -1031,14 +1047,14 @@ async fn test_worktree_add_rolls_back_populated_files_when_state_save_fails() { ignore_missing: false, }) .await; - exec_async(vec!["commit", "-m", "initial"]) + exec_commit(&["-m", "initial"]) .await .expect("initial commit should succeed"); let wt_path = repo_dir.path().join("wt_state_save_fail"); fs::create_dir_all(&wt_path).expect("failed to create existing empty target"); - exec_async(vec!["worktree", "list"]) + exec_worktree(&["list"]) .await .expect("worktree list should initialize worktree state"); assert!( @@ -1059,9 +1075,7 @@ async fn test_worktree_add_rolls_back_populated_files_when_state_save_fails() { .expect("failed to set storage directory read-only"); assert!( - exec_async(vec!["worktree", "add", "wt_state_save_fail"]) - .await - .is_err(), + exec_worktree(&["add", "wt_state_save_fail"]).await.is_err(), "adding worktree with unwritable state should fail" ); @@ -1103,7 +1117,7 @@ async fn test_worktree_move_across_filesystems_rolls_back_when_supported() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt_cross_src"]) + exec_worktree(&["add", "wt_cross_src"]) .await .expect("worktree add should succeed"); let src_path = repo_dir.path().join("wt_cross_src"); @@ -1124,7 +1138,7 @@ async fn test_worktree_move_across_filesystems_rolls_back_when_supported() { let dest_str = dest_path.to_string_lossy().to_string(); let before_paths = worktree_paths(); - exec_async(vec!["worktree", "move", "wt_cross_src", dest_str.as_str()]) + exec_worktree(&["move", "wt_cross_src", dest_str.as_str()]) .await .expect("worktree move command itself should not fail"); @@ -1151,7 +1165,7 @@ async fn test_worktree_corrupted_state_file_is_handled_without_side_effects() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "list"]) + exec_worktree(&["list"]) .await .expect("worktree list should initialize state first"); @@ -1160,7 +1174,7 @@ async fn test_worktree_corrupted_state_file_is_handled_without_side_effects() { let before = fs::read_to_string(&state_path).unwrap(); assert!( - exec_async(vec!["worktree", "list"]).await.is_err(), + exec_worktree(&["list"]).await.is_err(), "listing worktrees with corrupt state should fail" ); @@ -1173,9 +1187,7 @@ async fn test_worktree_corrupted_state_file_is_handled_without_side_effects() { let new_path = repo_dir.path().join("wt_from_corrupt"); assert!(!new_path.exists()); assert!( - exec_async(vec!["worktree", "add", "wt_from_corrupt"]) - .await - .is_err(), + exec_worktree(&["add", "wt_from_corrupt"]).await.is_err(), "adding worktree with corrupt state should fail" ); assert!( @@ -1192,19 +1204,19 @@ async fn test_worktree_lock_unlock_and_remove() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt2"]) + exec_worktree(&["add", "wt2"]) .await .expect("worktree add should succeed"); - exec_async(vec!["worktree", "lock", "wt2"]) + exec_worktree(&["lock", "wt2"]) .await .expect("worktree lock should succeed"); - exec_async(vec!["worktree", "unlock", "wt2"]) + exec_worktree(&["unlock", "wt2"]) .await .expect("worktree unlock should succeed"); - exec_async(vec!["worktree", "remove", "wt2"]) + exec_worktree(&["remove", "wt2"]) .await .expect("worktree remove should succeed"); } @@ -1235,7 +1247,7 @@ async fn test_worktree_add_does_not_reset_index() { }) .await; - exec_async(vec!["commit", "-m", "initial"]) + exec_commit(&["-m", "initial"]) .await .expect("initial commit should succeed"); @@ -1266,7 +1278,7 @@ async fn test_worktree_add_does_not_reset_index() { "tracked.txt should be staged before worktree add" ); - exec_async(vec!["worktree", "add", "wt_index"]) + exec_worktree(&["add", "wt_index"]) .await .expect("worktree add should succeed even when index has staged changes"); @@ -1305,7 +1317,7 @@ async fn test_worktree_add_populates_from_head_not_staged_index() { ignore_missing: false, }) .await; - exec_async(vec!["commit", "-m", "initial"]) + exec_commit(&["-m", "initial"]) .await .expect("initial commit should succeed"); @@ -1327,7 +1339,7 @@ async fn test_worktree_add_populates_from_head_not_staged_index() { }) .await; - exec_async(vec!["worktree", "add", "wt_head_content"]) + exec_worktree(&["add", "wt_head_content"]) .await .expect("worktree add should succeed"); @@ -1347,7 +1359,7 @@ async fn test_worktree_list_includes_main_and_added_worktrees() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt_list"]) + exec_worktree(&["add", "wt_list"]) .await .expect("worktree add should succeed"); @@ -1357,7 +1369,7 @@ async fn test_worktree_list_includes_main_and_added_worktrees() { "state should contain the added worktree" ); - exec_async(vec!["worktree", "list"]) + exec_worktree(&["list"]) .await .expect("worktree list should succeed"); @@ -1379,7 +1391,7 @@ async fn test_worktree_move_moves_unlocked_non_main_worktree() { let src = repo_dir.path().join("wt_move_src"); let dest = repo_dir.path().join("wt_move_dest"); - exec_async(vec!["worktree", "add", "wt_move_src"]) + exec_worktree(&["add", "wt_move_src"]) .await .expect("worktree add should succeed"); @@ -1391,7 +1403,7 @@ async fn test_worktree_move_moves_unlocked_non_main_worktree() { let src_canonical = src.canonicalize().unwrap(); - exec_async(vec!["worktree", "move", "wt_move_src", "wt_move_dest"]) + exec_worktree(&["move", "wt_move_src", "wt_move_dest"]) .await .expect("worktree move should succeed"); @@ -1422,7 +1434,7 @@ async fn test_worktree_move_main_is_rejected_without_side_effects() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "list"]) + exec_worktree(&["list"]) .await .expect("worktree list should initialize worktree state"); @@ -1439,9 +1451,7 @@ async fn test_worktree_move_main_is_rejected_without_side_effects() { assert!(!dest.exists()); assert!( - exec_async(vec!["worktree", "move", ".", "moved_main"]) - .await - .is_err(), + exec_worktree(&["move", ".", "moved_main"]).await.is_err(), "moving main worktree should fail" ); @@ -1475,11 +1485,11 @@ async fn test_worktree_move_locked_is_rejected_without_side_effects() { let src = repo_dir.path().join("wt_locked"); let dest = repo_dir.path().join("wt_locked_moved"); - exec_async(vec!["worktree", "add", "wt_locked"]) + exec_worktree(&["add", "wt_locked"]) .await .expect("worktree add should succeed"); - exec_async(vec!["worktree", "lock", "wt_locked"]) + exec_worktree(&["lock", "wt_locked"]) .await .expect("worktree lock should succeed"); @@ -1489,7 +1499,7 @@ async fn test_worktree_move_locked_is_rejected_without_side_effects() { let src_canonical = src.canonicalize().unwrap(); assert!( - exec_async(vec!["worktree", "move", "wt_locked", "wt_locked_moved"]) + exec_worktree(&["move", "wt_locked", "wt_locked_moved"]) .await .is_err(), "moving locked worktree should fail" @@ -1521,10 +1531,10 @@ async fn test_worktree_move_rejects_duplicate_destination() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt_a"]) + exec_worktree(&["add", "wt_a"]) .await .expect("first worktree add should succeed"); - exec_async(vec!["worktree", "add", "wt_b"]) + exec_worktree(&["add", "wt_b"]) .await .expect("second worktree add should succeed"); @@ -1536,9 +1546,7 @@ async fn test_worktree_move_rejects_duplicate_destination() { let before_paths = worktree_paths(); assert!( - exec_async(vec!["worktree", "move", "wt_a", "wt_b"]) - .await - .is_err(), + exec_worktree(&["move", "wt_a", "wt_b"]).await.is_err(), "moving worktree to occupied path should fail" ); @@ -1564,7 +1572,7 @@ async fn test_worktree_move_rejects_destination_inside_storage() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt_storage_src"]) + exec_worktree(&["add", "wt_storage_src"]) .await .expect("worktree add should succeed"); @@ -1576,14 +1584,9 @@ async fn test_worktree_move_rejects_destination_inside_storage() { let before_paths = worktree_paths(); assert!( - exec_async(vec![ - "worktree", - "move", - "wt_storage_src", - ".libra/moved_inside_storage", - ]) - .await - .is_err(), + exec_worktree(&["move", "wt_storage_src", ".libra/moved_inside_storage",]) + .await + .is_err(), "moving worktree into storage should fail" ); @@ -1611,7 +1614,7 @@ async fn test_worktree_prune_removes_missing_non_main_worktrees() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt_prune"]) + exec_worktree(&["add", "wt_prune"]) .await .expect("worktree add should succeed"); @@ -1626,7 +1629,7 @@ async fn test_worktree_prune_removes_missing_non_main_worktrees() { let before_paths = worktree_paths(); - exec_async(vec!["worktree", "prune"]) + exec_worktree(&["prune"]) .await .expect("worktree prune should succeed"); @@ -1645,10 +1648,10 @@ async fn test_worktree_prune_keeps_locked_worktrees() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt_locked_prune"]) + exec_worktree(&["add", "wt_locked_prune"]) .await .expect("worktree add should succeed"); - exec_async(vec!["worktree", "lock", "wt_locked_prune"]) + exec_worktree(&["lock", "wt_locked_prune"]) .await .expect("worktree lock should succeed"); @@ -1660,7 +1663,7 @@ async fn test_worktree_prune_keeps_locked_worktrees() { .to_string(); fs::remove_dir_all(&wt_path).expect("failed to remove locked worktree directory"); - exec_async(vec!["worktree", "prune"]) + exec_worktree(&["prune"]) .await .expect("worktree prune should succeed"); @@ -1681,10 +1684,10 @@ async fn test_worktree_remove_locked_is_rejected_without_side_effects() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt_for_remove"]) + exec_worktree(&["add", "wt_for_remove"]) .await .expect("worktree add should succeed"); - exec_async(vec!["worktree", "lock", "wt_for_remove"]) + exec_worktree(&["lock", "wt_for_remove"]) .await .expect("worktree lock should succeed"); @@ -1694,9 +1697,7 @@ async fn test_worktree_remove_locked_is_rejected_without_side_effects() { let before_paths = worktree_paths(); assert!( - exec_async(vec!["worktree", "remove", "wt_for_remove"]) - .await - .is_err(), + exec_worktree(&["remove", "wt_for_remove"]).await.is_err(), "removing locked worktree should fail" ); @@ -1721,7 +1722,7 @@ async fn test_worktree_repair_deduplicates_entries() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt_repair"]) + exec_worktree(&["add", "wt_repair"]) .await .expect("worktree add should succeed"); @@ -1739,7 +1740,7 @@ async fn test_worktree_repair_deduplicates_entries() { .expect("failed to serialize duplicated worktree state"); fs::write(&state_path, data).expect("failed to overwrite worktrees.json with duplicates"); - exec_async(vec!["worktree", "repair"]) + exec_worktree(&["repair"]) .await .expect("worktree repair should succeed"); @@ -1764,7 +1765,7 @@ async fn test_worktree_repair_persists_main_flag_fix_without_duplicates() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt_main_fix"]) + exec_worktree(&["add", "wt_main_fix"]) .await .expect("worktree add should succeed"); @@ -1778,7 +1779,7 @@ async fn test_worktree_repair_persists_main_flag_fix_without_duplicates() { .expect("failed to serialize worktree state with broken main flags"); fs::write(&state_path, data).expect("failed to overwrite worktrees.json"); - exec_async(vec!["worktree", "repair"]) + exec_worktree(&["repair"]) .await .expect("worktree repair should succeed"); @@ -1804,7 +1805,7 @@ async fn test_worktree_main_flag_remains_single_and_stable() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt_main"]) + exec_worktree(&["add", "wt_main"]) .await .expect("worktree add should succeed"); @@ -1812,7 +1813,7 @@ async fn test_worktree_main_flag_remains_single_and_stable() { let wt_path = repo_dir.path().join("wt_main"); let _guard_wt = test::ChangeDirGuard::new(&wt_path); - exec_async(vec!["worktree", "list"]) + exec_worktree(&["list"]) .await .expect("worktree list from linked worktree should succeed"); @@ -1840,14 +1841,14 @@ async fn test_worktree_remove_default_keeps_disk_directory() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt_keep"]) + exec_worktree(&["add", "wt_keep"]) .await .expect("worktree add should succeed"); let wt_path = repo_dir.path().join("wt_keep"); assert!(wt_path.is_dir()); - exec_async(vec!["worktree", "remove", "wt_keep"]) + exec_worktree(&["remove", "wt_keep"]) .await .expect("worktree remove (default) should succeed"); @@ -1870,7 +1871,7 @@ async fn test_worktree_remove_json_reports_kept_directory() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt_keep_json"]) + exec_worktree(&["add", "wt_keep_json"]) .await .expect("worktree add should succeed"); @@ -1910,14 +1911,14 @@ async fn test_worktree_remove_with_delete_dir_clean_path() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt_delete"]) + exec_worktree(&["add", "wt_delete"]) .await .expect("worktree add should succeed"); let wt_path = repo_dir.path().join("wt_delete"); assert!(wt_path.is_dir()); - exec_async(vec!["worktree", "remove", "--delete-dir", "wt_delete"]) + exec_worktree(&["remove", "--delete-dir", "wt_delete"]) .await .expect("worktree remove --delete-dir on a clean worktree should succeed"); @@ -1940,7 +1941,7 @@ async fn test_worktree_remove_machine_reports_deleted_directory() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt_delete_machine"]) + exec_worktree(&["add", "wt_delete_machine"]) .await .expect("worktree add should succeed"); @@ -1989,7 +1990,7 @@ async fn test_worktree_remove_with_delete_dir_dirty_path_is_rejected() { test::setup_with_new_libra_in(repo_dir.path()).await; let _guard = test::ChangeDirGuard::new(repo_dir.path()); - exec_async(vec!["worktree", "add", "wt_dirty_delete"]) + exec_worktree(&["add", "wt_dirty_delete"]) .await .expect("worktree add should succeed"); diff --git a/tests/compat/README.md b/tests/compat/README.md index 390c13953..d4dafd0ee 100644 --- a/tests/compat/README.md +++ b/tests/compat/README.md @@ -43,7 +43,7 @@ top-level `[[test]]` entries in `Cargo.toml`. | `extra_production_unwrap_guard.rs` | unwrap audit (v0.17.266) | extra audited files (`lfs.rs`, `object.rs`, `storage/local.rs`, `storage/tiered.rs`, `path_ext.rs`, `git_protocol.rs`, `lfs_structs.rs`, `command/reflog.rs`) must not regress | | `all_production_unwrap_guard.rs` | unwrap audit (v0.17.268) | catch-all guard walking the entire `src/` tree; new modules are automatically in scope | | `agent_run_non_exhaustive_guard.rs` | agent_run | every `pub enum` exposed under `src/internal/ai/agent_run/` must carry `#[non_exhaustive]` so additive evolution is non-breaking | -| `agent_docs_contract.rs` | agent plan docs | `docs/development/commands/agent.md` must not claim removed provider surfaces still exist after source/tests close them | +| `agent_docs_contract.rs` | agent plan docs | `docs/development/tracing/agent.md` must not claim removed provider surfaces still exist after source/tests close them | | `help_examples_banner.rs` | cross-cutting item B (v0.17.841) | every visible command in `src/cli.rs::Commands` renders `EXAMPLES:` / `Examples:` in ` --help` | | `error_codes_doc_sync.rs` | cross-cutting (v0.17.842) | every `LBR-*-NNN` literal in `src/utils/error.rs` is documented in `docs/error-codes.md` | | `command_docs_examples_section.rs` | cross-cutting item B (v0.17.851) | every `docs/commands/.md` page carries an `## Examples` / `## Common Commands` heading | diff --git a/tests/compat/agent_docs_contract.rs b/tests/compat/agent_docs_contract.rs index ae76f6a83..529a62b3c 100644 --- a/tests/compat/agent_docs_contract.rs +++ b/tests/compat/agent_docs_contract.rs @@ -1,11 +1,11 @@ -//! Guard `docs/development/commands/agent.md` against stale implementation claims. +//! Guard `docs/development/tracing/agent.md` against stale implementation claims. //! //! The Agent plan is an active backlog document. Several sections are historical //! by design, but the current-risk table must not claim removed provider //! surfaces still exist after the source and CLI migration tests have closed //! them. -const AGENT_DOC: &str = include_str!("../../docs/development/commands/agent.md"); +const AGENT_DOC: &str = include_str!("../../docs/development/tracing/agent.md"); const CODE_COMMAND: &str = include_str!("../../src/command/code.rs"); #[test] @@ -21,7 +21,7 @@ fn agent_doc_keeps_claudecode_marked_removed_not_active() { ] { assert!( !AGENT_DOC.contains(forbidden), - "docs/development/commands/agent.md must not keep stale claudecode-active claim: {forbidden}", + "docs/development/tracing/agent.md must not keep stale claudecode-active claim: {forbidden}", ); } diff --git a/tests/compat/matrix_alignment.rs b/tests/compat/matrix_alignment.rs index 8d01e8948..f39020985 100644 --- a/tests/compat/matrix_alignment.rs +++ b/tests/compat/matrix_alignment.rs @@ -100,8 +100,8 @@ fn docs_consistency_covers_code_command_router_contracts() { let web_mod = read_repo_file("src/internal/ai/web/mod.rs"); let code_doc = read_repo_file("docs/commands/code.md"); let code_control_doc = read_repo_file("docs/commands/code-control.md"); - let integration_plan = read_repo_file("docs/development/integration-test-plan.md"); - let agent_doc = read_repo_file("docs/development/commands/agent.md"); + let integration_plan = read_repo_file("docs/development/integration/integration-test-plan.md"); + let agent_doc = read_repo_file("docs/development/tracing/agent.md"); let workflow = read_repo_file(".github/workflows/base.yml"); let source_and_docs = [ web_mod.as_str(), @@ -158,17 +158,17 @@ fn docs_consistency_covers_code_command_router_contracts() { ( integration_plan.as_str(), "test-provider", - "docs/development/integration-test-plan.md", + "docs/development/integration/integration-test-plan.md", ), ( integration_plan.as_str(), "code_ui_scenarios", - "docs/development/integration-test-plan.md", + "docs/development/integration/integration-test-plan.md", ), ( agent_doc.as_str(), "diagnostics_redaction_test", - "docs/development/commands/agent.md", + "docs/development/tracing/agent.md", ), ] { assert_contains(body, needle, context); diff --git a/tests/compat/matrix_alignment_support.rs b/tests/compat/matrix_alignment_support.rs index ecbf91f80..405a20142 100644 --- a/tests/compat/matrix_alignment_support.rs +++ b/tests/compat/matrix_alignment_support.rs @@ -171,14 +171,14 @@ fn flag_values(body: &str, flag: &str) -> BTreeSet { pub(crate) fn plan_test_targets() -> BTreeSet { flag_values( - &read_repo_file("docs/development/integration-test-plan.md"), + &read_repo_file("docs/development/integration/integration-test-plan.md"), "--test", ) } pub(crate) fn plan_features() -> BTreeSet { flag_values( - &read_repo_file("docs/development/integration-test-plan.md"), + &read_repo_file("docs/development/integration/integration-test-plan.md"), "--features", ) .into_iter() diff --git a/tests/network_remotes_test.rs b/tests/network_remotes_test.rs index af88b20c2..b738442b7 100644 --- a/tests/network_remotes_test.rs +++ b/tests/network_remotes_test.rs @@ -15,9 +15,8 @@ use url::Url; #[tokio::test] async fn https_discovery_upload_pack_lists_refs() { - let client = HttpsClient::from_url( - &Url::parse("https://github.com/web3infra-foundation/mega.git/").unwrap(), - ); + let client = + HttpsClient::from_url(&Url::parse("https://github.com/libra-tools/mega.git/").unwrap()); let discovery = client .discovery_reference(UploadPack) .await @@ -35,9 +34,8 @@ async fn https_discovery_upload_pack_lists_refs() { #[tokio::test] async fn https_upload_pack_returns_pack_data() { - let client = HttpsClient::from_url( - &Url::parse("https://github.com/web3infra-foundation/mega/").unwrap(), - ); + let client = + HttpsClient::from_url(&Url::parse("https://github.com/libra-tools/mega/").unwrap()); let discovery = client .discovery_reference(UploadPack) .await @@ -79,9 +77,8 @@ async fn github_lfs_batch_download_returns_response() { }], hash_algo: lfs::LFS_HASH_ALGO.to_string(), }; - let lfs_client = LFSClient::from_url( - &Url::parse("https://github.com/web3infra-foundation/mega.git").unwrap(), - ); + let lfs_client = + LFSClient::from_url(&Url::parse("https://github.com/libra-tools/mega.git").unwrap()); let response = lfs_client .client .post(lfs_client.batch_url.clone()) diff --git a/tests/operation_wrapper_test.rs b/tests/operation_wrapper_test.rs index cecb9cf44..22a8a0d63 100644 --- a/tests/operation_wrapper_test.rs +++ b/tests/operation_wrapper_test.rs @@ -79,7 +79,7 @@ async fn create_operation_schema_missing_view(db: &DatabaseConnection) { async fn create_reference_table_with_head(db: &DatabaseConnection) { db.execute(Statement::from_string( DbBackend::Sqlite, - "CREATE TABLE reference (id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT,kind TEXT NOT NULL,\"commit\" TEXT,remote TEXT)".to_string(), + "CREATE TABLE reference (id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT,kind TEXT NOT NULL,\"commit\" TEXT,remote TEXT,worktree_id TEXT)".to_string(), )) .await .unwrap(); @@ -102,7 +102,7 @@ async fn create_reference_table_with_head(db: &DatabaseConnection) { async fn create_reference_table_without_head(db: &DatabaseConnection) { db.execute(Statement::from_string( DbBackend::Sqlite, - "CREATE TABLE reference (id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT,kind TEXT NOT NULL,\"commit\" TEXT,remote TEXT)".to_string(), + "CREATE TABLE reference (id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT,kind TEXT NOT NULL,\"commit\" TEXT,remote TEXT,worktree_id TEXT)".to_string(), )) .await .unwrap(); diff --git a/tools/integration-runner/README.md b/tools/integration-runner/README.md index 893fbb0b7..560c2ccf3 100644 --- a/tools/integration-runner/README.md +++ b/tools/integration-runner/README.md @@ -1,6 +1,6 @@ # Libra Integration Runner -Independent black-box runner for `docs/development/integration-test-plan.md`. +Independent black-box runner for `docs/development/integration/integration-test-plan.md`. This tool is intentionally outside the root `Cargo.toml` test graph. Run it explicitly: diff --git a/tools/integration-runner/src/plan.rs b/tools/integration-runner/src/plan.rs index 11d4c3c22..27a9fab63 100644 --- a/tools/integration-runner/src/plan.rs +++ b/tools/integration-runner/src/plan.rs @@ -98,7 +98,7 @@ fn load_scenario_docs(repo_root: &Path) -> Result<(BTreeSet, BTreeSet Result<()> { let manifest = load_manifest(repo_root)?; - let plan_path = repo_root.join("docs/development/integration-test-plan.md"); + let plan_path = repo_root.join("docs/development/integration/integration-test-plan.md"); let plan_md = fs::read_to_string(&plan_path).with_context(|| format!("read {}", plan_path.display()))?; let (md_headings, md_scenarios) = load_scenario_docs(repo_root)?; diff --git a/web/package.json b/web/package.json index 8f4b3f9b1..f10e23e92 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "libra", - "version": "0.18.0", + "version": "0.18.1", "private": true, "scripts": { "dev": "next dev", diff --git a/worker/package.json b/worker/package.json index d1dc0401e..86aef26b7 100644 --- a/worker/package.json +++ b/worker/package.json @@ -1,6 +1,6 @@ { "name": "libra-publish-worker", - "version": "0.18.0", + "version": "0.18.1", "private": true, "description": "Libra publish — Cloudflare Worker (Next.js + OpenNext) reading D1/R2 publish snapshots", "scripts": {