diff --git a/README.md b/README.md
index 05ca821b..2a5f9492 100644
--- a/README.md
+++ b/README.md
@@ -110,6 +110,28 @@ fledge ai status # show active provider/model and
That is the whole core. Anything else is a plugin.
+## Architecture
+
+One Rust binary. clap sends each core command to its own module, and any verb it does not
+know is looked up as a plugin (`fledge-NAME`, native or sandboxed WASM). Tasks and lanes come
+from the project's `fledge.toml`, and every `--json` output uses the same envelope.
+
+```mermaid
+flowchart LR
+ user["Developer or agent"] --> cli["fledge"]
+ cli --> core["Core commands run, lanes, work, release, spec, ai, ..."]
+ cli -->|"unknown verb"| plugins["Plugins fledge-NAME, native or WASM"]
+ core --> toml[("fledge.toml tasks, lanes, work, release")]
+ core --> tools["Project toolchain and git"]
+ core --> net["GitHub API, LLM providers"]
+ plugins --> reg[("plugins.toml registry and grants")]
+```
+
+The high-level design in [docs/HLD.md](docs/HLD.md) covers command dispatch, `fledge.toml`,
+task and lane execution, the plugin system and protocol, `work` and `release`, hooks, and the
+trust model, with sequence diagrams for `fledge run`, `fledge lanes run`, and plugin install
+and invocation.
+
## Plugins
Plugins extend fledge with community-built commands. Native plugins run as regular executables. **WASM plugins** run in a sandboxed Wasmtime runtime with no host access by default.
diff --git a/docs/HLD.md b/docs/HLD.md
new file mode 100644
index 00000000..40feec7c
--- /dev/null
+++ b/docs/HLD.md
@@ -0,0 +1,877 @@
+# fledge: high-level design
+
+This document explains how fledge works end to end: how a command is dispatched, how
+`fledge.toml` drives tasks and lanes, how plugins are installed and run, how the ship
+commands (`work`, `release`, and the GitHub plugin) fit together, and where hooks fire.
+It describes the code on `main` at version 1.7.2 (`Cargo.toml`). The code and the specs
+in [`specs/`](../specs) are the source of truth. When they disagree with this page, they win,
+and this page should be fixed.
+
+**Contents**
+
+1. [Purpose](#1-purpose)
+2. [Context](#2-context)
+3. [Components](#3-components)
+4. [Key flows](#4-key-flows)
+5. [Hooks](#5-hooks)
+6. [Data](#6-data)
+7. [Runtime and deployment](#7-runtime-and-deployment)
+8. [Security and trust boundaries](#8-security-and-trust-boundaries)
+9. [Failure modes and limits](#9-failure-modes-and-limits)
+10. [Decisions](#10-decisions)
+11. [Glossary](#11-glossary)
+
+## 1. Purpose
+
+fledge is a single Rust binary that gives every repository the same small set of dev-loop
+commands, whatever language is inside: scaffold (`templates`), run (`run`, `lanes`, `watch`),
+spec (`spec`), AI (`ai`, `ask`, `review`), ship (`work`, `release`, `changelog`) and extend
+(`plugins`, `config`, `introspect`, `completions`, `doctor`). It is used by developers and,
+just as much, by AI agents: every command that matters has a `--json` envelope and a
+non-interactive mode. It works with nothing configured (it detects the project type), grows
+through a checked-in `fledge.toml`, and keeps anything ecosystem-specific out of core in
+plugins. The product intent is written down in [`INTENT.md`](../INTENT.md) and the
+[`hi/`](../hi) criteria. The agent contract is in [`AGENTS.md`](../AGENTS.md).
+
+## 2. Context
+
+fledge runs on the user's machine, in the user's current directory. It reads the project,
+shells out to the project's own toolchain and to `git`, and reaches the network only for
+GitHub (search, import, publish, clone) and for LLM providers (AI commands).
+
+```mermaid
+flowchart LR
+ dev["Developer"] --> cli
+ agent["AI agent --json, FLEDGE_NON_INTERACTIVE=1"] --> cli
+ subgraph host["User machine"]
+ cli["fledge CLI single Rust binary"]
+ proj[("Project directory fledge.toml, .fledge/lanes, specs/")]
+ cfg[("config_dir/fledge config.toml, plugins.toml, plugins/")]
+ sh["Project toolchain via sh -c or cmd /C"]
+ git["git"]
+ native["Native plugins fledge-NAME executables"]
+ wasm["WASM plugins Wasmtime, in-process"]
+ specsync["specsync binary optional"]
+ gh["gh CLI optional"]
+ end
+ cli --> proj
+ cli --> cfg
+ cli --> sh
+ cli --> git
+ cli --> native
+ cli --> wasm
+ cli --> specsync
+ cli -. "token fallback" .-> gh
+ native -. "fledge-plugin-github" .-> gh
+ cli -->|"REST over HTTPS"| ghapi["GitHub API"]
+ gh --> ghapi
+ git -->|"clone, push"| ghrepos["github.com repos plugins, templates, lanes"]
+ cli -->|"HTTP via corvid-ai"| llm["LLM providers Anthropic, OpenAI-compatible, Ollama"]
+```
+
+| External | Used by | How |
+|---|---|---|
+| Project toolchain (`cargo`, `npm`, `go`, ...) | `run`, `lanes`, `watch` | Task strings run through `sh -c` (Unix) or `cmd /C` (Windows) in the project directory ([`src/run.rs`](../src/run.rs), [`src/lanes/execute.rs`](../src/lanes/execute.rs)) |
+| `git` | `work`, `release`, `changelog`, `review`, plugin and template installs | Spawned as a subprocess ([`src/work.rs`](../src/work.rs), [`src/release/git.rs`](../src/release/git.rs), [`src/plugin/install.rs`](../src/plugin/install.rs), [`src/remote.rs`](../src/remote.rs)) |
+| GitHub REST API | `plugins search`, `lanes search/import`, `templates search`, the `publish` commands | `ureq` with a 30 s timeout ([`src/github.rs`](../src/github.rs)) |
+| github.com git remotes | plugin install/update, remote templates, publish | `git clone` / `git push`, with the GitHub token passed as an HTTP header for github.com URLs |
+| LLM providers | `ai`, `ask`, `review`, `work commit --ai`, `spec lint --ai` | Plain HTTP through the [`corvid-ai`](https://crates.io/crates/corvid-ai) crate ([`src/llm.rs`](../src/llm.rs)) |
+| `specsync` | `spec check` | Delegated to when it is on `PATH`, otherwise a built-in structural check ([`src/spec/engine.rs`](../src/spec/engine.rs)) |
+| `gh` | token resolution | `gh auth token` is the last fallback for a GitHub token ([`src/config.rs`](../src/config.rs)). The GitHub commands themselves live in the separate `fledge-plugin-github` repo, whose own help says it drives the `gh` CLI |
+
+## 3. Components
+
+### 3.1 Module map
+
+Core is one crate. [`src/main.rs`](../src/main.rs) declares every module and holds the
+top-level dispatch. [`src/cli.rs`](../src/cli.rs) holds the clap derive types. Folder modules
+(`plugin/`, `lanes/`, `protocol/`, `spec/`, `release/`) split a pillar into files.
+
+```mermaid
+flowchart LR
+ main["main.rs + cli.rs clap parse, dispatch"]
+ subgraph runp["Run"]
+ run["run.rs"]
+ deps["deps.rs task graph walk"]
+ lanes["lanes/"]
+ watch["watch.rs"]
+ end
+ subgraph scaffoldp["Scaffold"]
+ tinit["template_cmds.rs, init.rs"]
+ tpl["templates.rs, remote.rs"]
+ tauth["create_template.rs, validate.rs, publish.rs, search.rs"]
+ end
+ subgraph specp["Spec"]
+ spec["spec/"]
+ end
+ subgraph aip["AI"]
+ ai["ai.rs, ask.rs, review.rs"]
+ llm["llm.rs"]
+ end
+ subgraph shipp["Ship"]
+ work["work.rs"]
+ release["release/"]
+ changelog["changelog.rs"]
+ end
+ subgraph extendp["Extend"]
+ plugin["plugin/"]
+ protocol["protocol/"]
+ config["config.rs, config_cmds.rs"]
+ intro["introspect.rs, doctor.rs"]
+ end
+ subgraph sharedp["Shared"]
+ envelope["envelope.rs"]
+ trust["trust.rs"]
+ github["github.rs"]
+ end
+ main --> runp & scaffoldp & specp & aip & shipp & extendp
+ run --> deps
+ lanes --> deps
+ watch --> run
+ watch --> lanes
+ release --> lanes
+ work --> plugin
+ tinit --> plugin
+ plugin --> protocol
+ plugin --> trust
+ lanes --> trust
+ ai --> llm
+ ai --> spec
+```
+
+| Module | Owns |
+|---|---|
+| [`main.rs`](../src/main.rs), [`cli.rs`](../src/cli.rs) | Parsing, the global `--non-interactive` flag, one match arm per command, the `External` catch-all that routes unknown verbs to plugins |
+| [`run.rs`](../src/run.rs) | `fledge run`: reading `[tasks]`, auto-detecting a project type when there is no `fledge.toml`, building the shell command, `--json`/`--stream` output, `--init` |
+| [`deps.rs`](../src/deps.rs) | `walk_task_graph`: the shared depth-first walk (cycle detection, diamond handling, a 1,000-level depth bound) used by `run` and `lanes` |
+| [`lanes/`](../src/lanes) | `fledge lanes`: config loading and validation ([`mod.rs`](../src/lanes/mod.rs)), execution ([`execute.rs`](../src/lanes/execute.rs)), community search/import ([`community.rs`](../src/lanes/community.rs)), defaults, create, publish, validate |
+| [`watch.rs`](../src/watch.rs) | File watching (`notify`) and re-running a task or lane |
+| [`templates.rs`](../src/templates.rs), [`remote.rs`](../src/remote.rs), [`init.rs`](../src/init.rs), [`template_cmds.rs`](../src/template_cmds.rs) | Built-in templates embedded with `include_dir!`, remote templates cloned into the cache dir, Tera rendering, `post_create` hooks |
+| [`create_template.rs`](../src/create_template.rs), [`validate.rs`](../src/validate.rs), [`publish.rs`](../src/publish.rs), [`search.rs`](../src/search.rs) | Template authoring, validation, publishing and GitHub search |
+| [`spec/`](../src/spec) | `fledge spec`: parse, structural validation, delegation to `specsync`, `spec lint` |
+| [`ai.rs`](../src/ai.rs), [`ask.rs`](../src/ask.rs), [`review.rs`](../src/review.rs), [`llm.rs`](../src/llm.rs) | Provider selection and precedence, spec-aware prompts, single and multi-model review (one thread per panel slot) |
+| [`work.rs`](../src/work.rs) | Branch naming, conventional commits, guarded push, `work status` |
+| [`release/`](../src/release) | Version resolution, file bumps, `CHANGELOG.md` entry, release commit, tag, push |
+| [`changelog.rs`](../src/changelog.rs) | `fledge changelog`: read-only changelog from tags and commits |
+| [`plugin/`](../src/plugin) | Plugin install, update, remove, list, audit, search, recommend, create, publish, validate, run, lifecycle hooks, WASM runtime ([`wasm.rs`](../src/plugin/wasm.rs)) |
+| [`protocol/`](../src/protocol) | The `fledge-v1` plugin protocol: init message, message loop, capability-gated `exec`, `store`, `metadata`, and UI messages |
+| [`trust.rs`](../src/trust.rs) | Trust-tier classification of a source (local, official, team, unverified) |
+| [`config.rs`](../src/config.rs), [`config_cmds.rs`](../src/config_cmds.rs) | Global `config.toml`, GitHub token resolution, `fledge config` |
+| [`envelope.rs`](../src/envelope.rs) | The `{schema_version, ...}` JSON envelope helpers (`resource`, `action`, `versioned`) |
+| [`github.rs`](../src/github.rs) | GitHub REST GET helper, API and remote base URLs, `ensure_git_repo` |
+| [`introspect.rs`](../src/introspect.rs), [`doctor.rs`](../src/doctor.rs) | Command tree as JSON (core commands only), environment diagnostics |
+| [`utils.rs`](../src/utils.rs), [`prompts.rs`](../src/prompts.rs), [`spinner.rs`](../src/spinner.rs), [`versioning.rs`](../src/versioning.rs), [`meta.rs`](../src/meta.rs) | Non-interactive state, prompts, spinner, version parsing, project metadata |
+
+### 3.2 Command dispatch
+
+`main()` resets `SIGPIPE` to the default on Unix (so `fledge ... | head` exits quietly), then
+`run()` reads `FLEDGE_NON_INTERACTIVE`, parses with clap, applies `--non-interactive` (`--ni`),
+and matches on the `Commands` enum. Each arm converts the clap subcommand into the handler's
+own action enum (`spec_action_from`, `lane_action_from`, `plugin_action_from`, ...) and calls it.
+Any verb clap does not know lands in `Commands::External`, which is how plugin commands run.
+
+```mermaid
+flowchart TD
+ argv["fledge ARGS"] --> parse["read FLEDGE_NON_INTERACTIVE, clap parses Cli, apply --ni"]
+ parse -->|"bad flag or missing positional"| usage["clap usage error, exit 2"]
+ parse --> match{"Commands variant"}
+ match -->|"no subcommand"| help["print help, exit 0"]
+ match -->|"core command"| handler["module handler run, lanes, work, release, plugins, ..."]
+ match -->|"External(args)"| resolve{"plugins/bin/fledge-NAME or fledge-NAME on PATH?"}
+ resolve -->|"found"| prun["plugin::run with a Run action"]
+ resolve -->|"not found"| unk["unrecognized subcommand"]
+ handler --> result{"Result"}
+ prun --> result
+ unk --> fail
+ result -->|"Ok"| ok["exit 0"]
+ result -->|"Err"| fail["error on stderr, exit 1"]
+```
+
+Two extra routing rules:
+
+- `fledge lanes NAME` (no `run`) is accepted: `LaneSubcommand::External` is parsed by
+ `parse_external_lane_args` into the same `Run` action, honoring `--dry-run`, `--json` and `--from`.
+- Aliases: `lane` for `lanes`, `plugin` for `plugins`, `template` for `templates`.
+
+Because plugins are dispatched as external subcommands, `fledge introspect --json` shows the
+core surface only. `fledge plugins list --json` is the source for plugin verbs.
+
+### 3.3 Output contract
+
+Every `--json` output is built with [`envelope.rs`](../src/envelope.rs): either
+`{schema_version, : [...]}` (list and query commands) or
+`{schema_version, action: "", ...}` (everything else). `schema_version` is tracked per
+command by constants such as `RUN_TASK_SCHEMA` and `LANES_RUN_SCHEMA`. Errors always go to
+stderr as plain text, and the exit code is the contract (0 ok, 1 runtime error, 2 usage
+error). Progress from hooks and plugin UI goes to stderr so a `--json` stdout stays parseable.
+[`AGENTS.md`](../AGENTS.md) documents each envelope.
+
+## 4. Key flows
+
+### 4.1 `fledge run `
+
+`fledge.toml` is read from the **current directory only** (no upward search). Without it,
+fledge detects the project type from marker files and synthesizes tasks.
+
+```mermaid
+sequenceDiagram
+ autonumber
+ actor User
+ participant Main as main.rs
+ participant Run as run.rs
+ participant Deps as deps.rs
+ participant Shell as sh -c or cmd /C
+ User->>Main: fledge run test -- --release
+ Main->>Run: run(RunOptions)
+ alt fledge.toml in cwd
+ Run->>Run: parse [tasks], bail if empty
+ else no fledge.toml
+ Run->>Run: detect_project_type, auto_detect_tasks
+ end
+ Run->>Run: unknown task name? bail with the available list
+ Run->>Deps: walk_task_graph("test")
+ loop each task, dependencies first, each node once
+ Deps->>Deps: cycle check on the recursion stack, depth at most 1000
+ Deps->>Run: on_ready(name)
+ Run->>Shell: spawn cmd in dir with env, pass-through args only for "test"
+ alt --json
+ Shell-->>Run: captured stdout, stderr, exit code
+ Run-->>User: one run_task envelope for this task
+ else human mode
+ Shell-->>User: inherited stdio, live output
+ end
+ opt non-zero exit
+ Run-->>Main: Err, the walk stops
+ end
+ end
+ Main-->>User: exit 0, or error on stderr and exit 1
+```
+
+Details, all from [`src/run.rs`](../src/run.rs) and [`src/deps.rs`](../src/deps.rs):
+
+- **Task forms.** `name = "cmd"` (short) or a table with `cmd`, `deps`, `env`, `dir` and
+ `description`. `dir` is joined to the project directory.
+- **Order.** `walk_task_graph` is a two-set DFS. `in_progress` (the recursion stack) detects a
+ real cycle and reports it as an ordered walk (`a → b → a`). `completed` makes a shared
+ dependency in a diamond run once. The walk is bounded at `MAX_TASK_DEPTH = 1000` so a
+ pathological chain errors instead of overflowing the stack. A dependency that is not defined
+ fails when the walk reaches it.
+- **Pass-through args** (after `--`) apply to the named task only. On POSIX they are passed as
+ real positional parameters (`sh -c ' "$@"' fledge `), so they are never spliced
+ into the command string. If the command already references `$1`, `$@` or `$*`, `"$@"` is not
+ appended again. On Windows they are appended as separate argv entries.
+- **`--json`** captures each task with `Command::output()` and prints one `run_task` envelope per
+ executed task, so a task with `deps` yields a stream of JSON objects, not one document.
+- **`--stream`** (with `--json`) mirrors the child's output live to **stderr** while still
+ capturing it for the envelope, and lets the child keep stdin. `SIGPIPE` is ignored only while
+ mirroring. A broken mirror produces a warning, not a failed run.
+- **Auto-detection** (first match wins): `Cargo.toml` rust, `package.json` node (runner from the
+ lockfile: bun, yarn, pnpm, else npm), `go.mod`, `pyproject.toml`/`setup.py`, `Gemfile`,
+ `build.gradle(.kts)`, `pom.xml`, `Package.swift`. `fledge run --init` writes a starter
+ `fledge.toml` from `task_defaults`.
+
+### 4.2 `fledge lanes run `
+
+A lane is an ordered list of steps in `fledge.toml`. Lanes add ordering, parallel groups,
+conditions, timeouts, retries and failure policy on top of tasks.
+
+```mermaid
+sequenceDiagram
+ autonumber
+ actor User
+ participant Lanes as lanes/mod.rs
+ participant Exec as lanes/execute.rs
+ participant Deps as deps.rs
+ participant Shell as sh -c or cmd /C
+ User->>Lanes: fledge lanes run ci
+ Lanes->>Lanes: load fledge.toml, merge .fledge/lanes/*.toml without overwriting
+ Lanes->>Lanes: validate_lane, every task ref exists and has no dep cycle
+ Lanes->>Lanes: resolve --from, a 1-based index or a step name
+ alt --dry-run
+ Lanes-->>User: plan, or the dry-run envelope
+ else run
+ Lanes->>Exec: execute_lane(lane, tasks, cwd)
+ loop each step in order
+ alt before --from, or its when condition is false
+ Exec->>Exec: record as skipped
+ else run the step, up to retries + 1 attempts
+ alt task step
+ Exec->>Deps: walk deps, run each task once
+ Deps->>Shell: task cmd with env and dir
+ else inline run step
+ Exec->>Shell: run string in the project dir
+ else parallel step
+ Exec->>Shell: one scoped thread per item
+ end
+ Shell-->>Exec: exit status, or a kill at the step deadline
+ end
+ opt step still failing after the last attempt
+ alt fail_fast (default true)
+ Exec-->>User: stop, lane failed at step N
+ else fail_fast = false
+ Exec->>Exec: record the failure, continue
+ end
+ end
+ end
+ Exec-->>User: summary or lane envelope, exit 1 if any step failed
+ end
+```
+
+Step semantics ([`src/lanes/mod.rs`](../src/lanes/mod.rs), [`src/lanes/execute.rs`](../src/lanes/execute.rs)):
+
+- **Step kinds.** `"task"` (a bare string), `{ task = "x", ... }`, `{ run = "cmd", ... }`, and
+ `{ parallel = ["a", { run = "cmd" }], ... }`. The full forms accept `when`, `timeout`
+ (seconds), `retries` and `retry_delay` (seconds, default 1).
+- **`when`** is a comma-separated list, all of which must hold: `VAR` (set and non-empty),
+ `VAR=value`, `!VAR` (unset or empty) and `!VAR=value`. It reads the process environment.
+- **Timeouts.** With a `timeout`, the child is spawned in its own process group (Unix) or a Job
+ Object (Windows), polled every 50 ms, and the whole tree is killed at the deadline.
+- **Parallel groups** use `std::thread::scope`, one thread per item. All items run to completion
+ and their errors are collected into one step error.
+- **Output.** In human mode task output is inherited. Under `--json` child stdout and stderr are
+ discarded and only the lane envelope is printed. `release --pre-lane --json` uses a silent
+ variant (`execute_lane_silent`) that prints nothing at all and always starts from step 1.
+- **Imports.** `fledge lanes import owner/repo[/path][@ref]` fetches the remote `fledge.toml`
+ through the GitHub contents API and writes the lanes and missing tasks to
+ `.fledge/lanes/-.toml`. Existing names are never overwritten. A non-official
+ source needs a confirmation, or `--yes` in non-interactive mode.
+- **`fledge watch`** re-invokes `lanes::run` or `run::run` in human mode on each debounced change.
+
+### 4.3 Plugin install
+
+`fledge plugins install ` accepts `owner/repo[@ref]`, any git URL, or a local path.
+Install is transactional: every failure after the plugin directory is created removes it
+again, and the registry is only written at the very end.
+
+```mermaid
+sequenceDiagram
+ autonumber
+ actor User
+ participant Inst as plugin/install.rs
+ participant Trust as trust.rs
+ participant Git as git
+ participant FS as config_dir/fledge
+ User->>Inst: fledge plugins install owner/repo@v1.0.0
+ Inst->>Inst: parse source, local path or git, reject . and .. segments
+ Inst->>Trust: determine_trust_tier(source)
+ Trust-->>Inst: local, official, team or unverified
+ opt no --force or --yes, and interactive
+ Inst->>User: install this plugin?
+ end
+ alt local path
+ Inst->>FS: symlink plugins/NAME to the source dir, or copy with --copy
+ else git source
+ Inst->>Git: clone into plugins/NAME, depth 1 unless a ref is pinned
+ opt ref pinned
+ Inst->>Git: checkout ref
+ end
+ end
+ Inst->>FS: read plugins/NAME/plugin.toml
+ Inst->>Inst: unverified tier asking for exec or network? roll back and refuse
+ opt capabilities or hooks declared
+ Inst->>User: list them and ask to grant, auto-granted by --force
+ end
+ Inst->>Inst: run the build hook, or auto-build (cargo, swift, go, npm)
+ opt runtime = "wasm"
+ Inst->>Inst: pre-compile each module to a .cwasm cache
+ end
+ Inst->>FS: symlink plugins/bin/fledge-CMD for every declared command
+ opt post_install hook
+ Inst->>Inst: run it, a failure removes the links and the dir
+ end
+ Inst->>FS: write plugins.toml, temp file then rename
+ Inst-->>User: installed commands, or the install envelope
+```
+
+Notes from [`src/plugin/install.rs`](../src/plugin/install.rs) and
+[`src/plugin/mod.rs`](../src/plugin/mod.rs):
+
+- `--defaults` installs `DEFAULT_PLUGINS` (currently `fledge-plugin-github@v0.4.0`,
+ `fledge-plugin-deps@v0.2.0`, `fledge-plugin-metrics@v0.2.1`), continues past individual
+ failures, and reports a per-plugin summary.
+- Non-interactive mode implies `--force`, which both skips the confirmation and auto-grants the
+ requested capabilities (with a warning on stderr).
+- `link_commands` rejects command names containing `/`, `\`, NUL, or a leading `.` or `-`, and
+ binaries whose path contains `..` or canonicalizes outside the plugin directory.
+- The registry records the granted capabilities for protocol plugins and for any plugin with
+ hooks. Lifecycle hooks only fire when `exec` is recorded there (see [Hooks](#5-hooks)).
+- `fledge plugins update` does `git pull --ff-only` for an unpinned plugin, then rebuilds and
+ relinks. A pinned plugin stays on its tag and the newer tag is reported, except under
+ `update --defaults`, which checks out the newest tag and records it as the new pin. A new
+ manifest that escalates capabilities is re-prompted and re-checked against the tier gate.
+ `post_install` is not re-run on update.
+
+### 4.4 Plugin invocation
+
+When clap does not recognize a verb, `main.rs` asks `resolve_plugin_command(name)` for
+`plugins/bin/fledge-NAME` (plus `.exe`, `.bat`, `.cmd` on Windows), then for `fledge-NAME` on
+`PATH`, git-style. The manifest's `protocol` and `runtime` then pick one of three execution modes.
+
+```mermaid
+sequenceDiagram
+ autonumber
+ actor User
+ participant Main as main.rs
+ participant RunP as plugin/ (mod.rs, run_plugin.rs)
+ participant Proto as protocol/mod.rs
+ participant Wasm as plugin/wasm.rs
+ participant Proc as plugin process
+ User->>Main: fledge deps --audit
+ Main->>RunP: resolve_plugin_command("deps")
+ RunP-->>Main: path to fledge-deps
+ Main->>RunP: run_plugin_cmd("deps", args)
+ RunP->>RunP: registry entry and plugin.toml give protocol, runtime, capabilities
+ alt no protocol declared
+ RunP->>Proc: exec binary with args and FLEDGE_PLUGIN_DIR, inherited stdio
+ Proc-->>User: output directly
+ else protocol fledge-v1, native
+ RunP->>Proto: run_protocol_plugin
+ Proto->>Proc: spawn with piped stdin and stdout, stderr inherited
+ Proto->>Proc: init message with args, project, plugin, fledge version, capabilities
+ loop one JSON message per stdout line until EOF
+ Proc->>Proto: prompt, confirm, select, progress, log, output, store, load, exec, metadata
+ Proto->>Proto: check the granted capability for store, load, exec, metadata
+ Proto-->>Proc: response line on stdin, or a denial
+ end
+ else protocol fledge-v1, runtime wasm
+ RunP->>Wasm: run_wasm_plugin
+ Wasm->>Wasm: load cached module, WASI preopens from the filesystem capability
+ Wasm->>Wasm: run with fuel limit, 60 s epoch deadline, 256 MB memory cap
+ end
+ RunP-->>Main: Ok, or Err with the plugin exit code
+ Main-->>User: exit status
+```
+
+The `fledge-v1` protocol ([`src/protocol/`](../src/protocol),
+[`specs/plugin/plugin-protocol.spec.md`](../specs/plugin/plugin-protocol.spec.md)):
+
+- **Transport.** Newline-delimited JSON. fledge writes an `init` message, then answers the
+ plugin's requests with `{type: "response", id, value}`. Malformed lines are skipped with a
+ warning. The run ends when the plugin closes stdout and exits.
+- **UI messages** (`prompt`, `confirm`, `select`, `multi_select`, `progress`, `log`, `output`)
+ are rendered by fledge, so plugins get the same prompts and progress bars as core.
+- **Capability-gated messages.** `exec` runs `sh -c` with a `cwd` confined to the project or
+ plugin directory, a 30 s default and 300 s maximum timeout, and 10 MB per output stream.
+ `store`/`load` persist to `plugins/NAME/state.json` under a file lock (64 KB per value, 1 MB
+ total, 256 keys). `metadata` answers keys such as `fledge_config`, `git_tags` and `git_status`.
+ Denied requests get a well-formed denial (for `exec`, exit code 126) instead of hanging.
+- **The init message** carries the project name, root, language and git context. Credentials
+ embedded in an `https://user:token@...` remote URL are stripped first
+ ([`src/protocol/detect.rs`](../src/protocol/detect.rs)).
+- **WASM plugins** ([`src/plugin/wasm.rs`](../src/plugin/wasm.rs)) get the same init message and
+ message handling through host functions in the `fledge` import module (`send`, `recv`, `exit`,
+ and `exec`, `store_set`, `store_get`, `metadata` linked only when granted). `filesystem =
+ "project"` preopens the project read-only at `/project`. `"plugin"` adds a read-write
+ `/plugin` data dir. `network = true` inherits the host network. Interactive UI messages are
+ not supported in WASM. The runtime is a default cargo feature (`wasm`).
+
+### 4.5 Ship: `work`, GitHub, and `release`
+
+`fledge work` wraps the branch, commit and push loop with guardrails. PR, issue and CI
+commands are **not in core**: since the v0.15 tight-core refactor they live in
+`fledge-plugin-github`, reached as `fledge github ...` through plugin dispatch (4.4).
+`fledge work pr` now prints a pointer to `fledge github prs create` and exits 1.
+
+```mermaid
+sequenceDiagram
+ autonumber
+ actor User
+ participant Work as work.rs
+ participant Hooks as plugin lifecycle hooks
+ participant Git as git
+ participant GH as fledge github plugin
+ User->>Work: fledge work start add-search --issue 42
+ Work->>Git: status --porcelain, must be clean
+ Work->>Work: branch from branch_format, e.g. author/feat/42-add-search
+ Work->>Git: checkout -b BRANCH BASE
+ Work->>Hooks: post_work_start, errors ignored
+ User->>Work: fledge work commit -m "add search index"
+ Work->>Git: add -A only with --all, then commit with a conventional prefix
+ User->>Work: fledge work push
+ Work->>Work: refuse the default branch, refuse 0 commits ahead
+ Work->>Hooks: pre_push, an error aborts the push
+ Work->>Git: push -u origin BRANCH, --force-with-lease only with --force
+ User->>GH: fledge github prs create --fill
+ GH-->>User: PR number and URL
+```
+
+- **Branch names** come from `[work].branch_format` (default `{author}/{type}/{name}`), with
+ `{issue}` available and `--issue N` prefixing the name as `N-name`. `{author}` comes from
+ `defaults.author` in the global config or git `user.name`. Types are checked against
+ `[work].branch_types` or the built-in list, unless `--prefix` is used.
+- **Commits.** The type is `--type`, else inferred from the branch prefix, else
+ `[work].default_type`. A message that already has a conventional prefix (including `Add:`,
+ `Update:`, `Remove:`) is kept as is. `--ai` drafts the message from the staged diff.
+- **`work status --json`** (schema v2) reports branch, default branch, ahead, behind and dirty count.
+
+`fledge release ` cuts a release locally. It does not publish binaries: that happens in CI
+when the tag lands (section 7).
+
+```mermaid
+sequenceDiagram
+ autonumber
+ actor User
+ participant Rel as release/mod.rs
+ participant Lanes as lanes
+ participant Files as version files
+ participant Git as git
+ User->>Rel: fledge release minor --pre-lane ci --push
+ Rel->>Git: preflight, .git exists and status is clean unless --allow-dirty
+ opt --pre-lane
+ Rel->>Lanes: run the lane, silently under --json
+ end
+ Rel->>Rel: target version, explicit or a bump of plugin.toml, the manifest, or the latest tag
+ alt --dry-run
+ Rel-->>User: plan or dry-run envelope, nothing written
+ else release
+ Rel->>Files: bump Cargo.toml, package.json, pyproject.toml and [release].files
+ Rel->>Git: log PREV_TAG..HEAD, prepend a CHANGELOG.md entry
+ Rel->>Git: add bumped files and CHANGELOG.md, commit "chore: release vX.Y.Z"
+ opt unless --no-tag
+ Rel->>Git: tag -a vX.Y.Z
+ end
+ opt --push
+ Rel->>Git: push, then push origin vX.Y.Z
+ end
+ Rel-->>User: summary or release envelope
+ end
+```
+
+`--no-bump` makes a tag-only release and `--no-changelog` skips the changelog. Files listed in
+`[release].files` must stay inside the project directory. In each one, the first match of
+`version = "X.Y.Z"` (or `version: X.Y.Z`) is rewritten. This repo lists `flake.nix` there.
+Gitignored files are filtered out before `git add`.
+
+## 5. Hooks
+
+fledge has two hook families. **Plugin hooks** are declared in a plugin's `plugin.toml`
+`[hooks]` table. **Template hooks** (`post_create`) are declared in a template's
+`template.toml`. Plugin lifecycle hooks are global: they fire for every installed plugin that
+declares the event, whichever project you are in.
+
+```mermaid
+flowchart LR
+ subgraph ph["plugin.toml [hooks]"]
+ build["build"]
+ post_install["post_install"]
+ post_remove["post_remove"]
+ pre_init["pre_init"]
+ post_work_start["post_work_start"]
+ pre_push["pre_push"]
+ end
+ subgraph th["template.toml [hooks]"]
+ post_create["post_create"]
+ end
+ install["plugins install"] --> build
+ install --> post_install
+ update["plugins update"] --> build
+ remove["plugins remove"] --> post_remove
+ tinit["templates init"] --> pre_init
+ tinit --> post_create
+ wstart["work start"] --> post_work_start
+ wpush["work push"] --> pre_push
+```
+
+| Hook | Fires | On failure | Gate |
+|---|---|---|---|
+| `build` | install and update, in place of the auto-detected build | install rolls back, update fails | shown at install for approval |
+| `post_install` | after commands are linked, before the registry is written | links and plugin dir are removed | shown at install for approval |
+| `post_remove` | during `plugins remove`, before the plugin dir is deleted | removal stops (see section 9) | none beyond the original install |
+| `pre_init` | start of `templates init` | ignored | registry entry must have `exec` granted |
+| `post_work_start` | after `work start` creates the branch | ignored | registry entry must have `exec` granted |
+| `pre_push` | before `work push` runs `git push` | the push is aborted | registry entry must have `exec` granted |
+| `post_create` (template) | after a template is rendered, each command through `sh -c` in the new project | `templates init` fails and shows the hook's output | local templates: `--yes` or non-interactive auto-confirms. Remote templates: only `--trust-hooks` or `FLEDGE_TRUST_HOOKS=1`, otherwise skipped when non-interactive |
+
+How a plugin hook runs ([`src/plugin/run_plugin.rs`](../src/plugin/run_plugin.rs), `run_hook`):
+if the hook value names a file inside the plugin directory it is executed (after checking it
+does not escape the directory), otherwise it is split with `shell_words` and executed directly,
+not through a shell. The working directory is the plugin directory, and the environment adds
+`FLEDGE_PLUGIN_DIR` and `FLEDGE_REPO_ROOT` (the git toplevel of the invoking repo, falling back
+to the current directory). Hook progress is written to stderr.
+
+## 6. Data
+
+fledge has no database. Its state is a handful of TOML and JSON files.
+
+### 6.1 `fledge.toml` (per project)
+
+One file configures tasks, lanes, `work` and `release`. Each module deserializes only the tables
+it needs (`run.rs`, `lanes/mod.rs`, `work.rs`, `release/bump.rs`), so unknown tables are ignored.
+Imported lane files in `.fledge/lanes/*.toml` have the same shape.
+
+```mermaid
+erDiagram
+ FLEDGE_TOML ||--o{ TASK : "tasks table"
+ FLEDGE_TOML ||--o{ LANE : "lanes table"
+ FLEDGE_TOML ||--o| WORK : "work table"
+ FLEDGE_TOML ||--o| RELEASE : "release table"
+ LANE ||--|{ STEP : "steps"
+ STEP }o--o{ TASK : "references"
+ TASK }o--o{ TASK : "deps"
+ IMPORTED_LANES ||--o{ LANE : "adds, never overwrites"
+ IMPORTED_LANES ||--o{ TASK : "adds, never overwrites"
+ TASK {
+ string cmd "required, or the whole value in short form"
+ string_list deps
+ string_map env
+ string dir "relative to the project dir"
+ string description
+ }
+ LANE {
+ string description
+ bool fail_fast "default true"
+ }
+ STEP {
+ string kind "task, run or parallel"
+ string when "env condition"
+ int timeout "seconds"
+ int retries
+ int retry_delay "seconds, default 1"
+ }
+ WORK {
+ string branch_format
+ string default_type "default feat"
+ string_list branch_types
+ }
+ RELEASE {
+ string_list files "extra version files"
+ }
+```
+
+The full reference, with examples, is on the docs site under *fledge.toml* and in
+[`specs/run/run.spec.md`](../specs/run/run.spec.md) and
+[`specs/lanes/lanes.spec.md`](../specs/lanes/lanes.spec.md).
+
+### 6.2 Plugin manifest and registry
+
+```mermaid
+classDiagram
+ direction LR
+ class PluginManifest {
+ PluginMeta plugin
+ List~PluginCommand~ commands
+ PluginHooks hooks
+ PluginCapabilities capabilities
+ }
+ class PluginMeta {
+ String name
+ String version
+ String description
+ String author
+ String protocol
+ String runtime
+ }
+ class PluginCommand {
+ String name
+ String binary
+ String description
+ }
+ class PluginHooks {
+ String build
+ String post_install
+ String post_remove
+ String pre_init
+ String post_work_start
+ String pre_push
+ }
+ class PluginCapabilities {
+ bool exec
+ bool store
+ bool metadata
+ String filesystem
+ bool network
+ }
+ class PluginsRegistry {
+ List~PluginEntry~ plugins
+ }
+ class PluginEntry {
+ String name
+ String source
+ String version
+ String installed
+ List~String~ commands
+ String pinned_ref
+ PluginCapabilities capabilities
+ String runtime
+ }
+ PluginManifest --> PluginMeta
+ PluginManifest --> PluginCommand
+ PluginManifest --> PluginHooks
+ PluginManifest --> PluginCapabilities
+ PluginsRegistry --> PluginEntry
+ PluginEntry ..> PluginCapabilities : granted copy
+```
+
+`plugin.toml` lives in each plugin's directory and is re-read on every invocation.
+`plugins.toml` is the registry of what is installed and what was granted. `protocol` is
+`"fledge-v1"` or absent. `runtime` is `"wasm"` or absent (`"native"` is accepted by validation).
+`filesystem` is `"none"`, `"project"` or `"plugin"`.
+
+### 6.3 File locations
+
+`config_dir` is the platform config directory from the `dirs` crate: `~/Library/Application
+Support` on macOS, `~/.config` on Linux, `%APPDATA%` on Windows.
+
+| Path | Contents | Written by |
+|---|---|---|
+| `./fledge.toml` | tasks, lanes, work, release | the user, `run --init`, `lanes init` |
+| `./.fledge/lanes/-.toml` | imported lanes and tasks, headed `# Imported from ...` | `lanes import` |
+| `config_dir/fledge/config.toml` | global config (author, GitHub token and org, AI providers, trust orgs and users, template repos). `FLEDGE_CONFIG_DIR` overrides the directory for this file only | `fledge config`, `ai use` |
+| `config_dir/fledge/plugins.toml` | plugin registry | install, update, remove (atomic rename) |
+| `config_dir/fledge/plugins/NAME/` | plugin checkout or symlink, `plugin.toml`, protocol `state.json`, WASM `data/`, `.cwasm` caches | install, update, the plugin itself |
+| `config_dir/fledge/plugins/bin/fledge-CMD` | symlinks to plugin command binaries (a copy on Windows if symlinks are not allowed) | install, update |
+| `cache_dir/fledge/templates//` | cached remote template clones | `templates init owner/repo` |
+
+## 7. Runtime and deployment
+
+fledge is a local CLI. Nothing runs as a service. Distribution is binaries and crates.
+
+```mermaid
+flowchart LR
+ rel["fledge release X --push pushes tag vX.Y.Z"] --> test
+ subgraph wf["release.yml, on v* tags"]
+ test["test gate fmt, clippy, test"] --> build["build matrix Linux musl x86_64 and aarch64, macOS x86_64 and aarch64, Windows x86_64"]
+ build --> ghrel["GitHub Release binaries and .sha256 sidecars"]
+ ghrel --> pub["cargo publish, then read crates.io back"]
+ end
+ ghrel --> brew["post-release-formula.yml updates CorvidLabs/homebrew-tap"]
+ ghrel --> dl["install.sh and the CorvidLabs/fledge action"]
+ pub --> crates["cargo install fledge"]
+```
+
+- **CI** ([`.github/workflows/ci.yml`](../.github/workflows/ci.yml)) runs on every PR and push to
+ `main` with no path filter, because its jobs are required checks: `cargo test` on Linux, macOS
+ and Windows, fmt and clippy, `cargo audit`, an integration job that builds fledge and runs
+ `fledge lanes run check` and the default plugins, the SpecSync action plus
+ `specsync change check`, and `hi check`.
+- **Trust gate** ([`.github/workflows/trust.yml`](../.github/workflows/trust.yml)) runs the
+ CorvidLabs Trust action on PRs and `main`. Locally the same gate is `fledge trust verify`,
+ whose lifecycle command is `fledge lanes run verify-native` ([`.trust.toml`](../.trust.toml)).
+- **Release** ([`.github/workflows/release.yml`](../.github/workflows/release.yml)) is described
+ above. The publish job needs the `CARGO_REGISTRY_TOKEN` repository secret and fails loudly
+ without it. Release steps for maintainers are in [`CONTRIBUTING.md`](../CONTRIBUTING.md).
+- **GitHub Action** ([`action.yml`](../action.yml)) installs a released binary in CI and verifies
+ it against the `.sha256` sidecar.
+- **GitHub Pages** ([`.github/workflows/pages.yml`](../.github/workflows/pages.yml)) builds the
+ Astro project in [`site/`](../site). That site is now **redirect-only**: every route redirects
+ to the CorvidLabs hub, where the user docs live
+ ([`site/src/data/hub.ts`](../site/src/data/hub.ts),
+ [`site/src/components/Redirect.astro`](../site/src/components/Redirect.astro)). The Pages
+ build still publishes the atlas spec-coverage badges used by the README. This HLD is not
+ published there; read it here on GitHub, which renders the Mermaid diagrams.
+- **Build features.** `wasm` (Wasmtime and WASI) is on by default. `--no-default-features` builds
+ without it, and WASM plugins then fail with a clear error.
+
+## 8. Security and trust boundaries
+
+The user's own project and config are trusted. Everything fetched from elsewhere is classified
+by trust tier before it can run, and the prompts and gates scale with the tier.
+[`SECURITY.md`](../SECURITY.md) is the full policy.
+
+```mermaid
+flowchart TD
+ src["source string"] --> isLocal{"local path? ./ ../ / . .. or local:"}
+ isLocal -->|"yes"| local["local"]
+ isLocal -->|"no"| norm["strip github.com URL prefix and .git"]
+ norm --> trav{"a . or .. path segment?"}
+ trav -->|"yes"| unv["unverified"]
+ trav -->|"no"| owner{"owner"}
+ owner -->|"CorvidLabs"| off["official"]
+ owner -->|"built-in team list, or trust.orgs / trust.users"| team["team"]
+ owner -->|"anyone else"| unv
+ unv --> refuse["plugin asking for exec or network: install refused"]
+ unv --> warn["lanes, hooks: warning and confirmation"]
+ off --> allowed["install proceeds, capabilities still shown"]
+ team --> allowed
+ local --> allowed
+```
+
+- **Trust tiers** ([`src/trust.rs`](../src/trust.rs)) apply to plugins, imported lanes and
+ search results. `fledge config add trust.orgs ` promotes an owner to `team`. Install
+ rejects `.` and `..` segments outright so a source like `CorvidLabs/../x/y` cannot borrow the
+ official tier (git would collapse the path while classification keyed on the first segment).
+- **Native plugins are not sandboxed.** A native plugin is arbitrary code running as the user.
+ Capabilities gate only the `fledge-v1` protocol requests (`exec`, `store`, `metadata`), not
+ what the process itself can do. Granting `exec` means unrestricted shell access.
+- **WASM plugins are sandboxed** by Wasmtime: no filesystem, network or exec unless granted,
+ fuel and a 60 s wall clock bound compute, and linear memory is capped at 256 MB. Compiled
+ `.cwasm` caches are validated against the SHA-256 of the `.wasm` and the Wasmtime version.
+- **Hooks and tasks run shell code.** Lane imports, remote template `post_create` hooks and plugin
+ hooks all need explicit consent. Non-interactive mode never silently authorizes remote template
+ hooks (that needs `--trust-hooks`). It does auto-confirm plugin installs and auto-grant their
+ capabilities, so agents should install only sources they would install by hand. The
+ unverified-tier refusal of `exec` and `network` still applies with `--force`.
+- **Secrets.** The GitHub token is resolved from `FLEDGE_GITHUB_TOKEN`, then `GITHUB_TOKEN`, then
+ `github.token` in `config.toml`, then `gh auth token`. It is sent as an `Authorization` header
+ (never embedded in a URL) for GitHub API calls and github.com clones. AI keys come from
+ `_API_KEY` or `ai..api_key`. `fledge config` knows which keys are secret
+ (`Config::is_secret_key`).
+- **Supply chain.** Release binaries ship with `.sha256` sidecars. The GitHub Action verifies
+ them. `install.sh` downloads the release binary over HTTPS but does not check the sidecar.
+ `cargo audit` runs in CI.
+
+## 9. Failure modes and limits
+
+| Situation | Behavior |
+|---|---|
+| No `fledge.toml` and an unrecognized project | `run` fails with a hint to run `fledge run --init`. `lanes` always needs a `fledge.toml` |
+| `fledge.toml` has `[tasks]` but it is empty | `run` fails with "No tasks defined" |
+| Swift project with no `fledge.toml` | detected as `swift`, but `auto_detect_tasks` has no Swift entry, so no tasks are listed. `run --init` still writes Swift defaults |
+| Task dependency cycle | error naming the ordered cycle. `lanes run` checks every step before starting. `run` finds it during the walk |
+| Dependency chain deeper than 1,000 | error instead of a stack overflow (`MAX_TASK_DEPTH`) |
+| Task or step exits non-zero | `run` stops at that task. A lane stops (`fail_fast = true`) or records it and continues. Exit 1 either way |
+| Step `timeout` reached | the whole process tree is killed, the step fails with "step timed out", retries apply |
+| Lane under `--json` | child output is discarded, so diagnose with a human-mode run |
+| `plugins.toml` unreadable or mid-write | one 50 ms retry, then a warning. Plugin verbs resolve as unrecognized for that invocation |
+| Plugin install fails at any step | the plugin dir (and links, after linking) are removed. The registry is untouched |
+| Failing `post_remove` hook | `plugins remove` returns the error after the command links were already deleted, leaving the plugin dir and registry entry in place |
+| Plugin requires an unknown `protocol` | refused with "update fledge" |
+| Protocol plugin sends malformed JSON | the line is skipped with a warning |
+| Plugin `exec` request | 30 s default, 300 s maximum, 10 MB per stream |
+| Plugin state store | 256-byte keys, 64 KB values, 1 MB total, 256 keys |
+| WASM plugin runs too long or too big | fuel exhaustion, a 60 s epoch deadline, or the 256 MB memory limit terminates it |
+| GitHub API | 30 s timeout per request. Status errors (for example rate limits) become readable messages |
+| LLM call | timeout from `FLEDGE_AI_TIMEOUT` or provider config. In a multi-model review a failing slot reports its own `error` and the others still return |
+| Prompt needed in non-interactive mode | confirmations behave as `--yes`. Prompts without a default fail fast with the flag to pass |
+| Broken pipe on stdout | fledge exits quietly (default `SIGPIPE`), except while `--stream` is mirroring |
+
+## 10. Decisions
+
+The design decisions live next to each module's spec as `specs//context.md`. The ones
+that shape the architecture:
+
+- **Tight core, plugins for the rest.** GitHub, dependency audits and metrics moved out of core
+ in v0.15 and came back as the default plugins (`DEFAULT_PLUGINS` in
+ [`src/plugin/mod.rs`](../src/plugin/mod.rs)).
+- **Git-style plugin commands.** `fledge NAME` resolves to `fledge-NAME`, with symlinks in
+ `plugins/bin/` so PATH is never modified ([`specs/plugin/context.md`](../specs/plugin/context.md)).
+- **One file for tasks and lanes.** Lanes share `fledge.toml`, parallel groups use threads rather
+ than async, and imports never overwrite ([`specs/lanes/context.md`](../specs/lanes/context.md)).
+- **One graph walk.** `run`, `lanes run` and `lanes validate` share `walk_task_graph`, so they
+ cannot disagree about diamonds, cycles or depth ([`specs/run/context.md`](../specs/run/context.md)).
+- **stdout belongs to envelopes.** `--stream` mirrors to stderr and hook progress goes to stderr,
+ so `--json` output stays machine-readable ([`specs/run/context.md`](../specs/run/context.md)).
+- **Sandbox by default for WASM.** Capabilities are opt-in and prompted
+ ([`specs/plugin/context-wasm.md`](../specs/plugin/context-wasm.md),
+ [`specs/plugin/plugin-wasm.spec.md`](../specs/plugin/plugin-wasm.spec.md)).
+- **AI over plain HTTP.** Providers moved from a CLI to HTTP in 1.5.0 ([`MIGRATION.md`](../MIGRATION.md)).
+- **Specs are the contract.** SpecSync governs `src/` and `templates/`
+ ([`.specsync/config.toml`](../.specsync/config.toml)), and the change history is in
+ [`CHANGELOG.md`](../CHANGELOG.md).
+
+## 11. Glossary
+
+| Term | Meaning |
+|---|---|
+| Task | A named shell command in `[tasks]`, optionally with deps, env and dir |
+| Lane | A named, ordered pipeline of steps in `[lanes.NAME]` |
+| Step | One entry in a lane: a task ref, an inline `run`, or a `parallel` group |
+| Envelope | The `{schema_version, ...}` JSON object a `--json` command prints |
+| Native plugin | An executable plugin, run as the user, unsandboxed |
+| Protocol plugin | A plugin with `protocol = "fledge-v1"`, which talks JSON lines with fledge |
+| WASM plugin | A protocol plugin with `runtime = "wasm"`, run inside Wasmtime |
+| Capability | A permission a plugin requests (`exec`, `store`, `metadata`, `filesystem`, `network`) and the user grants at install |
+| Trust tier | `local`, `official`, `team` or `unverified`, derived from a source's path or owner |
+| Lifecycle hook | A plugin hook fired by a core command (`pre_init`, `post_work_start`, `pre_push`) |
+| Spec | A `specs//*.spec.md` contract, validated by SpecSync |
+| hi | Human Intent: the plain-language criteria in [`hi/`](../hi) |