diff --git a/.attest.json b/.attest.json new file mode 100644 index 0000000..ba4a063 --- /dev/null +++ b/.attest.json @@ -0,0 +1,4 @@ +{ + "requireAttestation": true, + "requireTestsPassed": true +} diff --git a/.augur.toml b/.augur.toml new file mode 100644 index 0000000..031b459 --- /dev/null +++ b/.augur.toml @@ -0,0 +1,3 @@ +[thresholds] +review = 35 +block = 65 diff --git a/.github/workflows/trust.yml b/.github/workflows/trust.yml new file mode 100644 index 0000000..6cc854e --- /dev/null +++ b/.github/workflows/trust.yml @@ -0,0 +1,20 @@ +name: trust + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + trust: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + + - uses: CorvidLabs/trust@fcc889f54d8b4892a81af463c5a0250e2be66fc5 # v1.2.0 + id: trust diff --git a/.specsync/config.toml b/.specsync/config.toml new file mode 100644 index 0000000..c0f66ac --- /dev/null +++ b/.specsync/config.toml @@ -0,0 +1,8 @@ +specs_dir = "specs" +source_dirs = ["bin"] +exclude_dirs = [] +required_sections = ["Purpose", "Public API", "Invariants", "Behavioral Examples", "Error Cases", "Dependencies", "Change Log"] +enforcement = "strict" + +[lifecycle] +track_history = false diff --git a/.trust.toml b/.trust.toml new file mode 100644 index 0000000..490fbb8 --- /dev/null +++ b/.trust.toml @@ -0,0 +1,22 @@ +schema_version = 1 +profile = "standard" + +[lifecycle] +command = ["fledge", "lanes", "run", "verify"] + +[contract] +enabled = true +require_coverage = 0 +skip_reason = "" + +[risk] +threshold = "block" + +[provenance] +mode = "soft" +policy = ".attest.json" +skip_reason = "" + +[atlas] +enabled = false +skip_reason = "Repo has no Atlas workflow; not adding one in this change" diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3da08a2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,12 @@ + +## CorvidLabs trust toolchain + +This repository uses one trust gate. Every session must use it and must not bypass or weaken it. + +- Run `fledge trust verify` before calling a change complete. +- Keep module specs synchronized with implementation changes. +- Treat an Augur block verdict as a hard stop that must be surfaced and de-risked. +- Record and verify provenance with Attest after the repository's verification lane passes. +- Keep generated trust configuration and this managed block in place. + + diff --git a/specs/corvid-skills/corvid-skills.spec.md b/specs/corvid-skills/corvid-skills.spec.md new file mode 100644 index 0000000..f2ac98b --- /dev/null +++ b/specs/corvid-skills/corvid-skills.spec.md @@ -0,0 +1,133 @@ +--- +module: corvid-skills +version: 1 +status: active +files: + - bin/corvid-skills + - bin/fledge-skills +db_tables: [] +depends_on: [] +--- + +# corvid-skills + +## Purpose + +`bin/corvid-skills` is the installer that copies or links the shared skill directories under +`skills/` into an agent host directory, either repository-local or machine-global, and records +every managed install in a `.corvid-skills.json` manifest next to that target root. It owns the +catalog listing, install, status, update, and uninstall behavior for this repository. + +`bin/fledge-skills` is the Fledge plugin entry point declared by `plugin.toml`. It resolves the +plugin source directory from `FLEDGE_PLUGIN_DIR` (falling back to the parent of its own +directory) and `exec`s `bin/corvid-skills` with the caller's arguments unchanged. + +## Public API + +SpecSync cannot parse extensionless shell executables, so this surface is documented as prose +rather than as an export table. + +- `corvid-skills list [--json]` prints the catalog skill names from `skills/`, sorted with + `LC_ALL=C`. `--json` emits `{"skills": [...]}`. +- `corvid-skills install [--repo |--global] [--host ] [--link] [--dry-run]` + copies (default) or symlinks (`--link`) the skill into the host directory and appends a + manifest entry recording destination, install mode, source revision, and `v2:` content digest. +- `corvid-skills status [--repo |--global] [--json]` reports each manifest entry as + `skill`, `host`, `destination`, `source_revision`, `install_mode`, `state`, where state is + `current`, `legacy`, `modified`, or `missing`. +- `corvid-skills update |--all [--repo |--global] [--host ] [--dry-run]` + refreshes unmodified managed copies and manifest-owned links to the current catalog revision + and digest. +- `corvid-skills uninstall [--repo |--global] [--host ] [--dry-run]` removes + a managed install and its manifest entry. +- `corvid-skills help`, `-h`, `--help`, or no arguments prints usage. +- `fledge-skills ` is the Fledge plugin shim; it `exec`s `bin/corvid-skills` with the + same arguments. + +Host directories are fixed: `codex` to `.codex/skills`, `claude` to `.claude/skills`, `cursor` to +`.cursor/skills`, `gemini` to `.gemini/skills`, `grok` to `.grok/skills`, and `openai` to +`.agents/skills`. + +## Invariants + +- Install never overwrites an existing destination; it fails if the destination path exists. +- Install, update, and uninstall refuse any path component that is a symlink, and refuse a + symlinked `.corvid-skills.json`. +- A skill name must match `^[a-z0-9][a-z0-9-]*$` and must resolve to a real directory under + `skills/` that contains `SKILL.md` and only regular files and directories. +- The manifest is written atomically through a temporary file in the manifest directory followed + by `os.replace`, and is always `schema_version = 1` with an `installs` list. +- A skill and host pair appears at most once in a manifest; a duplicate install is rejected. +- Update and uninstall act only on entries whose state is `current` (uninstall also accepts + `legacy`), or, for `link` mode, on a symlink that still points at this checkout's source + directory. +- `--global` and `--repo` are mutually exclusive; `--global` resolves to `CORVID_SKILLS_HOME` + or `HOME`. +- Copy install and copy update stage into a temporary sibling path and roll back the filesystem + change if the manifest write fails. +- `--dry-run` prints the intended action and makes no filesystem or manifest change. +- `--host auto` resolves only when exactly one host directory or host agent root is present. + +## Behavioral Examples + +- `corvid-skills list --json` on this checkout prints the sorted `skills/*/SKILL.md` directory + names, which `tests/installer.sh` asserts is a 16 entry sorted list. +- `corvid-skills install agent-coordination --repo --host claude` creates + `/.claude/skills/agent-coordination`, writes `/.corvid-skills.json` with + `install_mode: "copy"`, and prints `Installed agent-coordination for claude at ...`. +- `corvid-skills status --repo --json` after that install reports one entry with + `"state": "current"`. +- `corvid-skills update --all --repo --host claude --dry-run` prints one + `Would update ...` line per matching entry and changes nothing. +- `corvid-skills uninstall agent-coordination --repo --host claude` removes the + destination directory and drops the manifest entry. +- Editing a file inside an installed copy moves its status to `modified`, and update then + refuses that entry. + +## Error Cases + +All failures exit with status 2 through `fail()` and write `corvid-skills: ` to stderr, +except rollback paths that exit 1 after restoring the previous filesystem state. + +| Condition | Message | +|-----------|---------| +| Unknown subcommand | `unknown command: ` | +| Unknown flag | `unknown option: ` | +| `--repo` or `--host` without a value | `--repo needs a path` / `--host needs a value` | +| Skill name fails the name pattern | `invalid skill name: ` | +| Skill directory or `SKILL.md` missing | `unknown skill: ` | +| Skill directory holds a device, socket, or symlink entry | `skill contains unsupported filesystem entries: ` | +| Unsupported `--host` value | `unsupported host: ` | +| `--host auto` with zero or several host directories | `cannot infer an agent host; pass --host ...` / `multiple agent hosts are present (...); pass --host explicitly` | +| Target path is not a directory or not a Git repository | `not a directory: ` / `not a Git repository: ` | +| `--global` combined with `--repo` | `--global cannot be combined with --repo` | +| `--global` with no usable home | `HOME is unset; cannot use --global` / `skills home is not a directory: ` | +| Install destination already exists | `destination already exists: ` | +| Any symlinked path component or manifest | `refusing symlinked install path: ...`, `refusing symlinked managed path: ...`, `refusing symlinked managed copy: ...`, `refusing symlinked manifest: ...` | +| Manifest missing for update, uninstall | `managed-skills manifest not found: ` | +| Manifest is not schema 1 or has malformed entries | `unsupported managed-skills manifest: ` / `invalid managed-skills manifest entry` / `invalid control character in managed-skills manifest` / `invalid managed-skills manifest: ` | +| Manifest destination does not match the host layout | `refusing unsafe manifest destination: ` | +| Update or uninstall matches nothing | `no matching managed skills are installed` / `no matching managed skill is installed: ` | +| Managed copy drifted from its recorded digest | `refusing to update on : state is ` | +| Managed link no longer points at this checkout | `refusing to update ...` / `refusing to uninstall on : managed link was changed or removed` | +| Legacy `.openai/skills` placement on update | `OpenAI skill uses legacy .openai placement; uninstall then reinstall it for OpenAI` | +| Staging path collision | `temporary update path already exists for on ` / `temporary removal path already exists for on ` | +| `python3` unavailable for a manifest operation | `python3 is required to maintain .corvid-skills.json safely` | + +## Dependencies + +- `bash` 4 or newer, run with `set -euo pipefail`. +- `python3` for every manifest read, write, and JSON output path. +- `git` for `rev-parse --show-toplevel` (repository resolution) and `rev-parse --verify HEAD` + (recorded source revision; falls back to the literal `working-tree`). +- Coreutils and findutils: `find`, `sort`, `awk`, `cp`, `mv`, `rm`, `ln`, `mkdir`, `dirname`, + `basename`, `readlink`, `uname`. +- `shasum -a 256` when present, otherwise `sha256sum`, for content digests. +- `stat -f '%Lp'` on Darwin, `stat -c '%a'` elsewhere, for entry modes. +- Environment: `FLEDGE_PLUGIN_DIR`, `CORVID_SKILLS_HOME`, `HOME`. + +## Change Log + +| Change | Date | Version | +|--------|------|---------| +| Created | 2026-09-10 | 1 |