diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index e21f8e9..c04149e 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -4,14 +4,14 @@ "name": "TerminallyLazy", "url": "https://github.com/TerminallyLazy" }, - "description": "Claude Code marketplace for Tree Ring Memory v0.14 local-first recall and receipt-backed harness readiness.", - "version": "0.3.1", + "description": "Claude Code marketplace for Tree Ring Memory v0.15 verified bootstrap, local-first recall, and receipt-backed harness readiness.", + "version": "0.3.2", "plugins": [ { "name": "tree-ring-memory", "source": "./plugins/tree-ring-memory", "displayName": "Tree Ring Memory", - "description": "Local-first memory lifecycle and receipt-backed harness guidance for Claude Code using Tree Ring Memory v0.14+.", + "description": "Local-first memory lifecycle, project bootstrap, and receipt-backed harness guidance for Claude Code using Tree Ring Memory v0.15+.", "author": { "name": "TerminallyLazy", "url": "https://github.com/TerminallyLazy" diff --git a/Cargo.lock b/Cargo.lock index d726ddf..a226cf1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1690,13 +1690,14 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "tree-ring-memory-cli" -version = "0.14.0" +version = "0.15.0" dependencies = [ "chrono", "clap", "libc", "ratatui", "rusqlite", + "semver", "serde", "serde_json", "sha2", @@ -1708,7 +1709,7 @@ dependencies = [ [[package]] name = "tree-ring-memory-core" -version = "0.14.0" +version = "0.15.0" dependencies = [ "chrono", "libc", @@ -1724,7 +1725,7 @@ dependencies = [ [[package]] name = "tree-ring-memory-sqlite" -version = "0.14.0" +version = "0.15.0" dependencies = [ "rusqlite", "serde", diff --git a/Cargo.toml b/Cargo.toml index 6735592..adad624 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.14.0" +version = "0.15.0" edition = "2021" license = "MIT" authors = ["TerminallyLazy"] @@ -22,6 +22,7 @@ regex = "1" rusqlite = { version = "0.32", features = ["bundled", "functions"] } serde = { version = "1", features = ["derive"] } serde_json = "1" +semver = "1" sha2 = "0.10" tempfile = "3" thiserror = "1" diff --git a/README.md b/README.md index c9eee46..fa71948 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ framework-agnostic and does not replace either protocol. Tree Ring Memory is in protocol-preview status. Current launch links: - Launch page: -- Launch release: +- Launch release: - Launch discussion: - Rust-native CLI article: - Feedback issue: @@ -40,6 +40,7 @@ Tree Ring Memory is in protocol-preview status. Current launch links: - v0.12 adds a controlled, retained agent-workflow proof with explicit model identity and exact structured-output checks; it reports observed outcomes without claiming a universal memory advantage. - v0.13 adds same-host multi-agent identities and idempotency, opt-in coordinator authorization for shared writes, a protected-write audit, and a schema-v3 fence for old memory inserts, updates, and deletes. - v0.14 adds project-local harness activation with receipt-backed readiness, so configured bridges do not imply that an agent has used memory. +- v0.15 adds verified release bootstrap, project-root-aware agent guidance, and scope-preserving CLI updates. @@ -147,7 +148,9 @@ Add and install the same package in Claude Code: /plugin install tree-ring-memory@tree-ring-memory ``` -The plugin still requires Tree Ring Memory CLI v0.14.0 or newer. See the +The plugin requires Tree Ring Memory CLI v0.15.0 or newer and teaches agents how +to install a verified project-local runtime when setup is already authorized. +See the [plugin README](plugins/tree-ring-memory/README.md) for the platform manifests, commands, DOX contract flow, and certification boundary. @@ -174,18 +177,17 @@ The matching Tree Ring core and Agent Zero plugin release must both be installed; a source checkout, a passive binding, or an older bundled CLI is not an installed-capability claim. -Source install (requires Rust and Cargo): +Verified prebuilt install for macOS ARM64 or Linux x86_64: ```bash -curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh +curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh -s -- --release latest ``` -Linux x86_64 prebuilt install (glibc 2.36 or newer): +Recommended project-local install with first-run initialization: ```bash -curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh -s -- \ - --archive-url https://github.com/TerminallyLazy/Tree-Ring-Memory/releases/download/v0.14.0/tree-ring-memory-0.14.0-linux-x86_64.tar.gz \ - --archive-sha256 c72191aca81f195472272a1962df354fe0af04a08b01a7472a1faf987cd177fa +cd +curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh -s -- --project --init --release latest --no-animation ``` macOS ARM64 install with Homebrew: @@ -195,10 +197,10 @@ brew tap TerminallyLazy/tree-ring brew install tree-ring ``` -Project-local source install with first-run initialization (requires Cargo): +Source install (requires Rust and Cargo): ```bash -curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh -s -- --project --init +curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh ``` Store the first project memory: @@ -222,11 +224,13 @@ tree-ring tui ## Install Details -The installer builds the Rust CLI with `cargo`, installs `tree-ring`, then shows -one stable terminal onboarding screen with a branded terminal ring and the next -useful commands. For global installs, it also adds the install bin directory to your -shell profile when that directory is not already on `PATH`. It does not -initialize memory unless `--init` is passed. +With `--release latest`, the installer downloads the official platform archive, +downloads its published SHA-256, verifies it, and installs `tree-ring` without +requiring Rust. Without `--release`, it builds the Rust CLI with `cargo`. It then +shows one terminal onboarding screen with a branded terminal ring and useful +commands. For global installs, it can add the install bin directory to the shell +profile when that directory is not already on `PATH`. It does not initialize +memory unless `--init` is passed. The installer command streams only the installer script into `sh`. It does not put memory in a temporary location and it does not remove `.tree-ring`, @@ -251,11 +255,12 @@ Useful installer options: ```bash sh install.sh --help -sh install.sh --project --init +sh install.sh --project --init --release latest sh install.sh --global --install-dir "$HOME/.local" sh install.sh --no-animation # stable output; kept for explicit script usage sh install.sh --no-path-update -sh install.sh --archive-url https://example/tree-ring-memory-0.14.0-darwin-arm64.tar.gz --archive-sha256 +sh install.sh --release 0.15.0 +sh install.sh --archive-url https://example/tree-ring-memory-0.15.0-darwin-arm64.tar.gz --archive-sha256 ``` After install, rerun onboarding anytime: @@ -267,8 +272,41 @@ tree-ring ``` By default, the installer uses `cargo install` from the Git repository or a -local `--source` checkout. Release builds can use `--archive-url` plus -`--archive-sha256` to install a prebuilt `tree-ring` binary archive. +local `--source` checkout. `--release latest` or `--release ` resolves +and verifies the matching official prebuilt archive. Advanced callers can still +use `--archive-url` plus `--archive-sha256` explicitly. + +## Update Tree Ring Memory + +Check the official release without changing files: + +```bash +tree-ring update --check +``` + +After authorizing an update, update the active executable in its existing +project-local, direct-prefix, or Homebrew-managed scope: + +```bash +tree-ring update +tree-ring --version +``` + +The updater verifies the official release archive and checksum and does not +install a second copy in a different prefix. After updating, return to each +project root and safely backfill its managed guidance: + +```bash +tree-ring --root .tree-ring init +tree-ring --root .tree-ring integrations status --verbose +``` + +CLIs older than v0.15 do not contain `tree-ring update`. Upgrade those with the +same manager or install prefix first: Homebrew with `brew upgrade tree-ring`, a +project-local install with `install.sh --project --release latest`, or another +direct install with `install.sh --install-dir --release +latest`. Use `command -v tree-ring` and `which -a tree-ring` afterward to find +an older binary that may still shadow the updated executable. Open the terminal console after a global install: diff --git a/crates/tree-ring-memory-cli/Cargo.toml b/crates/tree-ring-memory-cli/Cargo.toml index 6a84f5d..dd7ff18 100644 --- a/crates/tree-ring-memory-cli/Cargo.toml +++ b/crates/tree-ring-memory-cli/Cargo.toml @@ -22,11 +22,12 @@ libc.workspace = true ratatui.workspace = true serde.workspace = true serde_json.workspace = true +semver.workspace = true sha2.workspace = true +tempfile.workspace = true uuid.workspace = true -tree-ring-memory-core = { path = "../tree-ring-memory-core", version = "0.14.0" } -tree-ring-memory-sqlite = { path = "../tree-ring-memory-sqlite", version = "0.14.0" } +tree-ring-memory-core = { path = "../tree-ring-memory-core", version = "0.15.0" } +tree-ring-memory-sqlite = { path = "../tree-ring-memory-sqlite", version = "0.15.0" } [dev-dependencies] rusqlite.workspace = true -tempfile.workspace = true diff --git a/crates/tree-ring-memory-cli/src/agent_awareness.rs b/crates/tree-ring-memory-cli/src/agent_awareness.rs index bc2dcd5..59b8200 100644 --- a/crates/tree-ring-memory-cli/src/agent_awareness.rs +++ b/crates/tree-ring-memory-cli/src/agent_awareness.rs @@ -20,6 +20,12 @@ const SKILL_QUALITY_GATES_HEADING: &str = "## Memory Quality Gates"; const SKILL_QUALITY_GATES_ANCHOR: &str = "## Ring Selection"; const SKILL_POLICY_HEADING: &str = "## Coordinated Write Policy"; const SKILL_POLICY_ANCHOR: &str = "## Agent-Mediated Updates"; +const AGENT_RUNTIME_HEADING: &str = "## Local Runtime Bootstrap And Updates"; +const AGENT_RUNTIME_ANCHOR: &str = "## Harness Bridges"; +const SKILL_RUNTIME_HEADING: &str = "## Runtime Bootstrap And Updates"; +const SKILL_RUNTIME_ANCHOR: &str = "## When To Recall"; +const CLI_RUNTIME_HEADING: &str = "Runtime bootstrap and updates:"; +const CLI_RUNTIME_ANCHOR: &str = "Core commands:"; const PREFLIGHT_HEADING: &str = "## Harness Preflight"; const PREFLIGHT_GUIDANCE: &str = r#"## Harness Preflight @@ -49,10 +55,20 @@ Use memory deliberately: - use seeds for future work and hypotheses - forget, redact, or supersede stale or sensitive memory +Runtime bootstrap and updates: + +- Resolve the real project root before initialization. Never initialize a plugin cache, package directory, home directory, or incidental working directory. +- Prefer `/.tree-ring/bin/tree-ring` when present; otherwise use the active `tree-ring` on `PATH`. +- With existing user authorization for setup, install the verified project-local release from the project root with `curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh -s -- --project --init --release latest --no-animation`. Otherwise obtain permission before downloading or installing software. +- Use `tree-ring --root .tree-ring init` for a global CLI or `.tree-ring/bin/tree-ring --root .tree-ring init` for a project-local CLI. Verify that `memory.sqlite`, `AGENTS.md`, `SKILL.md`, and `CLI.md` are under the intended project. +- `tree-ring update --check` is read-only. Run `tree-ring update` only with update authorization; it preserves the active installation scope and verifies official release assets. Re-run project initialization afterward to backfill managed guidance. +- A pre-0.15 CLI must be upgraded with its existing package manager or install prefix. Check `command -v tree-ring` and `which -a tree-ring` so an old binary does not shadow the update. + Core commands: ```bash tree-ring init +tree-ring update --check tree-ring recall "project startup warnings" tree-ring remember "Use project-scoped recall before risky changes." --event-type lesson --scope project tree-ring evidence "Snapshot invalidation fixed stale unread chat state." --outcome promoted --evidence-ref evals/chat-state/run-042 --score 0.91 @@ -176,6 +192,27 @@ pub fn ensure_agent_awareness(root: &Path) -> Result Result<(), String> { return run_init(&cli.root, *dry_run, cli.json); } + if let Command::Update { check } = &cli.command { + return update::run(*check, cli.json); + } + if let Command::Integrations { command: IntegrationCommand::Scan { source_root }, } = &cli.command @@ -961,6 +971,7 @@ fn run(cli: Cli) -> Result<(), String> { match cli.command { Command::Init { .. } => unreachable!("init returns before the shared store route"), + Command::Update { .. } => unreachable!("update returns before the shared store route"), Command::Remember { summary, event_type, @@ -1513,6 +1524,7 @@ fn command_actor_profile(command: &Command) -> Option { fn command_origin(command: &Command) -> &'static str { match command { Command::Init { .. } => "cli:init", + Command::Update { .. } => "cli:update", Command::Remember { .. } => "cli:remember", Command::Evidence { .. } => "cli:evidence", Command::Recall { .. } => "cli:recall", @@ -2174,6 +2186,12 @@ mod tests { assert!(!root.join("activation.json").exists()); } + #[test] + fn update_check_parser_selects_a_non_mutating_release_check() { + let cli = Cli::try_parse_from(["tree-ring", "update", "--check"]).unwrap(); + assert!(matches!(cli.command, Command::Update { check: true })); + } + #[test] fn integrations_status_and_activation_dry_run_do_not_create_a_store() { let dir = tempdir().unwrap(); diff --git a/crates/tree-ring-memory-cli/src/update.rs b/crates/tree-ring-memory-cli/src/update.rs new file mode 100644 index 0000000..c3d103c --- /dev/null +++ b/crates/tree-ring-memory-cli/src/update.rs @@ -0,0 +1,546 @@ +use semver::Version; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +const RELEASE_API_URL: &str = + "https://api.github.com/repos/TerminallyLazy/Tree-Ring-Memory/releases/latest"; +const RELEASE_DOWNLOAD_PREFIX: &str = + "https://github.com/TerminallyLazy/Tree-Ring-Memory/releases/download/"; +const HOMEBREW_FORMULA: &str = "terminallylazy/tree-ring/tree-ring"; + +#[derive(Debug, Clone, Deserialize)] +struct GithubRelease { + tag_name: String, + assets: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +struct GithubAsset { + name: String, + browser_download_url: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +enum InstallMethod { + Homebrew, + ProjectLocal { prefix: PathBuf }, + Direct { prefix: PathBuf }, +} + +impl InstallMethod { + fn name(&self) -> &'static str { + match self { + Self::Homebrew => "homebrew", + Self::ProjectLocal { .. } => "project-local", + Self::Direct { .. } => "direct", + } + } +} + +#[derive(Debug, Serialize)] +struct UpdateReport { + current_version: String, + latest_version: String, + update_available: bool, + updated: bool, + install_method: String, + executable: String, + next_step: String, +} + +pub(crate) fn run(check_only: bool, json_output: bool) -> Result<(), String> { + let executable = std::env::current_exe() + .map_err(|error| format!("failed to resolve the current tree-ring executable: {error}"))?; + let executable = fs::canonicalize(&executable).unwrap_or(executable); + let install_method = classify_install(&executable)?; + let release = fetch_latest_release()?; + let current = Version::parse(env!("CARGO_PKG_VERSION")) + .map_err(|error| format!("invalid installed Tree Ring version: {error}"))?; + let latest = parse_release_version(&release.tag_name)?; + let update_available = latest > current; + + let updated = if check_only || !update_available { + false + } else { + apply_update(&release, &latest, &executable, &install_method)?; + true + }; + + let next_step = if updated { + "From each project root, run `tree-ring --root .tree-ring init` to refresh managed agent guidance." + .to_string() + } else if update_available { + "Run `tree-ring update` to install this release in the same location.".to_string() + } else { + "Tree Ring Memory is up to date.".to_string() + }; + let report = UpdateReport { + current_version: current.to_string(), + latest_version: latest.to_string(), + update_available, + updated, + install_method: install_method.name().to_string(), + executable: executable.display().to_string(), + next_step, + }; + print_report(&report, check_only, json_output) +} + +fn print_report(report: &UpdateReport, check_only: bool, json_output: bool) -> Result<(), String> { + if json_output { + println!( + "{}", + serde_json::to_string(report).map_err(|error| error.to_string())? + ); + return Ok(()); + } + + if report.updated { + println!( + "Updated Tree Ring Memory {} -> {} ({})", + report.current_version, report.latest_version, report.install_method + ); + } else if report.update_available { + println!( + "Tree Ring Memory {} is available (installed: {}, method: {}).", + report.latest_version, report.current_version, report.install_method + ); + if check_only { + println!("No files were changed (--check)."); + } + } else { + println!("Tree Ring Memory {} is up to date.", report.current_version); + } + println!("{}", report.next_step); + Ok(()) +} + +fn fetch_latest_release() -> Result { + let body = download_text(RELEASE_API_URL)?; + serde_json::from_str(&body) + .map_err(|error| format!("failed to parse the latest Tree Ring release: {error}")) +} + +fn download_text(url: &str) -> Result { + let output = if command_exists("curl") { + Command::new("curl") + .args([ + "-fsSL", + "-H", + "Accept: application/vnd.github+json", + "-H", + "X-GitHub-Api-Version: 2022-11-28", + "-A", + "tree-ring-memory-updater", + url, + ]) + .output() + } else if command_exists("wget") { + Command::new("wget") + .args(["-qO-", "--user-agent=tree-ring-memory-updater", url]) + .output() + } else { + return Err("tree-ring update requires curl or wget".to_string()); + } + .map_err(|error| format!("failed to start release download: {error}"))?; + + if !output.status.success() { + return Err(format!( + "failed to download {url}: {}", + String::from_utf8_lossy(&output.stderr).trim() + )); + } + String::from_utf8(output.stdout) + .map_err(|_| format!("downloaded response from {url} was not UTF-8")) +} + +fn download_file(url: &str, destination: &Path) -> Result<(), String> { + let status = if command_exists("curl") { + Command::new("curl") + .args(["-fsSL", "-A", "tree-ring-memory-updater", "-o"]) + .arg(destination) + .arg(url) + .status() + } else if command_exists("wget") { + Command::new("wget") + .args(["-q", "--user-agent=tree-ring-memory-updater", "-O"]) + .arg(destination) + .arg(url) + .status() + } else { + return Err("tree-ring update requires curl or wget".to_string()); + } + .map_err(|error| format!("failed to start release download: {error}"))?; + + if status.success() { + Ok(()) + } else { + Err(format!("failed to download {url}")) + } +} + +fn command_exists(command: &str) -> bool { + Command::new(command) + .arg("--version") + .output() + .map(|output| output.status.success()) + .unwrap_or(false) +} + +fn parse_release_version(tag: &str) -> Result { + Version::parse(tag.trim_start_matches('v')) + .map_err(|error| format!("latest release has an invalid version `{tag}`: {error}")) +} + +fn classify_install(executable: &Path) -> Result { + let components = executable + .components() + .map(|component| component.as_os_str().to_string_lossy()) + .collect::>(); + if components + .windows(2) + .any(|pair| pair[0] == "Cellar" && pair[1] == "tree-ring") + { + return Ok(InstallMethod::Homebrew); + } + + let bin_dir = executable.parent().ok_or_else(|| { + format!( + "cannot determine the install prefix for {}", + executable.display() + ) + })?; + if bin_dir.file_name().and_then(|name| name.to_str()) != Some("bin") { + return Err(format!( + "refusing to update an executable outside a bin directory: {}", + executable.display() + )); + } + let prefix = bin_dir.parent().ok_or_else(|| { + format!( + "cannot determine the install prefix for {}", + executable.display() + ) + })?; + if prefix.file_name().and_then(|name| name.to_str()) == Some(".tree-ring") { + Ok(InstallMethod::ProjectLocal { + prefix: prefix.to_path_buf(), + }) + } else { + Ok(InstallMethod::Direct { + prefix: prefix.to_path_buf(), + }) + } +} + +fn apply_update( + release: &GithubRelease, + latest: &Version, + executable: &Path, + install_method: &InstallMethod, +) -> Result { + match install_method { + InstallMethod::Homebrew => update_homebrew(latest), + InstallMethod::ProjectLocal { prefix } | InstallMethod::Direct { prefix } => { + update_direct(release, latest, executable, prefix) + } + } +} + +fn update_homebrew(expected: &Version) -> Result { + if !command_exists("brew") { + return Err( + "this Tree Ring binary is Homebrew-managed, but brew was not found".to_string(), + ); + } + let status = Command::new("brew") + .args(["upgrade", HOMEBREW_FORMULA]) + .status() + .map_err(|error| format!("failed to start Homebrew: {error}"))?; + if !status.success() { + return Err(format!( + "Homebrew could not upgrade {HOMEBREW_FORMULA}; try `brew update` and rerun `tree-ring update`" + )); + } + let prefix_output = Command::new("brew") + .args(["--prefix", HOMEBREW_FORMULA]) + .output() + .map_err(|error| format!("failed to resolve the Homebrew prefix: {error}"))?; + if !prefix_output.status.success() { + return Err( + "Homebrew upgraded Tree Ring but did not report its install prefix".to_string(), + ); + } + let prefix = String::from_utf8(prefix_output.stdout) + .map_err(|_| "Homebrew returned a non-UTF-8 install prefix".to_string())?; + let installed = installed_version_from_path(Path::new(prefix.trim()).join("bin/tree-ring"))?; + if &installed < expected { + return Err(format!( + "Homebrew installed {installed}, but the latest release is {expected}; the formula may still be updating" + )); + } + Ok(true) +} + +fn update_direct( + release: &GithubRelease, + latest: &Version, + executable: &Path, + prefix: &Path, +) -> Result { + let platform = release_platform()?; + let archive_name = format!("tree-ring-memory-{latest}-{platform}.tar.gz"); + let checksum_name = format!("{archive_name}.sha256"); + let archive_url = asset_url(release, &archive_name)?; + let checksum_url = asset_url(release, &checksum_name)?; + + let temp = tempfile::Builder::new() + .prefix("tree-ring-update-") + .tempdir_in(prefix) + .map_err(|error| { + format!( + "cannot create an update staging directory in {}: {error}", + prefix.display() + ) + })?; + let archive = temp.path().join(&archive_name); + download_file(archive_url, &archive)?; + let expected_checksum = checksum_from_text(&download_text(checksum_url)?)?; + verify_checksum(&archive, &expected_checksum)?; + + let unpacked = temp.path().join("unpacked"); + fs::create_dir(&unpacked).map_err(|error| error.to_string())?; + let status = Command::new("tar") + .args(["-xzf"]) + .arg(&archive) + .arg("-C") + .arg(&unpacked) + .status() + .map_err(|error| format!("failed to start tar: {error}"))?; + if !status.success() { + return Err("failed to unpack the verified Tree Ring release".to_string()); + } + let released_binary = find_released_binary(&unpacked)?; + let staged = temp.path().join("tree-ring.new"); + fs::copy(&released_binary, &staged) + .map_err(|error| format!("failed to stage the updated binary: {error}"))?; + make_executable(&staged)?; + + let staged_version = installed_version_from_path(&staged)?; + if &staged_version != latest { + return Err(format!( + "verified release contained Tree Ring {staged_version}, expected {latest}" + )); + } + fs::rename(&staged, executable) + .map_err(|error| format!("failed to replace {}: {error}", executable.display()))?; + Ok(true) +} + +fn release_platform() -> Result<&'static str, String> { + match (std::env::consts::OS, std::env::consts::ARCH) { + ("macos", "aarch64") => Ok("darwin-arm64"), + ("linux", "x86_64") => Ok("linux-x86_64"), + (os, arch) => Err(format!( + "no prebuilt Tree Ring release is available for {os}/{arch}" + )), + } +} + +fn asset_url<'a>(release: &'a GithubRelease, name: &str) -> Result<&'a str, String> { + let asset = release + .assets + .iter() + .find(|asset| asset.name == name) + .ok_or_else(|| format!("latest release does not include {name}"))?; + if !asset + .browser_download_url + .starts_with(RELEASE_DOWNLOAD_PREFIX) + { + return Err(format!( + "release asset {name} has an unexpected download URL" + )); + } + Ok(&asset.browser_download_url) +} + +fn checksum_from_text(text: &str) -> Result { + let checksum = text.split_whitespace().next().unwrap_or_default(); + if checksum.len() != 64 + || !checksum + .chars() + .all(|character| character.is_ascii_hexdigit()) + { + return Err("release checksum file did not contain a valid SHA-256".to_string()); + } + Ok(checksum.to_ascii_lowercase()) +} + +fn verify_checksum(path: &Path, expected: &str) -> Result<(), String> { + let bytes = + fs::read(path).map_err(|error| format!("failed to read release archive: {error}"))?; + let actual = format!("{:x}", Sha256::digest(bytes)); + if actual == expected { + Ok(()) + } else { + Err("release archive checksum mismatch".to_string()) + } +} + +fn find_released_binary(root: &Path) -> Result { + let mut pending = vec![root.to_path_buf()]; + while let Some(directory) = pending.pop() { + for entry in fs::read_dir(&directory) + .map_err(|error| format!("failed to inspect release archive: {error}"))? + { + let entry = entry.map_err(|error| error.to_string())?; + let path = entry.path(); + let file_type = entry.file_type().map_err(|error| error.to_string())?; + if file_type.is_dir() { + pending.push(path); + } else if file_type.is_file() && entry.file_name() == "tree-ring" { + return Ok(path); + } + } + } + Err("release archive did not contain tree-ring".to_string()) +} + +#[cfg(unix)] +fn make_executable(path: &Path) -> Result<(), String> { + use std::os::unix::fs::PermissionsExt; + let mut permissions = fs::metadata(path) + .map_err(|error| error.to_string())? + .permissions(); + permissions.set_mode(0o755); + fs::set_permissions(path, permissions).map_err(|error| error.to_string()) +} + +#[cfg(not(unix))] +fn make_executable(_path: &Path) -> Result<(), String> { + Ok(()) +} + +fn installed_version_from_path(command: impl AsRef) -> Result { + let command = command.as_ref(); + let output = Command::new(command) + .arg("--version") + .output() + .map_err(|error| format!("failed to run {} --version: {error}", command.display()))?; + parse_version_output(&output.stdout, output.status.success()) +} + +fn parse_version_output(stdout: &[u8], success: bool) -> Result { + if !success { + return Err("updated tree-ring binary did not run successfully".to_string()); + } + let output = String::from_utf8_lossy(stdout); + let version = output + .split_whitespace() + .last() + .ok_or_else(|| "updated tree-ring binary did not print a version".to_string())?; + Version::parse(version) + .map_err(|error| format!("updated tree-ring printed an invalid version: {error}")) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::File; + use std::io::Write; + + #[test] + fn parses_release_versions_with_or_without_v_prefix() { + assert_eq!( + parse_release_version("v0.15.0").unwrap(), + Version::new(0, 15, 0) + ); + assert_eq!( + parse_release_version("0.15.1").unwrap(), + Version::new(0, 15, 1) + ); + } + + #[test] + fn classifies_project_local_direct_and_homebrew_installs() { + assert!(matches!( + classify_install(Path::new("/work/demo/.tree-ring/bin/tree-ring")).unwrap(), + InstallMethod::ProjectLocal { .. } + )); + assert!(matches!( + classify_install(Path::new("/Users/example/.local/bin/tree-ring")).unwrap(), + InstallMethod::Direct { .. } + )); + assert_eq!( + classify_install(Path::new( + "/opt/homebrew/Cellar/tree-ring/0.15.0/bin/tree-ring" + )) + .unwrap(), + InstallMethod::Homebrew + ); + } + + #[test] + fn refuses_an_executable_without_a_bin_prefix() { + let error = classify_install(Path::new("/work/demo/tree-ring")).unwrap_err(); + assert!(error.contains("outside a bin directory")); + } + + #[test] + fn selects_only_official_named_release_assets() { + let release = GithubRelease { + tag_name: "v0.15.0".to_string(), + assets: vec![GithubAsset { + name: "tree-ring-memory-0.15.0-darwin-arm64.tar.gz".to_string(), + browser_download_url: format!( + "{RELEASE_DOWNLOAD_PREFIX}v0.15.0/tree-ring-memory-0.15.0-darwin-arm64.tar.gz" + ), + }], + }; + assert!(asset_url(&release, "tree-ring-memory-0.15.0-darwin-arm64.tar.gz").is_ok()); + assert!(asset_url(&release, "missing.tar.gz").is_err()); + } + + #[test] + fn rejects_release_assets_from_an_unexpected_host() { + let release = GithubRelease { + tag_name: "v0.15.0".to_string(), + assets: vec![GithubAsset { + name: "archive.tar.gz".to_string(), + browser_download_url: "https://example.com/archive.tar.gz".to_string(), + }], + }; + assert!(asset_url(&release, "archive.tar.gz") + .unwrap_err() + .contains("unexpected download URL")); + } + + #[test] + fn parses_and_validates_sha256_files() { + let temp = tempfile::tempdir().unwrap(); + let archive = temp.path().join("archive.tar.gz"); + let mut file = File::create(&archive).unwrap(); + file.write_all(b"tree-ring").unwrap(); + let checksum = format!("{:x}", Sha256::digest(b"tree-ring")); + + assert_eq!( + checksum_from_text(&format!("{checksum} archive.tar.gz\n")).unwrap(), + checksum + ); + verify_checksum(&archive, &checksum).unwrap(); + assert!(checksum_from_text("not-a-checksum").is_err()); + assert!(verify_checksum(&archive, &"0".repeat(64)).is_err()); + } + + #[test] + fn parses_tree_ring_version_output() { + assert_eq!( + parse_version_output(b"tree-ring 0.15.0\n", true).unwrap(), + Version::new(0, 15, 0) + ); + assert!(parse_version_output(b"tree-ring 0.15.0\n", false).is_err()); + } +} diff --git a/crates/tree-ring-memory-sqlite/Cargo.toml b/crates/tree-ring-memory-sqlite/Cargo.toml index 62cf2d8..995b535 100644 --- a/crates/tree-ring-memory-sqlite/Cargo.toml +++ b/crates/tree-ring-memory-sqlite/Cargo.toml @@ -16,7 +16,7 @@ rusqlite.workspace = true serde.workspace = true serde_json.workspace = true sha2.workspace = true -tree-ring-memory-core = { path = "../tree-ring-memory-core", version = "0.14.0" } +tree-ring-memory-core = { path = "../tree-ring-memory-core", version = "0.15.0" } uuid.workspace = true [dev-dependencies] diff --git a/docs/architecture/rust-core-status.md b/docs/architecture/rust-core-status.md index 4cdd107..cf99a85 100644 --- a/docs/architecture/rust-core-status.md +++ b/docs/architecture/rust-core-status.md @@ -12,7 +12,8 @@ idempotent-write semantics. The v0.13 line adds an opt-in coordinated write policy, protected-write audit, schema-v3 old-memory-mutation fence, and forward-schema rejection. The v0.14 line adds project-local harness activation with receipt-backed readiness, keeping configured bridges distinct from active -memory use. +memory use. The v0.15 line adds checksum-verified release bootstrap, +project-root-aware generated agent guidance, and scope-preserving CLI updates. ## Current Status @@ -41,7 +42,7 @@ memory use. scopes into deterministic per-record legacy partitions marked for review. Redaction retains a memory-ID tombstone, and replaced operation namespaces remain claimed until explicit hard deletion. -- Rust CLI owns the full local command surface: init, remember, evidence, +- Rust CLI owns the full local command surface: init, update, remember, evidence, recall, forget, import/export, audit, consolidate, maintain, DOX sync, Revolve sync, coordinated policy management, framework discovery, welcome onboarding, and TUI operation. @@ -92,7 +93,10 @@ memory use. agent scope. In Coordinated mode, lifecycle actions require the coordinator capability inherited through `TREE_RING_COORDINATOR_TOKEN`. - The repository includes `install.sh` for one-line global or project-local - installs, plus `tree-ring welcome` for first-run terminal onboarding. + source installs and checksum-verified official release installs, plus + `tree-ring welcome` for first-run terminal onboarding. `tree-ring update + --check` checks without mutation; `tree-ring update` preserves the active + project-local, direct-prefix, or Homebrew installation scope. - The Rust CLI includes `tree-ring dox sync` and `tree-ring revolve sync` as source adapters that produce concise, source-linked memory events without replacing DOX contracts or Revolve evidence records. diff --git a/docs/feed.xml b/docs/feed.xml index 432e869..d72cfe5 100644 --- a/docs/feed.xml +++ b/docs/feed.xml @@ -5,12 +5,20 @@ https://terminallylazy.github.io/Tree-Ring-Memory/ - 2026-08-14T08:25:58Z + 2026-08-25T20:56:04Z Tree Ring Memory https://github.com/TerminallyLazy/Tree-Ring-Memory + + Tree Ring Memory v0.15.0 adds verified bootstrap and CLI updates + https://github.com/TerminallyLazy/Tree-Ring-Memory/releases/tag/v0.15.0 + + 2026-08-25T20:56:04Z + Tree Ring Memory v0.15.0 adds checksum-verified project-local bootstrap, project-root-aware agent instructions, a read-only update check, and scope-preserving CLI updates for direct, project-local, and Homebrew-managed installs. + + Tree Ring Memory v0.14.0 adds receipt-backed harness activation https://github.com/TerminallyLazy/Tree-Ring-Memory/releases/tag/v0.14.0 diff --git a/docs/index.html b/docs/index.html index e1b80d5..f229213 100644 --- a/docs/index.html +++ b/docs/index.html @@ -28,7 +28,7 @@ "programmingLanguage": "Rust", "applicationCategory": "DeveloperApplication", "operatingSystem": "macOS, Linux", - "version": "0.14.0", + "version": "0.15.0", "image": "https://terminallylazy.github.io/Tree-Ring-Memory/assets/tree-ring-memory-og.png", "isAccessibleForFree": true } @@ -436,19 +436,18 @@

Try it locally.

  • Feedback flows through GitHub issue #26.
  • -
    # Source install (requires Rust and Cargo)
    -curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh
    +      
    # Verified prebuilt install (macOS ARM64 or Linux x86_64)
    +curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh -s -- --release latest
     
    -# Linux x86_64 prebuilt (glibc 2.36+)
    -curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh -s -- \
    -  --archive-url https://github.com/TerminallyLazy/Tree-Ring-Memory/releases/download/v0.14.0/tree-ring-memory-0.14.0-linux-x86_64.tar.gz \
    -  --archive-sha256 c72191aca81f195472272a1962df354fe0af04a08b01a7472a1faf987cd177fa
    +# Project-local install and initialization
    +curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh -s -- --project --init --release latest --no-animation
     
     # macOS ARM64 Homebrew tap
     brew tap TerminallyLazy/tree-ring
     brew install tree-ring
     
     tree-ring init
    +tree-ring update --check
     tree-ring remember "Use project-scoped recall before risky changes." --event-type lesson --scope project
     tree-ring recall "risky changes"
     tree-ring evidence "The eval passed after the fix." --outcome promoted --evidence-ref evals/run-042
    diff --git a/docs/integrations/agent-skill.md b/docs/integrations/agent-skill.md
    index d8335cd..f0cdf96 100644
    --- a/docs/integrations/agent-skill.md
    +++ b/docs/integrations/agent-skill.md
    @@ -51,6 +51,29 @@ The CLI does not modify a project root `AGENTS.md` automatically. Merge the
     generated `.tree-ring/AGENTS.md` guidance manually when you want DOX-aware
     agents to encounter Tree Ring Memory instructions before entering `.tree-ring/`.
     
    +## Runtime Bootstrap And Updates
    +
    +Agents must resolve the real project root before setup. If the user has already
    +authorized Tree Ring installation, the safe default is the verified
    +project-local release installer, run from that root:
    +
    +```bash
    +curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh -s -- --project --init --release latest --no-animation
    +```
    +
    +Without existing setup authorization, the agent should explain that exact
    +operation and ask before downloading or installing software. It must not
    +initialize a plugin cache, package directory, home directory, or unrelated
    +working directory. Existing project-local installs should use
    +`.tree-ring/bin/tree-ring --root .tree-ring init`; global installs should use
    +`tree-ring --root .tree-ring init` from the project root.
    +
    +`tree-ring update --check` checks for a release without changing files. With
    +update authorization, `tree-ring update` verifies official assets and preserves
    +the active project-local, direct-prefix, or Homebrew scope. Afterward, rerun
    +`init` in each project to backfill managed guidance. CLIs older than v0.15 must
    +first be upgraded through their existing package manager or install prefix.
    +
     ## Minimal CLI Flow
     
     ```bash
    diff --git a/docs/llms.txt b/docs/llms.txt
    index 5394793..3706378 100644
    --- a/docs/llms.txt
    +++ b/docs/llms.txt
    @@ -5,14 +5,14 @@
     
     Website: https://terminallylazy.github.io/Tree-Ring-Memory/
     Repository: https://github.com/TerminallyLazy/Tree-Ring-Memory
    -Launch release: https://github.com/TerminallyLazy/Tree-Ring-Memory/releases/tag/v0.14.0
    +Launch release: https://github.com/TerminallyLazy/Tree-Ring-Memory/releases/tag/v0.15.0
     Launch discussion: https://github.com/TerminallyLazy/Tree-Ring-Memory/discussions/27
     Homebrew tap: https://github.com/TerminallyLazy/homebrew-tree-ring
     Feedback: https://github.com/TerminallyLazy/Tree-Ring-Memory/issues/26
     Feed: https://terminallylazy.github.io/Tree-Ring-Memory/feed.xml
     License: MIT
     Status: protocol-preview
    -Current version: 0.14.0
    +Current version: 0.15.0
     
     ## Summary
     
    @@ -37,19 +37,18 @@ ideas stay as seeds.
     - DOX and Revolve sync adapters.
     - Read-only agent-framework discovery.
     - Terminal onboarding and a Ratatui operator console.
    +- Verified prebuilt bootstrap and scope-preserving CLI updates.
     
    -## Install From Source (Requires Rust and Cargo)
    +## Verified Prebuilt Install
     
     ```bash
    -curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh
    +curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh -s -- --release latest
     ```
     
    -Linux x86_64 prebuilt install (glibc 2.36 or newer):
    +Recommended project-local setup from the project root:
     
     ```bash
    -curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh -s -- \
    -  --archive-url https://github.com/TerminallyLazy/Tree-Ring-Memory/releases/download/v0.14.0/tree-ring-memory-0.14.0-linux-x86_64.tar.gz \
    -  --archive-sha256 c72191aca81f195472272a1962df354fe0af04a08b01a7472a1faf987cd177fa
    +curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh -s -- --project --init --release latest --no-animation
     ```
     
     macOS ARM64 Homebrew install:
    @@ -59,6 +58,19 @@ brew tap TerminallyLazy/tree-ring
     brew install tree-ring
     ```
     
    +Source install requires Rust and Cargo and omits `--release latest`.
    +
    +## Update
    +
    +```bash
    +tree-ring update --check
    +tree-ring update
    +tree-ring --root .tree-ring init
    +```
    +
    +The updater verifies official assets and preserves the active install scope.
    +Pre-0.15 CLIs must first be upgraded with their existing manager or prefix.
    +
     ## First Commands
     
     ```bash
    @@ -91,7 +103,7 @@ writes are explicit.
     - Rust article: https://terminallylazy.github.io/Tree-Ring-Memory/launch/rust-native-agent-memory-cli.md
     - Press kit: https://terminallylazy.github.io/Tree-Ring-Memory/press-kit.md
     - Repository: https://github.com/TerminallyLazy/Tree-Ring-Memory
    -- Launch release: https://github.com/TerminallyLazy/Tree-Ring-Memory/releases/tag/v0.14.0
    +- Launch release: https://github.com/TerminallyLazy/Tree-Ring-Memory/releases/tag/v0.15.0
     - Launch discussion: https://github.com/TerminallyLazy/Tree-Ring-Memory/discussions/27
     - Homebrew tap: https://github.com/TerminallyLazy/homebrew-tree-ring
     - Feedback issue: https://github.com/TerminallyLazy/Tree-Ring-Memory/issues/26
    diff --git a/docs/press-kit.md b/docs/press-kit.md
    index 556689f..b0f9e8a 100644
    --- a/docs/press-kit.md
    +++ b/docs/press-kit.md
    @@ -23,10 +23,10 @@ DOX/Revolve adapters, framework discovery, and a terminal TUI.
     - Category: AI agents, developer tools, local-first software, Rust CLI
     - License: MIT
     - Status: protocol-preview
    -- Current version: 0.14.0
    +- Current version: 0.15.0
     - Website: 
     - Repository: 
    -- Launch release: 
    +- Launch release: 
     - Launch discussion: 
     - Homebrew tap: 
     - Feedback: 
    diff --git a/install.sh b/install.sh
    index a5ea022..ec718ba 100755
    --- a/install.sh
    +++ b/install.sh
    @@ -8,6 +8,7 @@ INSTALL_DIR=${TREE_RING_INSTALL_DIR:-""}
     SOURCE_DIR=${TREE_RING_SOURCE:-""}
     ARCHIVE_URL=${TREE_RING_ARCHIVE_URL:-""}
     ARCHIVE_SHA256=${TREE_RING_ARCHIVE_SHA256:-""}
    +RELEASE_VERSION=${TREE_RING_RELEASE:-""}
     MEMORY_ROOT=${TREE_RING_ROOT:-".tree-ring"}
     RUN_INIT=${TREE_RING_INIT:-"0"}
     RUN_ONBOARDING=${TREE_RING_ONBOARDING:-"1"}
    @@ -45,7 +46,7 @@ Tree Ring Memory installer
     
     Usage:
       curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh
    -  curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh -s -- --project --init
    +  curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh -s -- --project --init --release latest
     
     Options:
       --global              Install to $HOME/.local/bin (default).
    @@ -61,6 +62,7 @@ Options:
       --repo URL            Git repository used by cargo install.
       --ref REF             Git branch used by cargo install (default main).
       --source DIR          Install from a local checkout instead of git.
    +  --release VERSION     Install a verified prebuilt release; use latest for newest.
       --archive-url URL     Install from a release tarball containing tree-ring.
       --archive-sha256 SUM  Required SHA-256 for --archive-url.
       -h, --help            Show this help.
    @@ -72,6 +74,7 @@ Environment:
       TREE_RING_REPO=https://github.com/TerminallyLazy/Tree-Ring-Memory
       TREE_RING_REF=main
       TREE_RING_SOURCE=/path/to/checkout
    +  TREE_RING_RELEASE=latest
       TREE_RING_ARCHIVE_URL=https://example/tree-ring-memory.tar.gz
       TREE_RING_ARCHIVE_SHA256=...
       TREE_RING_INIT=1
    @@ -132,6 +135,11 @@ while [ "$#" -gt 0 ]; do
           [ "$#" -gt 0 ] || die "--source requires a value"
           SOURCE_DIR=$1
           ;;
    +    --release)
    +      shift
    +      [ "$#" -gt 0 ] || die "--release requires a value"
    +      RELEASE_VERSION=$1
    +      ;;
         --archive-url)
           shift
           [ "$#" -gt 0 ] || die "--archive-url requires a value"
    @@ -153,6 +161,12 @@ while [ "$#" -gt 0 ]; do
       shift
     done
     
    +if [ "$RELEASE_VERSION" != "" ] && [ "$SOURCE_DIR" != "" ]; then
    +  die "--release cannot be combined with --source"
    +fi
    +if [ "$RELEASE_VERSION" != "" ] && [ "$ARCHIVE_URL" != "" ]; then
    +  die "--release cannot be combined with --archive-url"
    +fi
     if [ "$ARCHIVE_URL" != "" ] && [ "$ARCHIVE_SHA256" = "" ]; then
       die "--archive-sha256 is required with --archive-url"
     fi
    @@ -208,6 +222,55 @@ download() {
       fi
     }
     
    +release_platform() {
    +  os=$(uname -s)
    +  arch=$(uname -m)
    +  case "$os/$arch" in
    +    Darwin/arm64) printf '%s' "darwin-arm64" ;;
    +    Linux/x86_64) printf '%s' "linux-x86_64" ;;
    +    *) die "no prebuilt Tree Ring release is available for $os/$arch" ;;
    +  esac
    +}
    +
    +resolve_release_archive() {
    +  [ "$RELEASE_VERSION" != "" ] || return 0
    +  case "$REPO_URL" in
    +    https://github.com/*) ;;
    +    *) die "--release requires a https://github.com/OWNER/REPO repository URL" ;;
    +  esac
    +
    +  repo_path=${REPO_URL#https://github.com/}
    +  repo_path=${repo_path%.git}
    +  case "$repo_path" in
    +    */*) ;;
    +    *) die "could not determine the GitHub repository for --release" ;;
    +  esac
    +
    +  version=$RELEASE_VERSION
    +  if [ "$version" = "latest" ]; then
    +    release_tmp=$(mktemp "${TMPDIR:-/tmp}/tree-ring-release.XXXXXX")
    +    download "https://api.github.com/repos/$repo_path/releases/latest" "$release_tmp"
    +    version=$(sed -n 's/^[[:space:]]*"tag_name":[[:space:]]*"v\{0,1\}\([^"]*\)".*/\1/p' "$release_tmp" | head -n 1)
    +    rm -f "$release_tmp"
    +    [ "$version" != "" ] || die "latest GitHub release did not contain a version tag"
    +  else
    +    version=${version#v}
    +  fi
    +  case "$version" in
    +    ""|*[!0-9A-Za-z.+-]*) die "invalid release version: $version" ;;
    +  esac
    +
    +  platform=$(release_platform)
    +  archive_name="tree-ring-memory-$version-$platform.tar.gz"
    +  release_base="$REPO_URL/releases/download/v$version"
    +  checksum_tmp=$(mktemp "${TMPDIR:-/tmp}/tree-ring-checksum.XXXXXX")
    +  download "$release_base/$archive_name.sha256" "$checksum_tmp"
    +  ARCHIVE_SHA256=$(awk 'NR == 1 { print $1 }' "$checksum_tmp")
    +  rm -f "$checksum_tmp"
    +  [ "$ARCHIVE_SHA256" != "" ] || die "release checksum file was empty"
    +  ARCHIVE_URL="$release_base/$archive_name"
    +}
    +
     verify_sha256() {
       file=$1
       expected=$2
    @@ -329,6 +392,7 @@ update_shell_path() {
     }
     
     intro
    +resolve_release_archive
     require_cargo
     
     PREFIX=$(install_prefix)
    diff --git a/marketing/README.md b/marketing/README.md
    index 3f83042..8fe5bdc 100644
    --- a/marketing/README.md
    +++ b/marketing/README.md
    @@ -309,7 +309,7 @@ python3 marketing/scripts/build-campaign-cards.py
     
     - Repository: `https://github.com/TerminallyLazy/Tree-Ring-Memory`
     - Launch page: `https://terminallylazy.github.io/Tree-Ring-Memory/`
    -- Launch release: `https://github.com/TerminallyLazy/Tree-Ring-Memory/releases/tag/v0.14.0`
    +- Launch release: `https://github.com/TerminallyLazy/Tree-Ring-Memory/releases/tag/v0.15.0`
     - Launch discussion: `https://github.com/TerminallyLazy/Tree-Ring-Memory/discussions/27`
     - Homebrew tap: `https://github.com/TerminallyLazy/homebrew-tree-ring`
     - Agent-Skills.md main repo listing:
    diff --git a/plugins/tree-ring-memory/.claude-plugin/plugin.json b/plugins/tree-ring-memory/.claude-plugin/plugin.json
    index 2c396f6..870392c 100644
    --- a/plugins/tree-ring-memory/.claude-plugin/plugin.json
    +++ b/plugins/tree-ring-memory/.claude-plugin/plugin.json
    @@ -1,8 +1,8 @@
     {
       "name": "tree-ring-memory",
       "displayName": "Tree Ring Memory",
    -  "version": "0.3.1",
    -  "description": "Local-first memory lifecycle and receipt-backed harness guidance for Claude Code using Tree Ring Memory v0.14+.",
    +  "version": "0.3.2",
    +  "description": "Local-first memory lifecycle, project bootstrap, and receipt-backed harness guidance for Claude Code using Tree Ring Memory v0.15+.",
       "author": {
         "name": "TerminallyLazy",
         "url": "https://github.com/TerminallyLazy"
    diff --git a/plugins/tree-ring-memory/.codex-plugin/plugin.json b/plugins/tree-ring-memory/.codex-plugin/plugin.json
    index 23e548a..25141b5 100644
    --- a/plugins/tree-ring-memory/.codex-plugin/plugin.json
    +++ b/plugins/tree-ring-memory/.codex-plugin/plugin.json
    @@ -1,7 +1,7 @@
     {
       "name": "tree-ring-memory",
    -  "version": "0.3.2",
    -  "description": "Local-first memory lifecycle and receipt-backed harness guidance for coding agents using Tree Ring Memory v0.14+.",
    +  "version": "0.3.3",
    +  "description": "Local-first memory lifecycle, project bootstrap, and receipt-backed harness guidance for coding agents using Tree Ring Memory v0.15+.",
       "author": {
         "name": "TerminallyLazy",
         "url": "https://github.com/TerminallyLazy"
    @@ -25,7 +25,7 @@
       "interface": {
         "displayName": "Tree Ring Memory",
         "shortDescription": "Local-first memory lifecycle guidance for Codex agents.",
    -    "longDescription": "Tree Ring Memory gives coding agents a lifecycle-aware practice for project recall, durable decisions, receipt-backed harness readiness, same-host fan-out/fan-in, idempotent worker writes, coordinator-authorized shared publication, explicit forgetting, and privacy-safe memory capture using Tree Ring Memory v0.14 or newer.",
    +    "longDescription": "Tree Ring Memory gives coding agents a lifecycle-aware practice for verified project-local setup, project recall, durable decisions, receipt-backed harness readiness, same-host fan-out/fan-in, idempotent worker writes, coordinator-authorized shared publication, explicit forgetting, privacy-safe memory capture, and scope-preserving CLI updates using Tree Ring Memory v0.15 or newer.",
         "developerName": "TerminallyLazy",
         "category": "Developer Tools",
         "capabilities": [
    diff --git a/plugins/tree-ring-memory/README.md b/plugins/tree-ring-memory/README.md
    index 0fa47db..b07930f 100644
    --- a/plugins/tree-ring-memory/README.md
    +++ b/plugins/tree-ring-memory/README.md
    @@ -4,28 +4,40 @@ This directory is the repository-distributed Tree Ring Memory plugin for
     ChatGPT/Codex and Claude Code. It packages instruction files only; the local
     Tree Ring Memory CLI remains the runtime and data owner.
     
    -The Codex manifest is version `0.3.2`. The Claude Code manifest is version
    -`0.3.1`. Both target Tree Ring Memory CLI `0.14.0` or newer and share the same
    +The Codex manifest is version `0.3.3`. The Claude Code manifest is version
    +`0.3.2`. Both target Tree Ring Memory CLI `0.15.0` or newer and share the same
     reviewed wrapper skill.
     
     The package does not run a background service, scrape chats, install hooks, or
     ship an MCP server. The active agent decides when a local, source-linked,
     privacy-safe memory action is warranted.
     
    -## Install Tree Ring Memory
    +## Install Or Update Tree Ring Memory
     
    -On macOS ARM64:
    +The safe default is a verified project-local install. From the actual project
    +root, after the user has authorized Tree Ring setup:
     
     ```bash
    -brew tap TerminallyLazy/tree-ring
    -brew install tree-ring
    -tree-ring --version
    +curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh -s -- --project --init --release latest --no-animation
    +.tree-ring/bin/tree-ring --root .tree-ring integrations status --verbose
     ```
     
    -For other platforms, use the
    -[canonical installation guide](https://github.com/TerminallyLazy/Tree-Ring-Memory#install).
    -The plugin must not install or upgrade the CLI, edit shell configuration, or
    -claim a memory action ran without explicit permission and observed output.
    +The installer downloads the official platform release and verifies its SHA-256
    +asset. A global Homebrew install remains available on macOS ARM64 through
    +`brew install tree-ring`. Agents may proceed when the user's request already
    +authorizes setup; otherwise they must explain the operation and obtain
    +permission before downloading or installing software. They must never
    +initialize inside the plugin cache, change global scope, edit shell
    +configuration, or claim a memory action ran without the required authorization
    +and observed output.
    +
    +Use `tree-ring update --check` to check without changing files. With update
    +authorization, `tree-ring update` preserves the active project-local, direct,
    +or Homebrew install scope. Afterward, rerun `tree-ring --root .tree-ring init`
    +from each project root so managed guidance is refreshed without overwriting
    +custom files. See the
    +[canonical installation guide](https://github.com/TerminallyLazy/Tree-Ring-Memory#install)
    +for older CLIs and additional platforms.
     
     ## Install In ChatGPT And Codex
     
    @@ -61,6 +73,7 @@ The package adds the `tree-ring-memory` skill and these commands:
     - `/tree-ring-memory:tree-ring-status`
     - `/tree-ring-memory:tree-ring-dox-sync`
     - `/tree-ring-memory:tree-ring-certify`
    +- `/tree-ring-memory:tree-ring-update`
     
     ## DOX And Certification Boundaries
     
    diff --git a/plugins/tree-ring-memory/commands/tree-ring-audit.md b/plugins/tree-ring-memory/commands/tree-ring-audit.md
    index 7fb3b61..7bc5f7f 100644
    --- a/plugins/tree-ring-memory/commands/tree-ring-audit.md
    +++ b/plugins/tree-ring-memory/commands/tree-ring-audit.md
    @@ -10,9 +10,10 @@ Audit memory when work is closing, when privacy may matter, or when older
     entries may be stale.
     
     Read project-local `.tree-ring/SKILL.md` and `.tree-ring/CLI.md` first when
    -present. Confirm `tree-ring --version` reports 0.14.0 or newer. If the runtime
    -is missing or older, stop and explain the limitation; do not install, upgrade,
    -or invent results without explicit user permission.
    +present. Follow the skill's Runtime Bootstrap And Updates procedure and confirm
    +the selected project-local or global binary reports 0.15.0 or newer. Bootstrap
    +or upgrade when the user's request already authorizes it; otherwise obtain
    +permission before downloading or changing software. Never invent results.
     
     Before any current command other than `policy status` or `policy audit` opens
     an existing pre-v0.13 store, stop every Tree Ring process, checkpoint and back
    diff --git a/plugins/tree-ring-memory/commands/tree-ring-capture.md b/plugins/tree-ring-memory/commands/tree-ring-capture.md
    index 759d776..ae8a291 100644
    --- a/plugins/tree-ring-memory/commands/tree-ring-capture.md
    +++ b/plugins/tree-ring-memory/commands/tree-ring-capture.md
    @@ -10,9 +10,11 @@ Capture only durable, useful memory. Do not store transcripts, secrets,
     credentials, raw chain-of-thought, or unverified claims as truth.
     
     Read project-local `.tree-ring/SKILL.md` and `.tree-ring/CLI.md` first when
    -present. Confirm `tree-ring --version` reports 0.14.0 or newer. If the runtime
    -is missing or older, stop and explain the limitation; do not install, upgrade,
    -or invent a stored memory without explicit user permission.
    +present. Follow the skill's Runtime Bootstrap And Updates procedure and confirm
    +the selected project-local or global binary reports 0.15.0 or newer. Bootstrap
    +or upgrade when the user's request already authorizes it; otherwise obtain
    +permission before downloading or changing software. Never invent a stored
    +memory.
     
     For a single agent or a store in Open mode, use the user's argument as the
     memory summary:
    diff --git a/plugins/tree-ring-memory/commands/tree-ring-certify.md b/plugins/tree-ring-memory/commands/tree-ring-certify.md
    index d58fa73..4eb9faf 100644
    --- a/plugins/tree-ring-memory/commands/tree-ring-certify.md
    +++ b/plugins/tree-ring-memory/commands/tree-ring-certify.md
    @@ -7,9 +7,10 @@ allowed-tools: ["Bash", "Read"]
     # Tree Ring Certify
     
     Read project-local `.tree-ring/SKILL.md` and `.tree-ring/CLI.md` when present,
    -then confirm `tree-ring --version` reports 0.14.0 or newer. If the runtime is
    -missing or older, explain the limitation and do not install or upgrade it,
    -edit shell configuration, or invent certification results.
    +then follow the skill's Runtime Bootstrap And Updates procedure and confirm the
    +selected binary reports 0.15.0 or newer. Bootstrap or upgrade when the user's
    +request already authorizes it; otherwise obtain permission before downloading
    +or changing software. Never invent certification results.
     
     For an installed runtime, choose the requested self-contained evidence path:
     
    diff --git a/plugins/tree-ring-memory/commands/tree-ring-dox-sync.md b/plugins/tree-ring-memory/commands/tree-ring-dox-sync.md
    index 865d42d..7177832 100644
    --- a/plugins/tree-ring-memory/commands/tree-ring-dox-sync.md
    +++ b/plugins/tree-ring-memory/commands/tree-ring-dox-sync.md
    @@ -13,9 +13,10 @@ the current project root when no argument is supplied.
        directory. Current source contracts are authoritative; a memory summary
        never overrides them.
     2. Read project-local `.tree-ring/SKILL.md` and `.tree-ring/CLI.md` when present,
    -   then confirm `tree-ring --version` reports 0.14.0 or newer. If the runtime is
    -   missing or older, explain the limitation and do not install or upgrade it,
    -   edit shell configuration, or invent adapter results.
    +   then follow the skill's Runtime Bootstrap And Updates procedure and confirm
    +   the selected binary reports 0.15.0 or newer. Bootstrap or upgrade when the
    +   user's request already authorizes it; otherwise obtain permission before
    +   downloading or changing software. Never invent adapter results.
     3. Preview without writing:
     
        ```bash
    diff --git a/plugins/tree-ring-memory/commands/tree-ring-recall.md b/plugins/tree-ring-memory/commands/tree-ring-recall.md
    index 2eae9c6..816302b 100644
    --- a/plugins/tree-ring-memory/commands/tree-ring-recall.md
    +++ b/plugins/tree-ring-memory/commands/tree-ring-recall.md
    @@ -15,9 +15,10 @@ Recall useful project memory before acting on context-dependent work.
        test -f .tree-ring/CLI.md && sed -n '1,520p' .tree-ring/CLI.md
        ```
     
    -   Confirm `tree-ring --version` reports 0.14.0 or newer. If the runtime is
    -   missing or older, explain the limitation and do not fabricate recall or
    -   install software without explicit user permission.
    +   Follow the skill's Runtime Bootstrap And Updates procedure and confirm the
    +   selected binary reports 0.15.0 or newer. Bootstrap or upgrade when the
    +   user's request already authorizes it; otherwise obtain permission before
    +   downloading or changing software. Never fabricate recall.
     
     2. Use the user's argument as the focused recall query when present:
     
    diff --git a/plugins/tree-ring-memory/commands/tree-ring-status.md b/plugins/tree-ring-memory/commands/tree-ring-status.md
    index 4e25308..e3f8a0b 100644
    --- a/plugins/tree-ring-memory/commands/tree-ring-status.md
    +++ b/plugins/tree-ring-memory/commands/tree-ring-status.md
    @@ -6,15 +6,16 @@ allowed-tools: ["Bash", "Read"]
     # Tree Ring Status
     
     Read project-local `.tree-ring/SKILL.md` and `.tree-ring/CLI.md` when present,
    -then confirm the local runtime is Tree Ring Memory 0.14.0 or newer:
    +then follow the skill's Runtime Bootstrap And Updates procedure and confirm the
    +selected project-local or global runtime is Tree Ring Memory 0.15.0 or newer:
     
     ```bash
     tree-ring --version
     ```
     
    -If the runtime is missing or older, explain the limitation. Do not install or
    -upgrade software, edit shell configuration, or invent a status without the
    -user's explicit permission.
    +Bootstrap or upgrade when the user's request already authorizes it; otherwise
    +obtain permission before downloading or changing software. Never invent a
    +status.
     
     Inspect receipt-backed readiness without changing it:
     
    diff --git a/plugins/tree-ring-memory/commands/tree-ring-update.md b/plugins/tree-ring-memory/commands/tree-ring-update.md
    new file mode 100644
    index 0000000..bb9053f
    --- /dev/null
    +++ b/plugins/tree-ring-memory/commands/tree-ring-update.md
    @@ -0,0 +1,45 @@
    +---
    +description: Check for or install a verified Tree Ring Memory CLI update without changing installation scope
    +argument-hint: "[--check]"
    +allowed-tools: ["Bash", "Read"]
    +---
    +
    +# Tree Ring Update
    +
    +Resolve the real project root and read project-local `.tree-ring/SKILL.md` and
    +`.tree-ring/CLI.md` when present. Prefer `.tree-ring/bin/tree-ring` for that
    +project when it exists; otherwise resolve the active global binary with
    +`command -v tree-ring`. Use `which -a tree-ring` to detect older shadowing
    +copies.
    +
    +For a read-only release check, run the selected binary:
    +
    +```bash
    +tree-ring update --check
    +```
    +
    +If the user asked only to check, report the installed version, available
    +version, executable path, and install method without changing files. If the
    +user already authorized an update, run `tree-ring update`; otherwise obtain
    +permission immediately before the update. The updater must preserve the active
    +project-local, direct-prefix, or Homebrew scope and verify official release
    +assets. Never install a second copy merely to bypass an older active binary.
    +
    +CLIs older than 0.15.0 lack the update command. Upgrade with the same manager or
    +prefix: `brew upgrade tree-ring` for Homebrew, the official installer with
    +`--project --release latest` for an existing project-local binary, or
    +`--install-dir  --release latest` for another direct install.
    +Do not change global scope or edit shell startup files without separate user
    +authorization.
    +
    +After updating, verify the selected binary with `--version`, return to the
    +actual project root, and run:
    +
    +```bash
    +tree-ring --root .tree-ring init
    +tree-ring --root .tree-ring integrations status --verbose
    +```
    +
    +Use `.tree-ring/bin/tree-ring` for both commands when the project has a local
    +binary. Confirm the files remain under the intended project's `.tree-ring/`.
    +Initialization refreshes managed guidance but does not prove harness activation.
    diff --git a/plugins/tree-ring-memory/skills/tree-ring-memory/SKILL.md b/plugins/tree-ring-memory/skills/tree-ring-memory/SKILL.md
    index 0da8d29..f59704d 100644
    --- a/plugins/tree-ring-memory/skills/tree-ring-memory/SKILL.md
    +++ b/plugins/tree-ring-memory/skills/tree-ring-memory/SKILL.md
    @@ -1,21 +1,11 @@
     ---
     name: tree-ring-memory
     description: Guides AI agents in using Tree Ring Memory for durable recall, project decisions, user preferences, warnings, future seeds, privacy-safe memory capture, and lifecycle-aware forgetting.
    -version: 0.14.0
     license: MIT
    -tags: ["memory", "agents", "recall", "privacy", "projects", "dox", "revolve", "skills", "cli"]
    -triggers:
    -  - "remember this"
    -  - "recall what we decided"
    -  - "what did we learn"
    -  - "tree ring memory"
    -  - "consolidate memory"
    -  - "forget this"
    -  - "project memory"
    -  - "sync DOX"
    -  - "sync Revolve"
    -  - "evidence loop"
    -  - "multi-agent memory"
    +metadata:
    +  version: "0.15.0"
    +  tags: "memory, agents, recall, privacy, projects, dox, revolve, skills, cli"
    +  triggers: "remember this; recall what we decided; what did we learn; tree ring memory; consolidate memory; forget this; project memory; sync DOX; sync Revolve; evidence loop; multi-agent memory"
     ---
     
     # Tree Ring Memory
    @@ -31,23 +21,51 @@ Tree Ring Memory preserves meaningful agent learning like tree rings:
     - speculative future work stays as seeds
     - sensitive data is blocked, redacted, or kept out by default
     
    -## Runtime Preflight
    +## Runtime Bootstrap And Updates
     
    -Before running a Tree Ring command:
    +Resolve the actual project root before running Tree Ring. Never initialize a
    +plugin cache, downloaded package directory, home directory, or arbitrary
    +working directory by accident.
     
    -1. Read project-local `.tree-ring/SKILL.md` and `.tree-ring/CLI.md` when they
    -   exist. They describe the configured root and exact installed commands.
    -2. Confirm that the local runtime is available:
    +1. If `/.tree-ring/bin/tree-ring` exists, prefer that binary for
    +   this project. Otherwise check `command -v tree-ring` and run
    +   `tree-ring --version`.
    +2. Read existing `/.tree-ring/SKILL.md` and `CLI.md` when present.
    +3. This package targets Tree Ring Memory CLI 0.15.0 or newer. If no compatible
    +   CLI is available and the user's request already authorizes Tree Ring setup,
    +   install the verified current release project-locally from the project root.
    +   Otherwise explain the exact operation and obtain permission before the
    +   network download or software installation:
     
        ```bash
    -   tree-ring --version
    +   cd 
    +   curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh -s -- --project --init --release latest --no-animation
        ```
     
    -3. This package targets Tree Ring Memory CLI 0.14.0 or newer. If the command is
    -   missing or older, do not invent results, edit shell configuration, or install
    -   or upgrade software without the user's explicit permission. Explain the
    -   limitation and point to the canonical install guide:
    -   
    +4. For an existing global CLI, initialize from the project root with
    +   `tree-ring --root .tree-ring init`. For a project-local CLI, use
    +   `.tree-ring/bin/tree-ring --root .tree-ring init`. This must place
    +   `memory.sqlite`, `AGENTS.md`, `SKILL.md`, and `CLI.md` under that project's
    +   `.tree-ring/` directory.
    +5. Verify the created paths and run the same binary with
    +   `--root .tree-ring integrations status`. Initialization creates safe local
    +   guidance and bridge material; it is not receipt-backed activation proof.
    +
    +Check for releases without changing files with `tree-ring update --check`. Run
    +`tree-ring update` only when the user has authorized an update. It updates the
    +active binary in its existing project-local, direct, or Homebrew-managed scope,
    +verifies official release assets, and must not create a second shadowing binary.
    +After an update, return to each project root and rerun `tree-ring --root
    +.tree-ring init` (or the project-local equivalent) to backfill managed guidance
    +without replacing custom files.
    +
    +CLIs older than 0.15.0 do not have `tree-ring update`. Upgrade those with the
    +same manager or prefix that installed them: `brew upgrade tree-ring` for
    +Homebrew, `--project --release latest` for an existing project-local install,
    +or `--install-dir  --release latest` for another direct
    +install. Check `command -v tree-ring` and `which -a tree-ring` afterward. Do not
    +edit a shell profile or change global installation scope without separate user
    +authorization.
     
     If the current host cannot execute a local shell or access project files, use
     this skill only as memory-lifecycle guidance. Do not claim that recall, capture,
    diff --git a/scripts/validate-plugin-packages.py b/scripts/validate-plugin-packages.py
    index 2965513..e959546 100644
    --- a/scripts/validate-plugin-packages.py
    +++ b/scripts/validate-plugin-packages.py
    @@ -60,7 +60,7 @@ def validate_codex() -> None:
     
         manifest = load_json(PLUGIN / ".codex-plugin" / "plugin.json")
         require(manifest.get("name") == "tree-ring-memory", "Codex manifest name is stale")
    -    require(manifest.get("version") == "0.3.2", "Codex manifest version is stale")
    +    require(manifest.get("version") == "0.3.3", "Codex manifest version is stale")
         require(manifest.get("skills") == "./skills/", "Codex skills path is stale")
         for unsupported in ("mcpServers", "apps", "hooks"):
             require(unsupported not in manifest, f"skills-only Codex plugin must not declare {unsupported}")
    @@ -76,7 +76,7 @@ def validate_codex() -> None:
     def validate_claude() -> None:
         marketplace = load_json(ROOT / ".claude-plugin" / "marketplace.json")
         require(marketplace.get("name") == "tree-ring-memory", "Claude marketplace name is stale")
    -    require(marketplace.get("version") == "0.3.1", "Claude marketplace version is stale")
    +    require(marketplace.get("version") == "0.3.2", "Claude marketplace version is stale")
         require(isinstance(marketplace.get("owner"), dict), "Claude marketplace owner is required")
         entries = marketplace.get("plugins")
         require(isinstance(entries, list) and len(entries) == 1, "Claude marketplace must contain one plugin")
    @@ -97,6 +97,7 @@ def validate_claude() -> None:
             "tree-ring-dox-sync.md",
             "tree-ring-recall.md",
             "tree-ring-status.md",
    +        "tree-ring-update.md",
         }
         actual_commands = {path.name for path in (PLUGIN / "commands").glob("*.md")}
         require(actual_commands == expected_commands, "Claude command package is incomplete")
    @@ -107,8 +108,11 @@ def validate_shared_contract() -> None:
         require_markers(
             skill,
             [
    -            "Runtime Preflight",
    -            "0.14.0 or newer",
    +            "Runtime Bootstrap And Updates",
    +            "0.15.0 or newer",
    +            "--project --init --release latest --no-animation",
    +            "tree-ring update --check",
    +            "which -a tree-ring",
                 "DOX Contract Flow",
                 "tree-ring dox sync --source-root  --dry-run",
                 "Certification Boundary",
    @@ -132,6 +136,15 @@ def validate_shared_contract() -> None:
             PLUGIN / "commands" / "tree-ring-certify.md",
             ["tree-ring integrations certify", "tree-ring recall-quality", "repository-only", "does not execute the suite"],
         )
    +    require_markers(
    +        PLUGIN / "commands" / "tree-ring-update.md",
    +        [
    +            "tree-ring update --check",
    +            "preserve the active",
    +            "CLIs older than 0.15.0",
    +            "--root .tree-ring init",
    +        ],
    +    )
         require((ROOT / "scripts" / "certify-tree-ring.sh").is_file(), "source certification script is missing")
         require(not (PLUGIN / "scripts" / "certify-tree-ring.sh").exists(), "source certification suite must not be bundled")
         for filename in ("LICENSE", "PRIVACY.md", "SECURITY.md", "TERMS.md", "README.md"):
    diff --git a/skills/tree-ring-memory/SKILL.md b/skills/tree-ring-memory/SKILL.md
    index 9d49bae..98c8ed6 100644
    --- a/skills/tree-ring-memory/SKILL.md
    +++ b/skills/tree-ring-memory/SKILL.md
    @@ -1,20 +1,11 @@
     ---
     name: tree-ring-memory
     description: Guides AI agents in using Tree Ring Memory for durable recall, project decisions, user preferences, warnings, future seeds, privacy-safe memory capture, and lifecycle-aware forgetting.
    -version: 0.14.0
    -tags: ["memory", "agents", "recall", "privacy", "projects", "dox", "revolve", "skills", "cli"]
    -triggers:
    -  - "remember this"
    -  - "recall what we decided"
    -  - "what did we learn"
    -  - "tree ring memory"
    -  - "consolidate memory"
    -  - "forget this"
    -  - "project memory"
    -  - "sync DOX"
    -  - "sync Revolve"
    -  - "evidence loop"
    -  - "multi-agent memory"
    +license: MIT
    +metadata:
    +  version: "0.15.0"
    +  tags: "memory, agents, recall, privacy, projects, dox, revolve, skills, cli"
    +  triggers: "remember this; recall what we decided; what did we learn; tree ring memory; consolidate memory; forget this; project memory; sync DOX; sync Revolve; evidence loop; multi-agent memory"
     ---
     
     # Tree Ring Memory
    @@ -30,6 +21,57 @@ Tree Ring Memory preserves meaningful agent learning like tree rings:
     - speculative future work stays as seeds
     - sensitive data is blocked, redacted, or kept out by default
     
    +## Runtime Bootstrap And Updates
    +
    +Resolve the actual project root before running Tree Ring. Never initialize a
    +plugin cache, downloaded package directory, home directory, or arbitrary
    +working directory by accident.
    +
    +1. If `/.tree-ring/bin/tree-ring` exists, prefer that binary for
    +   this project. Otherwise check `command -v tree-ring` and run
    +   `tree-ring --version`.
    +2. Read existing `/.tree-ring/SKILL.md` and `CLI.md` when present.
    +3. If no CLI is available and the user's request already authorizes Tree Ring
    +   setup, install the verified current release project-locally from the project
    +   root. Otherwise explain the exact operation and obtain permission before the
    +   network download or software installation:
    +
    +   ```bash
    +   cd 
    +   curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh -s -- --project --init --release latest --no-animation
    +   ```
    +
    +4. For an existing global CLI, initialize from the project root with
    +   `tree-ring --root .tree-ring init`. For a project-local CLI, use
    +   `.tree-ring/bin/tree-ring --root .tree-ring init`. This must place
    +   `memory.sqlite`, `AGENTS.md`, `SKILL.md`, and `CLI.md` under that project's
    +   `.tree-ring/` directory.
    +5. Verify the created paths and run the same binary with
    +   `--root .tree-ring integrations status`. Initialization creates safe local
    +   guidance and bridge material; it is not receipt-backed activation proof.
    +
    +Check for releases without changing files:
    +
    +```bash
    +tree-ring update --check
    +```
    +
    +Run `tree-ring update` only when the user has authorized an update. It updates
    +the active binary in its existing project-local, direct, or Homebrew-managed
    +scope, verifies official release assets, and must not create a second binary
    +that shadows the active one. After an update, return to each project root and
    +run `tree-ring --root .tree-ring init` (or the project-local equivalent) to
    +backfill managed agent guidance without replacing custom files.
    +
    +CLIs older than 0.15.0 do not have `tree-ring update`. Upgrade those with the
    +same manager or prefix that installed them: `brew upgrade tree-ring` for
    +Homebrew, `--project --release latest` for an existing project-local install,
    +or `--install-dir  --release latest` for another direct
    +install. Check `command -v tree-ring` and `which -a tree-ring` afterward so an
    +older shadowing binary is not mistaken for the updated runtime. Do not edit a
    +shell profile or change global installation scope without separate user
    +authorization.
    +
     ## When To Recall
     
     Recall memory before:
    diff --git a/templates/dox/AGENTS.md b/templates/dox/AGENTS.md
    index df31adb..d98910a 100644
    --- a/templates/dox/AGENTS.md
    +++ b/templates/dox/AGENTS.md
    @@ -23,6 +23,32 @@ generated guidance instead of duplicating memory data. Prefer project-level
     bridges for the current repo. Treat global Tree Ring bridges as explicit user
     configuration that affects every project.
     
    +## Runtime Bootstrap And Updates
    +
    +Resolve the real project root before setup. Never initialize Tree Ring inside a
    +plugin cache, package directory, home directory, or incidental working
    +directory. Prefer `/.tree-ring/bin/tree-ring` when it exists;
    +otherwise use the active `tree-ring` on `PATH`.
    +
    +When no CLI exists and the user's request already authorizes setup, run the
    +official verified-release installer from the project root:
    +
    +```bash
    +curl -fsSL https://raw.githubusercontent.com/TerminallyLazy/Tree-Ring-Memory/main/install.sh | sh -s -- --project --init --release latest --no-animation
    +```
    +
    +Otherwise obtain permission before downloading or installing software. A
    +global CLI initializes with `tree-ring --root .tree-ring init`; a project-local
    +CLI initializes with `.tree-ring/bin/tree-ring --root .tree-ring init`. Verify
    +that the generated files and `memory.sqlite` are under the intended project's
    +`.tree-ring/`, then run `integrations status` with the same binary and root.
    +
    +Use `tree-ring update --check` for a read-only release check and, only after
    +update authorization, `tree-ring update`. Preserve the current manager and
    +install prefix; never create a second shadowing binary. After updating, rerun
    +`init` in each project to safely backfill managed guidance. For a pre-0.15 CLI,
    +use the same installer scope or package manager that originally installed it.
    +
     ## Recall Rules
     
     Before substantial work, recall project-scoped memory for: