From 63559258c47067fa05002ceed5fac471c8d0e3f8 Mon Sep 17 00:00:00 2001 From: Danil Silantyev Date: Tue, 29 Sep 2026 16:00:56 +0500 Subject: [PATCH] feat: a release of this repository Published at 0.0.81. Propose changes through this repository's issues and pull requests. --- .github/ISSUE_TEMPLATE/defect.md | 2 +- .github/workflows/ci.yml | 14 ++++--- .github/workflows/evidence.yml | 2 +- .github/workflows/release.yml | 2 +- README.md | 27 ++++++------ SUPPORT.md | 29 ++++++------- crates/cursor-setup-system/src/main.rs | 12 +++--- crates/harness-runtime/src/catalog.rs | 42 ++++++++++++++++--- crates/harness-runtime/src/facts.rs | 16 +++---- crates/harness-runtime/src/lib.rs | 6 +-- crates/provider-v3/src/bundle.rs | 6 +-- crates/provider-v3/src/vocabulary.rs | 4 +- install.ps1 | 2 +- references/cursor-baseline.json | 14 +++---- scripts/evidence.py | 10 +++-- .../references/authoring-commands.md | 6 +-- .../references/authoring-hooks.md | 6 +-- .../nddev-builder/references/authoring-mcp.md | 6 +-- .../references/authoring-plugins.md | 6 +-- .../references/authoring-skills.md | 6 +-- .../nddev-builder/references/surfaces.md | 10 ++--- .../references/validation-release.md | 4 +- tools/build_crates_io.py | 30 +++++++++---- tools/build_wheels.py | 2 +- 24 files changed, 154 insertions(+), 110 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/defect.md b/.github/ISSUE_TEMPLATE/defect.md index cf6c652..11920f1 100644 --- a/.github/ISSUE_TEMPLATE/defect.md +++ b/.github/ISSUE_TEMPLATE/defect.md @@ -18,7 +18,7 @@ labels: [] ## Environment - Operating system and architecture: -- `-setup-system --version`: +- `cursor-setup-system --version`: - Product version being configured: ## Target state, if relevant diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50978f4..7b8c886 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: contents: read uses: NDDev-OpenNetwork/ci-workflows/.github/workflows/rust-ci.yml@d92026cf31a10a0eeaa532e1f323c7cfdfbfac5b # 0.1.29 with: - toolchain: '1.98.0' + toolchain: '1.98.1' # The three-OS matrix is the evidence ADR-0113 asks for, and standard # hosted runners are free with unlimited minutes on a public repository. test_matrix_os: '["ubuntu-latest", "macos-latest", "windows-latest"]' @@ -99,7 +99,7 @@ jobs: - name: Set up Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 with: - toolchain: '1.98.0' + toolchain: '1.98.1' # The same reason the release job gives. This job's whole subject is # the bytes that come out of the build, so restoring someone else's # `target/` and then reading its import table would answer a question @@ -181,9 +181,13 @@ jobs: # bypassed, or read by someone with no reason to trust it. # # Measured on Linux across all seven binaries, then on macOS by - # `0.0.8`'s own run, and the two agree exactly: the six that declare - # `launch` import `execvp` and nothing else; antigravity, which - # declares none, imports no spawn symbol at all, on either platform. + # `0.0.8`'s own run, and the two agree exactly: every build that + # declares `launch` imports `execvp` and nothing else. `0.0.8` still + # carried the era when antigravity declared none and imported no + # spawn symbol at all -- all seven declare it now (antigravity's is + # a documented-home binding), so today the negative arm is enforced + # on nobody, and stays because the day a launch slips out of a + # declaration is the day the spawn symbol must fail here. # So the linker drops what nothing calls and the absence is real # rather than incidental -- which is what had to be true before this # could be enforced anywhere. diff --git a/.github/workflows/evidence.yml b/.github/workflows/evidence.yml index 4535974..3953d94 100644 --- a/.github/workflows/evidence.yml +++ b/.github/workflows/evidence.yml @@ -67,7 +67,7 @@ jobs: - name: Set up Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 with: - toolchain: '1.98.0' + toolchain: '1.98.1' - name: Prove this is the native architecture, not emulation env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5597642..6967d84 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,7 +61,7 @@ jobs: - name: Set up Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0 with: - toolchain: '1.98.0' + toolchain: '1.98.1' # This action caches by default, and a release must not build on top # of a cache. A run with weaker trust than this one can write the # cache a release would restore, so a poisoned entry would be baked diff --git a/README.md b/README.md index 90d3caa..47a5aee 100644 --- a/README.md +++ b/README.md @@ -17,22 +17,23 @@ instructions, skills, commands, hooks, MCP entries, plugins and settings togethe > verifies and installs with the network gone. > > `launch` starts the exact executable that install placed, never a name -> found on `PATH`, and points the product at the target through the -> environment variable its own documentation names. +> found on `PATH`, and runs it under a copied process home: the surfaces +> this product resolves from `HOME` itself are overlaid out of the target, +> so the session it assembles is the target's and not the caller's. ## Using it ```bash cursor-setup-system list -cursor-setup-system install baseline --target ~/.tool-config -cursor-setup-system status --target ~/.tool-config -cursor-setup-system select full-auto --target ~/.tool-config -cursor-setup-system diff --target ~/.tool-config -cursor-setup-system reinstall --target ~/.tool-config -cursor-setup-system backups --target ~/.tool-config -cursor-setup-system hold --backup slot-000000000001 --reason "before the experiment" --target ~/.tool-config -cursor-setup-system restore --backup slot-000000000001 --target ~/.tool-config -cursor-setup-system remove --target ~/.tool-config +cursor-setup-system install baseline --target ~/.cursor +cursor-setup-system status --target ~/.cursor +cursor-setup-system select full-auto --target ~/.cursor +cursor-setup-system diff --target ~/.cursor +cursor-setup-system reinstall --target ~/.cursor +cursor-setup-system backups --target ~/.cursor +cursor-setup-system hold --backup slot-000000000001 --reason "before the experiment" --target ~/.cursor +cursor-setup-system restore --backup slot-000000000001 --target ~/.cursor +cursor-setup-system remove --target ~/.cursor ``` Every command takes an explicit `--target`. There is no default and no fallback @@ -47,8 +48,8 @@ seven setup systems, expressed in each product's own format: | | | | --- | --- | -| `baseline` | a working floor: instructions plus a conservative configuration | -| `minimal` | the product's own defaults, and the state a restore proves it can reach | +| `baseline` | a working floor: instructions plus the shared autonomous posture | +| `minimal` | instructions plus the shared autonomous posture, and nothing else | | `full-auto` | nothing asked and nothing sandboxed, in this product's own keys | | `nddev-builder` | the full-auto posture plus the product-native NDDev authoring toolkit | diff --git a/SUPPORT.md b/SUPPORT.md index e3f3408..b4d82a4 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -34,23 +34,18 @@ A provider that advertised an operation it cannot perform would let a caller ask for something that cannot be honoured, which is worse than not offering it. All five core operations do work: `backup`, `restore`, `remove`, `install` and -`replace`, both from the local setup catalog and from an `ai-stp-bundle/1` +`replace`, both from the local setup catalog and from an `ai-stp-bundle/2` arriving over the wire. ## Using this against a home you already have -**An owned namespace is removed whole.** The table below says what this build -owns; `remove` deletes each of those paths entirely, and a backup slot holds -what was there first. That includes content this build never wrote -- if the -product itself put a key in a configuration file this provider owns, `remove` -takes the file, not the keys this provider added to it. +**Removal follows receipts, not namespaces.** The table below says what this +build owns; `remove` withdraws the files this provider recorded writing, and +in a JSON file it owns it strips the keys it added rather than taking the +file. Anything under those paths this build never wrote stays. Emptying every +owned namespace is a separate, explicitly named operation: `reset`. -Measured, with the real product: launching Codex through `launch` and running -`mcp add` writes `~/.codex/config.toml` with an `[mcp_servers.*]` entry; a -later `install` captures that file into a slot and replaces it; a later -`remove` deletes it. The entry is not lost -- `backups` lists the slot as -*before install, setup none*, and restoring it returns the file byte for byte --- but it is not in the target either. +No credential-free command is measured writing this product's home -- the dated measurement lives in `references/` and the absence is recorded, not assumed. The receipt discipline is the same for whatever arrives later: a file this provider wrote is captured into a slot before the next `install`, withdrawn by `remove`, and returned byte for byte by `restore`. So: point `--target` at a home you are willing to have managed. `backups --target ` names every earlier state and which setup each preceded, and @@ -229,19 +224,19 @@ So the page documents a directory the current product does not read. Recorded at **Re-measured 2026-09-02 when the pin moved to 2026.08.31-4057e58**, this time across every JavaScript member of the package: `computeAgentsDirs()` is unchanged -- the workspace join and, under third-party extensibility, the workspace's `.claude/agents` -- and a home-joined form is still absent while the invented control is absent and the home joins for `commands`, `hooks.json`, `mcp.json` and `rules` are present. The consumer's cursor#94 asked for this directory at the home on the strength of the `**/.cursor/agents` globs, which are workspace-index rules. The answer at the home stays no; the workspace surface is declared under the `project` scope, where the product actually reads it. ([source](https://cursor.com/docs/subagents)) -**`hooks`** -- **Corrected 2026-08-28: a user-level file exists.** This row read "a plugin manifest key, not a directory under the config home". The product resolves `userConfigPath: join(homedir(), ".cursor", "hooks.json")`, alongside an enterprise path and the manifest key. Not owned, for the same reason as `rules` and `commands`. Raised. ([source](https://cursor.com/docs/hooks) -- measured in the 2026.08.25-3e8eec8 linux/x86_64 bytes (sha256:7a212e5a...), digest verified before reading) +**`hooks`** -- **Corrected 2026-09-29: the comparison this ended on no longer holds.** The product resolves `userConfigPath: join(homedir(), ".cursor", "hooks.json")`, and the file form `hooks.json` is owned -- as are `rules` and `commands`, added 2026-08-28. What stays declined is the *directory* `hooks` this row names: ownership is by exact surface, and the directory form the manifest key implies is not a path this provider writes. ([source](https://cursor.com/docs/hooks) -- measured in the 2026.08.25-3e8eec8 linux/x86_64 bytes (sha256:7a212e5a...), digest verified before reading) **`NDDEV-CURSOR-PROVIDER.json`** -- This provider's own state file: which setup is applied, the identity it recorded, and which slot reverses the last operation. Written by every operation and excluded from target identity, because counting it would leave a target different from the identity the operation just wrote. Not a projection surface and never ownable as one. (this provider's own contract; no vendor page is involved) **`.cursor-setup-system`** -- This provider's own control directory: the target lock, the backup slots and their payloads. Kept out of the declaration for the same reason as the state file, and recorded here because the declined list is where a reader looks before opening a file to find out what it is. (this provider's own contract; no vendor page is involved) -**`plugins/cache`** -- The product's own plugin cache, a sibling of the owned `plugins/local`. Named in the same joins. It matters because this provider owns the parent `plugins` during the transition window, so a `remove` takes this with it. (measured in the 2026.08.25-3e8eec8 linux/x86_64 bytes (sha256:7a212e5a...), digest verified before reading) +**`plugins/cache`** -- The product's own plugin cache, a sibling of the owned `plugins/local`. Corrected 2026-09-29: the parent `plugins` is no longer owned -- owning it made `remove` take the product's own siblings, which is the cost that removed the declaration -- so a `remove` leaves this where it is. (measured in the 2026.08.25-3e8eec8 linux/x86_64 bytes (sha256:7a212e5a...), digest verified before reading) -**`plugins/marketplaces`** -- Where the product records the marketplaces a person added, sibling to `plugins/local`. Taken by a `remove` of the owned parent, which is the concrete cost of the transition window. (measured in the 2026.08.25-3e8eec8 linux/x86_64 bytes (sha256:7a212e5a...), digest verified before reading) +**`plugins/marketplaces`** -- Where the product records the marketplaces a person added, sibling to `plugins/local`. Corrected 2026-09-29: the parent `plugins` is no longer owned, so `remove` no longer takes this with it. (measured in the 2026.08.25-3e8eec8 linux/x86_64 bytes (sha256:7a212e5a...), digest verified before reading) -**`plugins/local-marketplaces.json`** -- The product's record of locally added marketplaces. Same sibling relationship and the same consequence. (measured in the 2026.08.25-3e8eec8 linux/x86_64 bytes (sha256:7a212e5a...), digest verified before reading) +**`plugins/local-marketplaces.json`** -- The product's record of locally added marketplaces. Same sibling relationship and, after 2026-09-29, the same standing: the parent is not owned, so `remove` leaves it alone. (measured in the 2026.08.25-3e8eec8 linux/x86_64 bytes (sha256:7a212e5a...), digest verified before reading) -**`plugins/installed_plugins.json`** -- The product's own record of what it installed. A `remove` of the owned parent takes it, and the product then no longer knows about plugins a person installed by hand. Recoverable from the capture that runs first, and the sharpest single reason the window should close when the consumer's corpus objects naming `plugins` retire. (measured in the 2026.08.25-3e8eec8 linux/x86_64 bytes (sha256:7a212e5a...), digest verified before reading) +**`plugins/installed_plugins.json`** -- The product's own record of what it installed. Corrected 2026-09-29: the parent `plugins` is no longer owned, so `remove` leaves this record -- and the product's knowledge of hand-installed plugins -- alone. (measured in the 2026.08.25-3e8eec8 linux/x86_64 bytes (sha256:7a212e5a...), digest verified before reading) **`cli-runtime-state`** -- One row for the subtree a run leaves behind: `agent-cli-state.json`, `ai-tracking/ai-code-tracking.db`, `cli-workspaces.json`, `ide_state.json`, `plans`, `projects`. The product's own lifetime. (measured from the 2026.08.25-3e8eec8 bundle) diff --git a/crates/cursor-setup-system/src/main.rs b/crates/cursor-setup-system/src/main.rs index f433444..85550b9 100644 --- a/crates/cursor-setup-system/src/main.rs +++ b/crates/cursor-setup-system/src/main.rs @@ -34,8 +34,8 @@ pub const CURSOR: Harness = Harness { documented_config_home: "~/.cursor", config_home_env: "CURSOR_CONFIG_DIR", // **Partial, and this is the one the inference got wrong.** This baseline's - // own note has said since 2026-08-28 that `cli-config.json` is *"one of the - // eight this build owns"* that follows the variable: `rules`, `commands`, + // own note has said since 2026-08-28 that `cli-config.json` is the one + // owned surface that follows the variable: `rules`, `commands`, // `hooks.json`, `mcp.json` and the plugin pair are built from a literal // join to the process home in `cursor-config/dist/paths.js` and reach no // resolver at all. The declaration said launch anyway, because the rule @@ -76,7 +76,7 @@ pub const CURSOR: Harness = Harness { // `cursor` -- not `.cursor` -- then falls back to the home. The data // root reads `CURSOR_DATA_DIR` and is not XDG-aware. // - // **Of the eight namespaces this build owns, exactly one goes through + // **Of the seven namespaces this build owns, exactly one goes through // either.** `cli-config.json` is `join(configRoot(), "cli-config.json")`. // `commands`, `rules`, `hooks.json`, `mcp.json` and the `plugins` pair // are built from a literal `join(homedir(), ".cursor", ...)` and go @@ -85,8 +85,8 @@ pub const CURSOR: Harness = Harness { // `permissions.json` and `statsig-cache.json` -- none of them ours. // // The first note said the product reads `$XDG_CONFIG_HOME/cursor` - // without qualification. True of the resolver, false of seven of the - // eight paths this provider writes: a measurement of one thing stated + // without qualification. True of the resolver, false of six of the + // seven paths this provider writes: a measurement of one thing stated // as a fact about another. config_home_note: "XDG_CONFIG_HOME moves cli-config.json to $XDG_CONFIG_HOME/cursor and moves nothing else this build owns", control_directory: ".cursor-setup-system", @@ -666,7 +666,7 @@ mod tests { } /// Three postures, on every one of the seven. /// - /// `baseline` is a working floor, `minimal` is the product's own defaults, + /// `baseline` is a working floor, `minimal` is the shared autonomous posture and nothing else, /// and `full-auto` asks nothing and sandboxes nothing. A caller who learns /// them on one product knows them on all seven, which is the whole reason /// the names are the estate's rather than each harness's. diff --git a/crates/harness-runtime/src/catalog.rs b/crates/harness-runtime/src/catalog.rs index e7a9a43..2bb51c0 100644 --- a/crates/harness-runtime/src/catalog.rs +++ b/crates/harness-runtime/src/catalog.rs @@ -324,7 +324,10 @@ pub struct Examined { /// half is the part that took the measuring: /// /// * `SKILL.md`, and a file directly under `agents/`, are entry points. Cursor's -/// own generator writes `{name, description}` for exactly these. +/// own generator writes `{name, description}` for exactly these. A codex +/// `agents/.toml` is the same obligation in TOML: the product refuses +/// the file by name when `name` or `description` is absent, so the check +/// reads the keys instead of frontmatter. /// * A file under `references/` is **not** -- it is a document a skill links to, /// and requiring frontmatter there would be inventing a rule. /// * A file under `commands/` is **not**. Cursor's loader builds @@ -357,10 +360,18 @@ pub fn undescribed(setups: &[Setup]) -> Examined { found.push(format!("{} cannot read {name:?}", setup.manifest.id)); continue; }; + let named = if std::path::Path::new(&name) + .extension() + .is_some_and(|extension| extension.eq_ignore_ascii_case("toml")) + { + toml_names + } else { + frontmatter_names + }; for key in ["name", "description"] { - if !frontmatter_names(&text, key) { + if !named(&text, key) { found.push(format!( - "{} ships {name:?} with no `{key}` in its frontmatter, and a component \ + "{} ships {name:?} with no `{key}` the product reads, and a component \ the product cannot describe is one the model cannot choose", setup.manifest.id )); @@ -767,13 +778,34 @@ fn is_entry_point(relative: &str) -> bool { if leaf.eq_ignore_ascii_case("SKILL.md") { return true; } - // `agents/.md`, and only directly under it. + // `agents/.md` and `agents/.toml`, and only directly under it. + // The `.toml` form is how codex declares a role file; it is measured there + // to carry the same two keys in TOML spelling. parts.len() >= 2 && parts[parts.len() - 2] == "agents" && std::path::Path::new(leaf) .extension() .and_then(std::ffi::OsStr::to_str) - .is_some_and(|extension| extension.eq_ignore_ascii_case("md")) + .is_some_and(|extension| { + extension.eq_ignore_ascii_case("md") || extension.eq_ignore_ascii_case("toml") + }) +} + +/// Whether a TOML entry point assigns `key` at the top level. +/// +/// Read by structure rather than by searching the whole file: only lines +/// before the first `[section]` header count, because a `description` three +/// tables down belongs to that table and not to the agent the file names. +fn toml_names(text: &str, key: &str) -> bool { + text.lines() + .take_while(|line| !line.trim_start().starts_with('[')) + .any(|line| { + let line = line.trim_start(); + line.starts_with(key) + && line[key.len()..] + .trim_start_matches([' ', '\t']) + .starts_with('=') + }) } /// Whether a file opens with YAML frontmatter naming `key`. diff --git a/crates/harness-runtime/src/facts.rs b/crates/harness-runtime/src/facts.rs index 35984fa..e5a2dff 100644 --- a/crates/harness-runtime/src/facts.rs +++ b/crates/harness-runtime/src/facts.rs @@ -6,7 +6,7 @@ //! *facts about a product*, verified against its official documentation and //! recorded in a baseline — not behaviour, and not code. //! -//! Holding them as data rather than as five copies of a dispatcher means a +//! Holding them as data rather than as seven copies of a dispatcher means a //! change to the shared logic lands in one place, and a change to a product's //! surface lands in exactly one struct with a test binding it to that product's //! baseline. @@ -137,7 +137,7 @@ pub struct Harness { /// makes the name *visible*, which is the part that was missing: the /// answer was true about what it examined and silent about what decides. /// - /// Empty for six of the seven, and empty because they were asked -- a + /// Empty for three of the seven, and empty because they were asked -- a /// product whose alternate spellings nobody has measured belongs here as /// nothing rather than as a guess. pub shadowing_names: &'static [Shadow], @@ -203,9 +203,9 @@ pub struct Harness { pub projection_kinds: &'static [ProjectionKind], /// Second targets this provider owns, if any. /// - /// Empty for six of the seven. Antigravity is the exception because the - /// product genuinely keeps a workspace copy of five of its surfaces, and - /// `ai_stp#424`/`#425` are the consumer asking for exactly that route. + /// No build leaves this empty today: each declares a `user_root` scope for + /// products that read the shared `~/.agents` convention, a `project` + /// scope for surfaces the product reads from a workspace, or both. pub scoped_projections: &'static [Scoped], /// The largest file count a bundle may carry. pub max_files: u64, @@ -234,12 +234,6 @@ pub struct Harness { /// [`Delivery::Manager`], which is a different statement -- the product is /// installable, but not by fetching bytes whose digest was fixed in advance /// -- and the refusal says which. - /// How the product's own software is installed, when this build can do it. - /// - /// `None` means the software lifecycle is not offered at all. So does a - /// [`Delivery::Manager`], which is a different statement -- the product is - /// installable, but not by fetching bytes whose digest was fixed in advance - /// -- and the refusal says which. pub software: Option, /// Target-relative path of the user-global instruction attachment. /// diff --git a/crates/harness-runtime/src/lib.rs b/crates/harness-runtime/src/lib.rs index a7b945f..4a583d7 100644 --- a/crates/harness-runtime/src/lib.rs +++ b/crates/harness-runtime/src/lib.rs @@ -1,12 +1,12 @@ //! The provider command runtime every NDDev setup system shares. //! -//! Five products, one set of commands. What differs between them is not +//! Seven products, one set of commands. What differs between them is not //! behaviour but *facts*: which directory a product configures, which files //! inside it this provider owns, and which files belong to the product and must //! be left alone. [`Harness`] holds those facts; [`wire::dispatch`] performs the //! commands over them. //! -//! Written this way, a change to the shared logic lands once instead of five +//! Written this way, a change to the shared logic lands once instead of seven //! times, and a change to one product's surface lands in exactly one struct that //! a test binds to that product's verified baseline. //! @@ -14,7 +14,7 @@ //! //! It performs all five core operations. `backup`, `restore` and `remove` read //! the target, a backup slot, or the provider's own state. `install` and -//! `replace` materialize an `ai-stp-bundle/1` the consumer sends, or a complete +//! `replace` materialize an `ai-stp-bundle/2` the consumer sends, or a complete //! setup from the local catalog when the owner asks for one by name. //! //! The software lifecycle is optional in the contract, and a harness declares diff --git a/crates/provider-v3/src/bundle.rs b/crates/provider-v3/src/bundle.rs index 18bdf74..3f322b4 100644 --- a/crates/provider-v3/src/bundle.rs +++ b/crates/provider-v3/src/bundle.rs @@ -34,10 +34,10 @@ use crate::zip; /// The digest domain for a bundle manifest. pub const BUNDLE_DOMAIN: &str = "ai-stp:bundle:v1"; -/// The original format tag, kept byte-identical during the v2 rollout. +/// The adaptation-bound format, the only tag a production bundle carries. pub const BUNDLE_FORMAT: &str = "ai-stp-bundle/2"; -/// The adaptation-bound format. +/// The original format tag, retired by the v2 rollout and refused on read. #[cfg(test)] const RETIRED_BUNDLE_FORMAT_V1: &str = "ai-stp-bundle/1"; @@ -237,7 +237,7 @@ pub struct ComponentAdaptationBinding { pub struct Manifest { /// Schema of this manifest. pub schema_version: u32, - /// Always `ai-stp-bundle/1`. + /// Always `ai-stp-bundle/2`. pub bundle_format: String, /// The *bundle* protocol, which is 1. Not the provider protocol. pub protocol_version: u32, diff --git a/crates/provider-v3/src/vocabulary.rs b/crates/provider-v3/src/vocabulary.rs index 1c1b0fc..12204b4 100644 --- a/crates/provider-v3/src/vocabulary.rs +++ b/crates/provider-v3/src/vocabulary.rs @@ -399,8 +399,8 @@ impl ProjectionKind { /// A target a projection profile owns, other than the product's own home. /// -/// The kit's schema enumerates exactly one value today, and the global scope is -/// deliberately not among them: the global profile is declared by +/// The kit's schema enumerates exactly two values today, and the global scope +/// is deliberately not among them: the global profile is declared by /// `projection_profile` itself, and two statements about one fact are a defect /// even while they agree. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] diff --git a/install.ps1 b/install.ps1 index 8ab4756..76290f8 100644 --- a/install.ps1 +++ b/install.ps1 @@ -8,7 +8,7 @@ [CmdletBinding()] param( [string]$Version = "0.0.81", - [string]$InstallDir = "$env:LOCALAPPDATA\Programs\cursor-setup-system" + [string]$InstallDir = $(if ($env:CURSOR_INSTALL_DIR) { $env:CURSOR_INSTALL_DIR } else { "$env:LOCALAPPDATA\Programs\cursor-setup-system" }) ) $ErrorActionPreference = "Stop" diff --git a/references/cursor-baseline.json b/references/cursor-baseline.json index 10af70e..1f82d8e 100644 --- a/references/cursor-baseline.json +++ b/references/cursor-baseline.json @@ -262,7 +262,7 @@ }, { "path": "hooks", - "reason": "**Corrected 2026-08-28: a user-level file exists.** This row read \"a plugin manifest key, not a directory under the config home\". The product resolves `userConfigPath: join(homedir(), \".cursor\", \"hooks.json\")`, alongside an enterprise path and the manifest key. Not owned, for the same reason as `rules` and `commands`. Raised.", + "reason": "**Corrected 2026-09-29: the comparison this ended on no longer holds.** The product resolves `userConfigPath: join(homedir(), \".cursor\", \"hooks.json\")`, and the file form `hooks.json` is owned -- as are `rules` and `commands`, added 2026-08-28. What stays declined is the *directory* `hooks` this row names: ownership is by exact surface, and the directory form the manifest key implies is not a path this provider writes.", "source": "https://cursor.com/docs/hooks; measured in the 2026.08.25-3e8eec8 linux/x86_64 bytes (sha256:7a212e5a...), digest verified before reading" }, { @@ -277,22 +277,22 @@ }, { "path": "plugins/cache", - "reason": "The product's own plugin cache, a sibling of the owned `plugins/local`. Named in the same joins. It matters because this provider owns the parent `plugins` during the transition window, so a `remove` takes this with it.", + "reason": "The product's own plugin cache, a sibling of the owned `plugins/local`. Corrected 2026-09-29: the parent `plugins` is no longer owned -- owning it made `remove` take the product's own siblings, which is the cost that removed the declaration -- so a `remove` leaves this where it is.", "source": "measured in the 2026.08.25-3e8eec8 linux/x86_64 bytes (sha256:7a212e5a...), digest verified before reading" }, { "path": "plugins/marketplaces", - "reason": "Where the product records the marketplaces a person added, sibling to `plugins/local`. Taken by a `remove` of the owned parent, which is the concrete cost of the transition window.", + "reason": "Where the product records the marketplaces a person added, sibling to `plugins/local`. Corrected 2026-09-29: the parent `plugins` is no longer owned, so `remove` no longer takes this with it.", "source": "measured in the 2026.08.25-3e8eec8 linux/x86_64 bytes (sha256:7a212e5a...), digest verified before reading" }, { "path": "plugins/local-marketplaces.json", - "reason": "The product's record of locally added marketplaces. Same sibling relationship and the same consequence.", + "reason": "The product's record of locally added marketplaces. Same sibling relationship and, after 2026-09-29, the same standing: the parent is not owned, so `remove` leaves it alone.", "source": "measured in the 2026.08.25-3e8eec8 linux/x86_64 bytes (sha256:7a212e5a...), digest verified before reading" }, { "path": "plugins/installed_plugins.json", - "reason": "The product's own record of what it installed. A `remove` of the owned parent takes it, and the product then no longer knows about plugins a person installed by hand. Recoverable from the capture that runs first, and the sharpest single reason the window should close when the consumer's corpus objects naming `plugins` retire.", + "reason": "The product's own record of what it installed. Corrected 2026-09-29: the parent `plugins` is no longer owned, so `remove` leaves this record -- and the product's knowledge of hand-installed plugins -- alone.", "source": "measured in the 2026.08.25-3e8eec8 linux/x86_64 bytes (sha256:7a212e5a...), digest verified before reading" }, { @@ -342,7 +342,7 @@ } ], "config_home_env": "CURSOR_CONFIG_DIR", - "config_home_env_note": "Measured 2026-08-28: the product wrote into the directory this variable named, and into no other configuration home.\n\n**The product's resolver has two overrides, not one, and the second renames the leaf.** Confirmed at the line in the pinned bundle:\n\n```js\nconst e = process.env.CURSOR_CONFIG_DIR; if (e?.trim()) return e;\nconst t = process.env.XDG_CONFIG_HOME;\nreturn t?.trim() ? join(t, \"cursor\") : join(homedir(), \".cursor\");\n```\n\nSo a Linux user with `XDG_CONFIG_HOME` set has their configuration at `$XDG_CONFIG_HOME/cursor` -- **`cursor`, not `.cursor`**. There is also a separate `CURSOR_DATA_DIR`, which is *not* XDG-aware and defaults to `~/.cursor` regardless, so config and data can be split.\n\n**The variable this build sets is still the right one.** `CURSOR_CONFIG_DIR` is the first branch of that resolver, so a `launch` pointing the product at a target wins over XDG. This field stays a single name deliberately: a list would claim this build sets more than one variable, which it does not and should not.\n\n**Corrected 2026-08-28, hours after the first version shipped.** That version said the product reads `$XDG_CONFIG_HOME/cursor` and stopped there. Tracing which surfaces actually go through the resolver shows **one of the eight this build owns** does: `cli-config.json`, built as `join(configRoot(), \"cli-config.json\")` in `cursor-config/dist/paths.js`.\n\n`commands`, `rules`, `hooks.json`, `mcp.json` and the `plugins` pair are built from a literal `join(homedir(), \".cursor\", …)` and reach neither resolver, so XDG does not move them. The config root carries `acp-config.json`, `acp-sessions`, `chats`, `permissions.json` and `statsig-cache.json`; the data root (`CURSOR_DATA_DIR`, not XDG-aware) carries `projects` and `computer-use`. None of those seven is ours.\n\nSo `documented_config_home` stays `~/.cursor` and is right for seven of eight. The lesson is the estate's own, one level up: measuring a resolver and then writing a note about the home states a fact about the thing not measured.", + "config_home_env_note": "Measured 2026-08-28: the product wrote into the directory this variable named, and into no other configuration home.\n\n**The product's resolver has two overrides, not one, and the second renames the leaf.** Confirmed at the line in the pinned bundle:\n\n```js\nconst e = process.env.CURSOR_CONFIG_DIR; if (e?.trim()) return e;\nconst t = process.env.XDG_CONFIG_HOME;\nreturn t?.trim() ? join(t, \"cursor\") : join(homedir(), \".cursor\");\n```\n\nSo a Linux user with `XDG_CONFIG_HOME` set has their configuration at `$XDG_CONFIG_HOME/cursor` -- **`cursor`, not `.cursor`**. There is also a separate `CURSOR_DATA_DIR`, which is *not* XDG-aware and defaults to `~/.cursor` regardless, so config and data can be split.\n\n**The variable this build sets is still the right one.** `CURSOR_CONFIG_DIR` is the first branch of that resolver, so a `launch` pointing the product at a target wins over XDG. This field stays a single name deliberately: a list would claim this build sets more than one variable, which it does not and should not.\n\n**Corrected 2026-08-28, hours after the first version shipped.** That version said the product reads `$XDG_CONFIG_HOME/cursor` and stopped there. Tracing which surfaces actually go through the resolver shows **one of the eight this build owns** does: `cli-config.json`, built as `join(configRoot(), \"cli-config.json\")` in `cursor-config/dist/paths.js`.\n\n`commands`, `rules`, `hooks.json`, `mcp.json` and the `plugins` pair are built from a literal `join(homedir(), \".cursor\", …)` and reach neither resolver, so XDG does not move them. The config root carries `acp-config.json`, `acp-sessions`, `chats`, `permissions.json` and `statsig-cache.json`; the data root (`CURSOR_DATA_DIR`, not XDG-aware) carries `projects` and `computer-use`. None of those seven is ours.\n\nSo `documented_config_home` stays `~/.cursor` and is right for seven of eight. The lesson is the estate's own, one level up: measuring a resolver and then writing a note about the home states a fact about the thing not measured.\n\n**Count corrected 2026-09-29.** The sentence above predates the `plugins` withdrawal: this build owns seven namespaces, not eight -- `cli-config.json`, `plugins/local`, `rules`, `commands`, `hooks.json`, `mcp.json`, `skills`. Exactly one still goes through the resolver it describes.", "config_home_note": "XDG_CONFIG_HOME moves cli-config.json to $XDG_CONFIG_HOME/cursor and moves nothing else this build owns", "configuration_format": { "file": "cli-config.json", @@ -533,7 +533,7 @@ "version": "2026.09.23-86fc751", "verified_at": "2026-09-25T11:49:37+00:00" }, - "setup_catalogue_digest": "sha256:2a47795c27692dec6d0d36afba92a43ba5be24142f05682b72a7b6adc99142bf", + "setup_catalogue_digest": "sha256:bbebdb33bb11a84660384b29b8f09b87be57de4bbde0fa89a8082fe402e0c265", "previous_pin_recovery": { "measured_at": "2026-08-31", "note": "The vendor exposes no release index, but this repository had already measured the immediately preceding release before the pin moved: exact version plus the four Unix URLs, lengths and digests. All six derived immutable URLs still answer; the two Windows archives were downloaded and hashed now, and their command-script members were read from the ZIPs. This is recovered measurement, not a second independently moving pin, and the next ordinary refresh replaces it through the same outgoing-current rotation as every other harness." diff --git a/scripts/evidence.py b/scripts/evidence.py index c450a6c..36eb96e 100644 --- a/scripts/evidence.py +++ b/scripts/evidence.py @@ -172,10 +172,12 @@ def plan( ] ) if answer.get("reason") == "unsupported_platform": - # An honest answer, not a failure. Cursor publishes no Windows build, - # and the provider says so by name rather than planning something it - # could not apply. Treating that as a red would make this job report a - # vendor's product range as a defect of ours. + # An honest answer, not a failure. When a vendor's published manifest + # carries no build for a declared platform, the provider says so by + # name rather than planning something it could not apply. Treating + # that as a red would make this job report a vendor's product range + # as a defect of ours. No harness answers it today; the path stays + # wired because the reason is contract, not decoration. raise NothingToProve(str(answer.get("detail", ""))) if answer.get("state") != "planned": raise Failed( diff --git a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-commands.md b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-commands.md index 2e0940a..3357ace 100644 --- a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-commands.md +++ b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-commands.md @@ -41,6 +41,6 @@ Generated from the same rows as the section above, for every harness in this est ## Before you ship one -- **The surface is declared, so the component is a promise.** Every kind this provider declares is a promise of a rollback. A component written to a path the declaration does not carry is installed by nobody and removed by nobody. -- **Name it once.** Where the product derives identity from the directory or the filename, the frontmatter `name` is either redundant or a second place to be wrong. Keep them equal. -- **Read it back.** After an install, look at the file where the product reads it, not at the step that put it there. +- **The surface is declared, so the component is a promise.** Every kind this provider declares is a promise of a rollback. A component written to a path the declaration does not carry is installed by nobody and removed by nobody. +- **Name it once.** Where the product derives identity from the directory or the filename, the frontmatter `name` is either redundant or a second place to be wrong. Keep them equal. +- **Read it back.** After an install, look at the file where the product reads it, not at the step that put it there. diff --git a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-hooks.md b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-hooks.md index c745cb6..5eed7fa 100644 --- a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-hooks.md +++ b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-hooks.md @@ -26,6 +26,6 @@ Generated from the vendor's own reference and the pinned binary. Do not edit: th ## Before you ship one -- **The surface is declared, so the component is a promise.** Every kind this provider declares is a promise of a rollback. A component written to a path the declaration does not carry is installed by nobody and removed by nobody. -- **Name it once.** Where the product derives identity from the directory or the filename, the frontmatter `name` is either redundant or a second place to be wrong. Keep them equal. -- **Read it back.** After an install, look at the file where the product reads it, not at the step that put it there. +- **The surface is declared, so the component is a promise.** Every kind this provider declares is a promise of a rollback. A component written to a path the declaration does not carry is installed by nobody and removed by nobody. +- **Name it once.** Where the product derives identity from the directory or the filename, the frontmatter `name` is either redundant or a second place to be wrong. Keep them equal. +- **Read it back.** After an install, look at the file where the product reads it, not at the step that put it there. diff --git a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-mcp.md b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-mcp.md index 4a58ea8..f5fa08a 100644 --- a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-mcp.md +++ b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-mcp.md @@ -47,6 +47,6 @@ Generated from the same rows as the section above, for every harness in this est ## Before you ship one -- **The surface is declared, so the component is a promise.** Every kind this provider declares is a promise of a rollback. A component written to a path the declaration does not carry is installed by nobody and removed by nobody. -- **Name it once.** Where the product derives identity from the directory or the filename, the frontmatter `name` is either redundant or a second place to be wrong. Keep them equal. -- **Read it back.** After an install, look at the file where the product reads it, not at the step that put it there. +- **The surface is declared, so the component is a promise.** Every kind this provider declares is a promise of a rollback. A component written to a path the declaration does not carry is installed by nobody and removed by nobody. +- **Name it once.** Where the product derives identity from the directory or the filename, the frontmatter `name` is either redundant or a second place to be wrong. Keep them equal. +- **Read it back.** After an install, look at the file where the product reads it, not at the step that put it there. diff --git a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-plugins.md b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-plugins.md index 3b630c2..1959cd5 100644 --- a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-plugins.md +++ b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-plugins.md @@ -71,6 +71,6 @@ Generated from the same rows as the section above, for every harness in this est ## Before you ship one -- **The surface is declared, so the component is a promise.** Every kind this provider declares is a promise of a rollback. A component written to a path the declaration does not carry is installed by nobody and removed by nobody. -- **Name it once.** Where the product derives identity from the directory or the filename, the frontmatter `name` is either redundant or a second place to be wrong. Keep them equal. -- **Read it back.** After an install, look at the file where the product reads it, not at the step that put it there. +- **The surface is declared, so the component is a promise.** Every kind this provider declares is a promise of a rollback. A component written to a path the declaration does not carry is installed by nobody and removed by nobody. +- **Name it once.** Where the product derives identity from the directory or the filename, the frontmatter `name` is either redundant or a second place to be wrong. Keep them equal. +- **Read it back.** After an install, look at the file where the product reads it, not at the step that put it there. diff --git a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-skills.md b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-skills.md index e0fc6d7..42bb2af 100644 --- a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-skills.md +++ b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/authoring-skills.md @@ -62,6 +62,6 @@ Generated from the same rows as the section above, for every harness in this est ## Before you ship one -- **The surface is declared, so the component is a promise.** Every kind this provider declares is a promise of a rollback. A component written to a path the declaration does not carry is installed by nobody and removed by nobody. -- **Name it once.** Where the product derives identity from the directory or the filename, the frontmatter `name` is either redundant or a second place to be wrong. Keep them equal. -- **Read it back.** After an install, look at the file where the product reads it, not at the step that put it there. +- **The surface is declared, so the component is a promise.** Every kind this provider declares is a promise of a rollback. A component written to a path the declaration does not carry is installed by nobody and removed by nobody. +- **Name it once.** Where the product derives identity from the directory or the filename, the frontmatter `name` is either redundant or a second place to be wrong. Keep them equal. +- **Read it back.** After an install, look at the file where the product reads it, not at the step that put it there. diff --git a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/surfaces.md b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/surfaces.md index 6b82f63..ac3384a 100644 --- a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/surfaces.md +++ b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/surfaces.md @@ -85,13 +85,13 @@ whole, which would take or revert a neighbour's work. - **`AGENTS.md`** — The CLI reads AGENTS.md at the project root and upward, not from ~/.cursor. Global user rules are set in the application under Customize -> Rules and have no file under the config home; the absence is a standing community request. - **`agents`** — A plugin manifest key. The directory form `join(this.workspacePath, ".cursor", "agents")` is workspace-scoped only -- unlike `rules`, `commands`, `hooks.json` and `mcp.json`, which all resolve against the home directory as well. measured in the 2026.08.25-3e8eec8 linux/x86_64 bytes (sha256:7a212e5a...), digest verified before reading, and it is the one of the five where the original reason survives the measurement. -- **`hooks`** — **Corrected 2026-08-28: a user-level file exists.** This row read "a plugin manifest key, not a directory under the config home". The product resolves `userConfigPath: join(homedir(), ".cursor", "hooks.json")`, alongside an enterprise path and the manifest key. Not owned, for the same reason as `rules` and `commands`. Raised. +- **`hooks`** — **Corrected 2026-09-29: the comparison this ended on no longer holds.** The product resolves `userConfigPath: join(homedir(), ".cursor", "hooks.json")`, and the file form `hooks.json` is owned -- as are `rules` and `commands`, added 2026-08-28. What stays declined is the *directory* `hooks` this row names: ownership is by exact surface, and the directory form the manifest key implies is not a path this provider writes. - **`NDDEV-CURSOR-PROVIDER.json`** — This provider's own state file: which setup is applied, the identity it recorded, and which slot reverses the last operation. Written by every operation and excluded from target identity, because counting it would leave a target different from the identity the operation just wrote. Not a projection surface and never ownable as one. - **`.cursor-setup-system`** — This provider's own control directory: the target lock, the backup slots and their payloads. Kept out of the declaration for the same reason as the state file, and recorded here because the declined list is where a reader looks before opening a file to find out what it is. -- **`plugins/cache`** — The product's own plugin cache, a sibling of the owned `plugins/local`. Named in the same joins. It matters because this provider owns the parent `plugins` during the transition window, so a `remove` takes this with it. -- **`plugins/marketplaces`** — Where the product records the marketplaces a person added, sibling to `plugins/local`. Taken by a `remove` of the owned parent, which is the concrete cost of the transition window. -- **`plugins/local-marketplaces.json`** — The product's record of locally added marketplaces. Same sibling relationship and the same consequence. -- **`plugins/installed_plugins.json`** — The product's own record of what it installed. A `remove` of the owned parent takes it, and the product then no longer knows about plugins a person installed by hand. Recoverable from the capture that runs first, and the sharpest single reason the window should close when the consumer's corpus objects naming `plugins` retire. +- **`plugins/cache`** — The product's own plugin cache, a sibling of the owned `plugins/local`. Corrected 2026-09-29: the parent `plugins` is no longer owned -- owning it made `remove` take the product's own siblings, which is the cost that removed the declaration -- so a `remove` leaves this where it is. +- **`plugins/marketplaces`** — Where the product records the marketplaces a person added, sibling to `plugins/local`. Corrected 2026-09-29: the parent `plugins` is no longer owned, so `remove` no longer takes this with it. +- **`plugins/local-marketplaces.json`** — The product's record of locally added marketplaces. Same sibling relationship and, after 2026-09-29, the same standing: the parent is not owned, so `remove` leaves it alone. +- **`plugins/installed_plugins.json`** — The product's own record of what it installed. Corrected 2026-09-29: the parent `plugins` is no longer owned, so `remove` leaves this record -- and the product's knowledge of hand-installed plugins -- alone. - **`cli-runtime-state`** — One row for the subtree a run leaves behind: `agent-cli-state.json`, `ai-tracking/ai-code-tracking.db`, `cli-workspaces.json`, `ide_state.json`, `plans`, `projects`. The product's own lifetime. - **`policy.json`** — An enterprise policy file, sibling to `managed/active-team-hooks/hooks.json`. Administrator-pushed and never a setup's to write, for the reason grok's `managed_config.toml` row gives at greater length. - **`skills-cursor`** — The same skill-root table flags this one `builtin: true` -- it is the product's own shipped skills, not a place a person or a consumer writes. Owning it would put this provider's backup and remove across bytes the product manages for itself. Recorded rather than left absent because the directory is real, sits beside the owned `skills`, and a reader who found it would otherwise have to repeat this search. diff --git a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/validation-release.md b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/validation-release.md index dba36da..f45d0f8 100644 --- a/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/validation-release.md +++ b/setups/nddev-builder/home/plugins/local/nddev-builder/skills/nddev-builder/references/validation-release.md @@ -8,8 +8,8 @@ When changing provider implementation, run that checkout's CI checks: ```bash cargo fmt --all --check -cargo clippy --workspace --all-targets -- -D warnings -cargo test --workspace +cargo clippy --locked --all-targets -- -D warnings +cargo test --locked --all-targets ``` Report each result and any unavailable check. The cargo commands apply only diff --git a/tools/build_crates_io.py b/tools/build_crates_io.py index 30e85b4..d292da6 100644 --- a/tools/build_crates_io.py +++ b/tools/build_crates_io.py @@ -172,7 +172,17 @@ def main() -> int: if args.self_check: with tempfile.TemporaryDirectory(prefix="nddev-crates-io-") as temporary: root = Path(temporary) - for harness in HARNESSES: + # This workspace carries all seven crates; a rendered public tree + # carries exactly one. The check walks whichever exist rather than + # asserting the shared layout in a tree that never had it. + harnesses = [ + harness + for harness in HARNESSES + if (ROOT / "crates" / f"{harness}-setup-system").is_dir() + ] + if not harnesses: + raise SystemExit("no harness crate under crates/ -- nothing to self-check") + for harness in harnesses: package = build(harness, root, args.version) document = package.joinpath("Cargo.toml").read_text(encoding="utf-8") expected = f'name = "{harness}-setup-system"' @@ -184,23 +194,29 @@ def main() -> int: stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) - codex = root / "codex-setup-system" + first = harnesses[0] + built = root / f"{first}-setup-system" subprocess.run( - ["cargo", "build", "--quiet", "--manifest-path", str(codex / "Cargo.toml")], + ["cargo", "build", "--quiet", "--manifest-path", str(built / "Cargo.toml")], check=True, ) answer = subprocess.run( - [codex / "target/debug/codex-setup-system", "provider-info"], + [built / "target/debug" / f"{first}-setup-system", "provider-info"], check=True, capture_output=True, text=True, ) info = json.loads(answer.stdout) - if info["provider_id"] != "codex-setup-system" or info["projection_profile"][ + if info["provider_id"] != f"{first}-setup-system" or info["projection_profile"][ "bundle_formats" ] != ["ai-stp-bundle/2"]: - raise SystemExit("the installed-shape provider-info is not the v2-only Codex provider") - print("crates.io: seven same-name packages; all package, and a standalone provider runs") + raise SystemExit( + f"the installed-shape provider-info is not the v2-only {first} provider" + ) + print( + f"crates.io: {len(harnesses)} same-name package(s); all package, " + "and a standalone provider runs" + ) return 0 if args.out is None: parser.error("--out is required unless --self-check is used") diff --git a/tools/build_wheels.py b/tools/build_wheels.py index 4301695..433d0a9 100644 --- a/tools/build_wheels.py +++ b/tools/build_wheels.py @@ -34,7 +34,7 @@ probe wheel without repairing it first. Usage: - python3 tools/build_wheels.py --harness claude --version 0.0.58 \ + python3 tools/build_wheels.py --harness --version \ --assets --out python3 tools/build_wheels.py --self-check """