From 655d652616e6683de1da23b91885f49b5794338c Mon Sep 17 00:00:00 2001 From: UnbreakableMJ Date: Mon, 24 Aug 2026 21:59:48 +0000 Subject: [PATCH] =?UTF-8?q?fix(orca-skills):=20let=20Orca=20own=20its=20ow?= =?UTF-8?q?n=20skills=20=E2=80=94=20enableOrca=20+=20perSkillLinks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Orca has reported all three vendored skills as "Skipped — the copy here doesn't match the official version" since they were vendored, and the earlier diagnosis in CREDITS.md (the tree is ahead of the binary; pin the revision) was wrong. Reading the app's scanner settles it: observeSkillPackage() throws skill-package-link on any file with nlink != 1, the catch reports the skill as status "unrecognized", and that is the row in the dialog. Store optimisation hardlinks store files — these sat at nlink 5-7 — so no revision pin can clear it. computer-use and orchestration were byte-identical to the installed app's manifest and were flagged just the same. Store files are mode 444 besides, so classifyHomeSkillTopology would mark them read-only even past the byte check. The install location is the problem, so hand it to Orca: - enableOrca (default off) makes the vendored leaves opt-in rather than merged unconditionally. On a host with the Orca app, Orca installs and updates its own copies; turn the flag on only where nothing else provides them. - perSkillLinks renders ~/.agents/skills as a real directory of per-skill symlinks instead of one directory symlink, so `orca skills install` (npx skills add) has somewhere writable to own computer-use/, orca-cli/ and orchestration/ as real nlink=1 directories. A real directory on a skill's name is left alone and reported, never clobbered, and pruning is limited to symlinks pointing into this module's own tree. - packages.skills-with-orca builds the merged tree for the opt-in case. Docs follow the change: a new AGENTS.md section on the Orca tree, a README section, and CREDITS.md's revision guidance corrected — a manifest match is still how the vendored bytes are verified, but it was never what the dialog was measuring. The fan-out section also drops a stale description of a per-skill HM layout this host has not used since the mutable pointer landed. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01M94JK8t6zguGCTDP7udiUv --- AGENTS.md | 78 +++++++++++++++---- README.md | 18 +++++ flake.nix | 166 ++++++++++++++++++++++++++++++++++++----- orca-skills/CREDITS.md | 60 ++++++++------- orca-skills/README.md | 22 +++++- 5 files changed, 282 insertions(+), 62 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5c88f0e..fd18253 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -255,6 +255,45 @@ to match how Construct's loader and flake discover skills, and its own `README.md`/`CREDITS.md` track provenance and the flattening rule. It is excluded from the root flake's `excludedDirs` and packaged separately. +## Vendored Orca skills (`orca-skills/`) + +`orca-skills/` vendors three skills from [Orca](https://github.com/stablyai/orca) +— `computer-use`, `orca-cli`, `orchestration` — **verbatim and unmodified** +(MIT, third-party — Standard §4.2). Never hand-edit them; a new revision comes +in by re-copying upstream's `SKILL.md` and updating `CREDITS.md`. The generic +leaf names `computer-use` and `orchestration` are reserved: Orca's own CLI looks +its skills up by exact leaf name, so a future Spacecraft skill must not claim +either. + +**They are OPT-IN in the flake (`spacecraft.construct.enableOrca`, default off), +and must stay off on any host that runs the Orca app.** Orca installs and +updates its own copies; a copy served from the Nix store makes its updater fail +in a way no amount of re-vendoring fixes: + +- Orca's scanner (`observeSkillPackage`, in the app's `app.asar`) throws + `skill-package-link` on any file with `nlink != 1`. Store optimisation + hardlinks identical files, so every store-served `SKILL.md` eventually has + nlink > 1 — ours sat at 5–7. The throw is caught and reported as status + `unrecognized`: the "The copy here doesn't match the official version" row in + Settings → Update skills. +- Even past that check, store files are mode 444 and + `classifyHomeSkillTopology` marks an unwritable path `read-only`, which the + updater also skips. +- **Byte-identity does not help.** `computer-use` and `orchestration` were + byte-for-byte the official revisions and were flagged just the same. Any note + claiming a revision pin clears the warning is wrong. + +On an Orca host the arrangement is: `enableOrca = false`, plus +`spacecraft.construct.perSkillLinks.enable = true` so `~/.agents/skills` is a +real directory with room for `orca skills install` (`npx skills add`) to own +`computer-use/`, `orca-cli/`, `orchestration/` as real, writable directories. +The module never clobbers a real directory it did not create, and prunes only +symlinks pointing into its own tree. + +Turn `enableOrca` on only where nothing else provides these skills — no Orca +app, an air-gapped host, a container image. `packages.skills-with-orca` builds +that merged tree. + ## Grok skills (`grok-skills/`) Grok uses a **flat** bundle format — `SKILL.md` and any `assets/` / `references/` @@ -311,17 +350,22 @@ byte-identical to it. ## Local agent fan-out (Home Manager hosts) -Local fan-out is managed by **Home Manager**, not by the assistant. Each -per-harness skill path is a real directory provisioned by Home Manager with -per-skill symlinks that chain through the Nix store to this repo: +Local fan-out is managed by **Home Manager**, not by the assistant. Every +per-harness path is a symlink to the canonical `~/.agents/skills`, which under +`mutablePointer` chains through a mutable pointer into the store: ``` -~/.claude/skills/ - → /nix/store/-home-manager-files/.claude/skills/ - → /nix/store/-hm_ - → /spacecraft-software/construct/ +~/.claude/skills → ~/.agents/skills +~/.agents/skills → ~/.local/state/construct/current +~/.local/state/construct/current → …/pinned → /nix/store/-construct-skills ``` +With `perSkillLinks.enable = true` the middle step changes shape: `~/.agents/skills` +is a **real directory** whose entries are per-skill symlinks into +`…/construct/current/`. Same content, but names the module does not carry +stay free for another installer to own — which is what an Orca host needs (see +*Vendored Orca skills* above). + Paths populated by Home Manager: `~/.claude/skills/`, `~/.codex/skills/`, `~/.ai/skills/`, `~/.agent/skills/`. Gemini CLI's scan path is Home Manager's responsibility on this host as well — the assistant does not provision it. @@ -331,9 +375,9 @@ unified path layout for new consumers: install once to `~/.agents/skills/` and symlink every per-harness path (`~/.claude/skills`, `~/.gemini/skills`, `~/.codex/skills`, `~/.ai/skills`, `~/.agent/skills`) to that canonical location. Grok skills install separately to `~/.grok/skills/` because of -their different bundle layout. The hand-written per-skill HM config currently -in use on this host predates the flake and should be migrated to -`spacecraft.construct.enable = true` at the maintainer's convenience. +their different bundle layout. This host is already on the module +(`spacecraft.construct` in `bravais/users/mj/home.nix`, with `mutablePointer` +and a longer `agentPaths` list). **After a PR merges, Home Manager must be rebuilt** before per-harness paths resolve to the new content. The maintainer runs the rebuild manually @@ -344,13 +388,17 @@ Verify after rebuild: ```sh readlink -f ~/.claude/skills/spacecraft-steelbore-standard -# → /spacecraft-software/construct/spacecraft-steelbore-standard +# → /nix/store/-construct-skills/spacecraft-steelbore-standard +sha256sum ~/.claude/skills/spacecraft-steelbore-standard/SKILL.md \ + /spacecraft-software/construct/spacecraft-steelbore-standard/SKILL.md ``` -If the symlink still resolves into a stale `/nix/store/-hm_*` path -(for example after a repo rename), the Home Manager config has not yet been -rebuilt against the new repo path. Until it is, agents read the previous -generation's content even though `origin/main` is current. +The tree is a store copy, not a link back into the checkout, so the check is +byte-equality against the working tree rather than the resolved path. If the +two hashes differ (or the path still resolves into a stale +`/nix/store/-hm_*` from an earlier layout), Home Manager has not been +rebuilt against the current commit — agents read the previous generation's +content even though `origin/main` is ahead. The assistant performs no `rsync`, no symlink setup, and no `home-manager switch`. Its responsibility ends at opening the PR. diff --git a/README.md b/README.md index 7146ac4..7cf94aa 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,24 @@ the §2 catalogue above and ship no `.zip`/`.skill` bundles (Google's `android` CLI installs them from upstream). See the section's README for provenance and the upstream→vendored path mapping. +### Orca skills + +Three skills from **[Orca](https://github.com/stablyai/orca)** — `computer-use`, +`orca-cli`, `orchestration` — are vendored verbatim (MIT, © Lovecast Inc.) under +[`orca-skills/`](orca-skills/). They are third-party, so they stay out of the §2 +catalogue above. + +They are **opt-in** (`spacecraft.construct.enableOrca = true`) and should stay +off wherever the Orca app is installed: Orca installs and updates its own +copies, and its updater rejects a copy served from the Nix store — store files +are hardlinked (`nlink != 1`) and read-only, both of which it refuses to +verify, so it reports every such skill as `Unrecognized` no matter how exactly +the bytes match. Pair `enableOrca = false` with +`spacecraft.construct.perSkillLinks.enable = true`, which renders +`~/.agents/skills` as a real directory so Orca has somewhere writable to own +them. Turn `enableOrca` on where nothing else provides these skills; +`packages.skills-with-orca` builds that tree. + ### Perplexity skills Perplexity caps an uploaded skill zip at **100 files**, which the diff --git a/flake.nix b/flake.nix index d4e96f3..674cd70 100644 --- a/flake.nix +++ b/flake.nix @@ -41,10 +41,26 @@ skillNamesIn (self + "/android-skills") else []; - # Vendored Orca skills — same open-standard SKILL.md format, merged into - # the canonical tree unconditionally (unlike the opt-in Android set): the - # `orca` CLI looks its skills up by exact leaf name, so they have to be - # present wherever an agent reads skills from, not behind a toggle. + # Vendored Orca skills — same open-standard SKILL.md format, but OPT-IN + # (`enableOrca`), not merged by default. + # + # They were unconditional until it turned out that installing them from + # the Nix store is what BREAKS Orca's own updater. Orca scans the agent + # skill directories and, in `observeSkillPackage`, throws + # `skill-package-link` on any file with `nlink !== 1`; the catch turns + # that into status `unrecognized`, which is the "The copy here doesn't + # match the official version" row in Settings → Update skills. Store + # files are hardlinked by store optimisation (ours sat at nlink 5–7), and + # they are mode 444 besides, so `classifyHomeSkillTopology` would mark + # them `read-only` even if the byte check passed. No pin of the vendored + # revision can clear it — the copies were byte-identical to the official + # rev and still flagged. + # + # So the default is now: Orca ships these skills, Orca installs them + # (`orca skills install`, which is `npx skills add` underneath) into a + # real writable directory, and Orca updates them. Turn `enableOrca` on + # for a host with no Orca app, where the vendored copies are the only + # ones. See `orca-skills/CREDITS.md` for the provenance procedure. orcaSkills = if builtins.pathExists (self + "/orca-skills") then skillNamesIn (self + "/orca-skills") @@ -84,15 +100,16 @@ mkMerged pkgs outName [ { inherit source; names = skillList; } ]; # The base tree every non-Grok consumer starts from: the cross-platform - # skills plus the vendored Orca ones. Leaf names don't collide — - # cross-platform skills are all spacecraft-* / gnu-* / microsoft-* / - # steelbore-*, and the three Orca leaves are distinct from those — so a - # flat merge is safe. `orca-skills/CREDITS.md` records that the generic - # Orca leaf names (`computer-use`, `orchestration`) are reserved and must - # not be claimed by a future Spacecraft skill. - baseSources = [ + # skills, plus the vendored Orca ones when the caller asks for them. Leaf + # names don't collide — cross-platform skills are all spacecraft-* / + # gnu-* / microsoft-* / steelbore-*, and the three Orca leaves are + # distinct from those — so a flat merge is safe. `orca-skills/CREDITS.md` + # records that the generic Orca leaf names (`computer-use`, + # `orchestration`) are reserved and must not be claimed by a future + # Spacecraft skill, whether or not this tree carries them. + baseSources = orca: [ { source = self; names = crossPlatformSkills; } - ] ++ nixpkgs.lib.optional (orcaSkills != []) + ] ++ nixpkgs.lib.optional (orca && orcaSkills != []) { source = self + "/orca-skills"; names = orcaSkills; }; # THE skill tree builder. Every consumer goes through this — the @@ -105,16 +122,16 @@ # pins" against "what is installed" must pass one derivation from here to # both sides; comparing `packages.skills` against a separately-built # module tree compares two nixpkgs, and reports drift forever. - mkSkills = { pkgs, android ? false, grok ? false }: + mkSkills = { pkgs, android ? false, grok ? false, orca ? false }: if grok then mkCombined pkgs (self + "/grok-skills") grokSkills "construct-grok-skills" else if android && androidSkills != [] then mkMerged pkgs "construct-skills-with-android" - (baseSources ++ [ + (baseSources orca ++ [ { source = self + "/android-skills"; names = androidSkills; } ]) else - mkMerged pkgs "construct-skills" baseSources; + mkMerged pkgs "construct-skills" (baseSources orca); in { # ─────────────────────────────────────────────────────────────────── @@ -153,6 +170,15 @@ android = true; }; } + // nixpkgs.lib.optionalAttrs (orcaSkills != [ ]) { + # The vendored Orca leaves merged in, for a host with no Orca app to + # install and update them itself. See `orcaSkills` above for why that + # is the exception rather than the default. + skills-with-orca = mkSkills { + inherit pkgs; + orca = true; + }; + } // nixpkgs.lib.optionalAttrs (grokSkills != [ ]) { skills-grok = mkSkills { inherit pkgs; @@ -219,11 +245,30 @@ enableAndroid = lib.mkEnableOption "vendored Google Android skills (merged into ~/.agents/skills/)"; + enableOrca = lib.mkEnableOption '' + the vendored Orca skills (`computer-use`, `orca-cli`, + `orchestration`) in the canonical tree. + + Leave this OFF on any host that runs the Orca app. Orca installs + and updates its own copies, and it refuses to touch a copy it + cannot verify: its scanner throws `skill-package-link` on a file + with `nlink != 1`, which every Nix-store file eventually is once + store optimisation hardlinks it, and reports the skill as + `Unrecognized` in Settings → Update skills. Matching the vendored + bytes to the official revision does not help — a byte-identical + copy is flagged just the same, and the store's 444 modes make the + path `read-only` for the updater regardless. + + Turn it on where nothing else provides these skills (no Orca app + installed, an air-gapped host, a container image) + ''; + package = lib.mkOption { type = lib.types.package; default = mkSkills { inherit pkgs; android = cfg.enableAndroid; + orca = cfg.enableOrca; }; defaultText = lib.literalExpression "this flake's own combined skill tree (Android merged in when enableAndroid)"; @@ -237,8 +282,9 @@ from this flake's — and any pinned-vs-live comparison built on store paths reports drift that is not there. - Setting this supersedes `enableAndroid` for the installed tree; - `enableAndroid` then only selects this option's default. + Setting this supersedes `enableAndroid` and `enableOrca` for + the installed tree; those flags then only select this option's + default. ''; }; @@ -282,6 +328,28 @@ }; }; + perSkillLinks.enable = lib.mkEnableOption '' + rendering `~/.agents/skills` as a REAL directory holding one + symlink per skill, instead of a single directory symlink into the + store (or into the mutable pointer). + + The skills themselves are unchanged — each entry still resolves + into the same tree. What changes is that the directory has room + for entries this module does not own, which is the whole point: + an installer that manages its own skills (Orca's `orca skills + install`, i.e. `npx skills add`) needs somewhere writable to put + them, and a directory symlink into the store gives it nowhere. + + A real directory already sitting on a skill's name is left alone + and reported, never replaced. Pruning is limited to symlinks that + point INTO the tree, so a foreign skill survives every rebuild. + + Caveat under `mutablePointer`: the links point through + `/current`, so swapping the pointer between rebuilds + still changes what every existing link resolves to, but a skill + the swapped-in tree ADDS is not linked until the next activation + ''; + agentPaths = lib.mkOption { type = lib.types.listOf lib.types.str; default = defaultAgentPaths; @@ -296,7 +364,11 @@ config = lib.mkMerge [ # Store-link install (the default). Home Manager owns # ~/.agents/skills outright and every change needs a rebuild. - (lib.mkIf (cfg.enable && !cfg.mutablePointer.enable) { + # Skipped under perSkillLinks, where the activation below renders + # the directory instead — the two cannot both own that path. + (lib.mkIf (cfg.enable + && !cfg.mutablePointer.enable + && !cfg.perSkillLinks.enable) { home.file.".agents/skills".source = cfg.package; }) @@ -343,11 +415,69 @@ $DRY_RUN_CMD rm -rf "${stateDir}/current" fi $DRY_RUN_CMD ln -sfn "${stateDir}/pinned" "${stateDir}/current" + '' + # Under perSkillLinks the entry below renders ~/.agents/skills + # as a real directory, so the pointer stops at and + # this link is not made. + + lib.optionalString (!cfg.perSkillLinks.enable) '' $DRY_RUN_CMD ln -sfn "${stateDir}/current" "$HOME/.agents/skills" ''; }) + # Per-skill links. ~/.agents/skills becomes a real directory whose + # entries point into the tree one skill at a time, leaving the + # names this module does not carry free for another installer to + # own — which is what Orca's updater needs, since it refuses any + # copy it cannot verify and a store copy is hardlinked (nlink != 1) + # and read-only (444). See `enableOrca` for the full mechanism. + (lib.mkIf (cfg.enable && cfg.perSkillLinks.enable) { + home.activation."spacecraft-construct-per-skill-links" = + lib.hm.dag.entryAfter + ([ "linkGeneration" ] + ++ lib.optional cfg.mutablePointer.enable + "spacecraft-construct-skill-pointer") '' + src="${if cfg.mutablePointer.enable + then "${stateDir}/current" + else cfg.package}" + canonical="$HOME/.agents/skills" + + # A directory symlink from a generation before this option + # was on has to go before the directory can be made. + if [ -L "$canonical" ]; then + $DRY_RUN_CMD rm -f "$canonical" + fi + $DRY_RUN_CMD mkdir -p "$canonical" + + for d in "$src"/*/; do + [ -d "$d" ] || continue + n="$(basename "$d")" + t="$canonical/$n" + # A REAL directory on that name belongs to whoever put it + # there. Report it and move on — clobbering another + # installer's skill is how this module would become the + # thing that breaks Orca's updater from the other side. + if [ -e "$t" ] && [ ! -L "$t" ]; then + echo "construct: $t is a real directory owned by another installer — left as is" >&2 + continue + fi + $DRY_RUN_CMD ln -sfn "$src/$n" "$t" + done + + # Prune only what this module made: a link INTO the tree + # whose skill the tree no longer carries. A link pointing + # anywhere else is someone else's and is left alone. + for l in "$canonical"/*; do + [ -L "$l" ] || continue + case "$(readlink "$l")" in + "$src"/*) + [ -d "$src/$(basename "$l")" ] || $DRY_RUN_CMD rm -f "$l" + ;; + esac + done + ''; + }) + (lib.mkIf cfg.enable { # Per-harness directory symlinks. Done via activation so the # symlink can point at the home-relative ~/.agents/skills diff --git a/orca-skills/CREDITS.md b/orca-skills/CREDITS.md index e82b160..f0969fe 100644 --- a/orca-skills/CREDITS.md +++ b/orca-skills/CREDITS.md @@ -45,22 +45,20 @@ later is a matter of copying its directory and extending the table above. ## Which revision to vendor -**Track the installed Orca app, not this repository's HEAD.** The two are not -the same thing, and following HEAD is what breaks Orca's own skill check. +**Track the installed Orca app, not this repository's HEAD.** A skill taken +from HEAD can describe a command the installed binary does not have. Orca ships a manifest inside its AppImage at `resources/skills/current-manifest.json`, listing every bundled skill with a -`releaseRevision` and a per-file `exactSha256`. Orca compares what is installed -on disk against *that* manifest. A copy taken from the repository's HEAD can be -newer than the app, and Orca reports it as `Skipped — the copy here doesn't -match the official version`, which reads like corruption but only means the -tree is ahead of the binary. +`releaseRevision` and a per-file `exactSha256`, plus every historical revision +in `snapshot-registry.json`. That manifest is what tells you which revision the +installed app expects. -That is exactly what happened on 2026-08-19: `orca-cli` at HEAD (3944 bytes) +The app lagging HEAD is what bit on 2026-08-19: `orca-cli` at HEAD (3944 bytes) advertised `share skills` and `skill sharing`, while the installed Orca's `orca skills` subcommand offered only `list`, `get`, `install` and `update`. The skill described a command the binary did not have. Pinning to revision 36 (3913 -bytes) fixed both the warning and the inaccuracy. +bytes) fixed the inaccuracy. The pin is a **hold, not a destination** — it is released the moment the app catches up. On 2026-08-24 the installed Orca had been rebuilt (AppImage dated @@ -71,6 +69,22 @@ now accurate against the binary, so the tree returned to 37. Both halves of the 2026-08-19 reasoning had reversed: the warning and the inaccuracy would come from *staying* at 36. +**A revision pin does not clear Orca's `Skipped — the copy here doesn't match +the official version` warning, and never did.** That claim stood here until +2026-08-25 and is wrong. Orca reads the installed directory with +`observeSkillPackage` and throws `skill-package-link` on any file whose +`nlink != 1`; the throw is caught and reported as status `unrecognized`, which +is exactly that row. Nix-store files are hardlinked by store optimisation +(these three sat at nlink 5–7) and are mode 444 besides, so +`classifyHomeSkillTopology` marks the path `read-only` for the updater too. +`computer-use` and `orchestration` were byte-for-byte the official revisions on +2026-08-25 and were flagged all the same. + +So on a host running Orca this tree is not the install surface: Orca installs +and updates its own copies, and `spacecraft.construct.enableOrca` stays off. +Verifying against the manifest remains how the vendored bytes are kept honest +for hosts that do install from here. + To re-vendor after updating the Orca app, read the manifest for the revision it now expects and verify each file against its `exactSha256`: @@ -86,24 +100,20 @@ exact revision the copy holds, which separates "one release behind" from "someon edited it" — a distinction the Orca dialog does not draw. A mismatch here is the signal to re-vendor. **A match is not a promise of -silence**, and the 2026-08-24 update proved it: `computer-use` and +silence** — see the mechanism above: on 2026-08-24 `computer-use` and `orchestration` were byte-identical to the app's own manifest and Orca still listed all three as `Skipped — the copy here doesn't match the official -version`. That wording is generic, and for those two it was simply untrue. - -The reason is the install location, not the bytes. `orca skills installed` -attributes all three to **Codex home** — `~/.agents/skills`, a symlink into -`/nix/store`, which is a read-only filesystem Orca never wrote to and cannot -write to. Orca skips what it does not own, and reports that with the only -message it has. The dialog's advice — "Remove it if you want Orca to update this -skill" — is not actionable here and must not be followed: nothing can be removed -from a store path, and a Home-Manager switch would restore it regardless. - -So this tree will report `Skipped` **on every Orca update, permanently, by -design**. That is the accepted cost of vendoring (see the `flake.nix` rationale: -Orca resolves skills by exact leaf name, so they must sit wherever agents read -skills from). Treat the dialog as a prompt to run the hash check above, not as -evidence of damage. +version`. For those two the wording was simply untrue; the scanner never got as +far as comparing bytes. + +The dialog's advice — "Remove it if you want Orca to update this skill" — is not +actionable against a store path: nothing can be removed from `/nix/store`, and a +Home-Manager switch would restore it regardless. The fix is upstream of the +dialog: leave `spacecraft.construct.enableOrca` off so these leaves are not +installed at all, and let `orca skills install` own real, writable copies +(`spacecraft.construct.perSkillLinks.enable = true` gives it the room). Where +the vendored copies ARE the install surface — no Orca app on the host — nothing +scans them and there is no dialog to satisfy. ## Leaf-name note diff --git a/orca-skills/README.md b/orca-skills/README.md index ce18b05..d7d1999 100644 --- a/orca-skills/README.md +++ b/orca-skills/README.md @@ -4,10 +4,19 @@ Third-party skills from [Orca](https://github.com/stablyai/orca), vendored verbatim under the MIT License. See [`CREDITS.md`](CREDITS.md) for provenance and [`LICENSE.txt`](LICENSE.txt) for the upstream license text. -These are **not** Spacecraft Software skills. They are merged into the same -canonical install tree as the cross-platform skills because they use the same -open-standard `SKILL.md` format, so every agent on the machine reads them from -one place. +These are **not** Spacecraft Software skills, and they are **not installed by +default**. They use the same open-standard `SKILL.md` format as the +cross-platform skills, so `spacecraft.construct.enableOrca = true` merges them +into the canonical install tree — but only turn that on where the Orca app is +absent. + +On a host running Orca, Orca installs and updates these skills itself +(`orca skills install`, which is `npx skills add` underneath) and its updater +cannot accept a copy served from the Nix store: it throws `skill-package-link` +on any file with `nlink != 1` (store optimisation hardlinks them) and skips +unwritable paths (store files are mode 444), reporting the skill as +`Unrecognized` in Settings → Update skills. Byte-identity with the official +revision does not change that. ## Contents @@ -24,3 +33,8 @@ then update the provenance commit and date in [`CREDITS.md`](CREDITS.md). Do not edit the vendored files in place — any local change makes this tree an *adaptation* rather than a verbatim vendoring, which changes its licensing posture under Standard §4.2. + +Which revision to take, and how to verify it, is in +[`CREDITS.md`](CREDITS.md) § *Which revision to vendor*. Freshness here is a +manual check against the installed app's manifest; Orca's own updater never +audits this tree, and on an Orca host it is not installing from it at all.