diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 175cbef..8174d05 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -5,7 +5,7 @@ "url": "https://github.com/TerminallyLazy" }, "description": "Claude Code marketplace for Tree Ring Memory v0.15 verified bootstrap, local-first recall, and receipt-backed harness readiness.", - "version": "0.3.2", + "version": "0.3.4", "plugins": [ { "name": "tree-ring-memory", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 3974127..8aba23e 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "tree-ring-memory", "displayName": "Tree Ring Memory", - "version": "0.3.2", + "version": "0.3.4", "description": "Local-first memory lifecycle, project bootstrap, and receipt-backed harness guidance for Claude Code using Tree Ring Memory v0.15+.", "author": { "name": "TerminallyLazy", @@ -22,5 +22,6 @@ "sqlite", "skills" ], - "skills": "./skills/" + "skills": "./skills/", + "hooks": "./hooks/claude-hooks.json" } diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 8ee9534..dbba0b6 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -10,8 +10,8 @@ permissions: contents: read env: - TREE_RING_VERSION: "0.15.0" - TREE_RING_LINUX_X86_64_SHA256: "9b47873268dbb94712a49b02bd785cc69507facee1e879e46e6922778b4afbe6" + TREE_RING_VERSION: "0.15.7" + TREE_RING_LINUX_X86_64_SHA256: "5c9da887e212b1a7222205d2a661910276171a927d7677f09ec097f2cddd26b7" jobs: validate: @@ -56,7 +56,9 @@ jobs: printf 'TREE_RING_BIN=%s\n' "${binary_path}" >> "${GITHUB_ENV}" - name: Validate plugin guidance - run: python3 scripts/validate.py + run: | + python3 scripts/validate.py + python3 scripts/validate-lifecycle.py - name: Validate with Claude Code 2.1.241 run: | diff --git a/PRIVACY.md b/PRIVACY.md index 6fe6cda..20adcca 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -2,22 +2,40 @@ Effective August 23, 2026 -The Tree Ring Memory Claude Code plugin is an instruction package. It does not -operate a hosted service, create a user account, collect analytics, send -telemetry, install hooks, or include a remote MCP server. +The Tree Ring Memory repository plugin packages instructions and local +lifecycle-hook registrations for AI coding agents. It does not operate a hosted +service, create a user account, collect analytics, send telemetry, or include a +remote MCP server. -When Claude Code runs the separately installed Tree Ring Memory CLI, the CLI -stores the memory content the user chooses in a local SQLite database under the -configured Tree Ring root. The project does not receive that database or its -contents. Data leaves the local environment only when the user or another tool -explicitly exports, syncs, publishes, or otherwise transmits it. +The repository hooks run only when a session or subagent starts or stops. They +forward the host's lifecycle JSON through standard input to the separately +installed Tree Ring Memory CLI and wait synchronously for at most 10 seconds. +They do not register for user prompts, tool calls, or `SessionEnd`; persist hook +input; capture prompts or transcripts; or run in the background. The lifecycle +parser never inspects or persists `transcript_path`, `last_assistant_message`, +prompts, or transcript content. + +Each stop event enforces one agent-mediated memory checkpoint. It asks the +active agent to evaluate already-grounded work rather than deriving a summary +from hook input. Only a concise, durable candidate classified as normal +sensitivity may be written automatically with strict `tree-ring capture`. +That command fixes agent scope, requires identity and provenance, tags the +memory as automatic capture, and rejects sensitive content. If no candidate +passes, no durable memory is created. + +When an agent runs an explicit command in the separately installed Tree Ring +Memory CLI, including an identity-bound strict capture approved by the +checkpoint gates, the CLI stores accepted memory content in a local SQLite +database under the configured Tree Ring root. The project does not receive that +database or its contents. Data leaves the local environment only when the user +or another tool explicitly exports, syncs, publishes, or otherwise transmits it. The plugin instructs agents to avoid transcripts, credentials, secrets, private keys, raw chain-of-thought, and unnecessary sensitive personal data. It also provides explicit redaction, deletion, supersession, audit, and consolidation -workflows. These safeguards do not replace the privacy and data-use terms of -Claude Code, the operating system, source-control provider, or another tool the -user chooses to invoke. +workflows. These safeguards do not replace the privacy and data-use terms of the +AI host, operating system, source-control provider, or any other tool the user +chooses to invoke. Support and privacy questions may be filed at . diff --git a/README.md b/README.md index c62a036..d4d5980 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,10 @@ that teach agents when to recall, write, audit, consolidate, and forget project memory using the open-source [Tree Ring Memory](https://github.com/TerminallyLazy/Tree-Ring-Memory) CLI. -It does not run a background service, scrape chats, capture transcripts, install -hooks, or ship a networked MCP server. The active agent chooses when a memory -action is useful, source-linked, and privacy-safe. +The plugin registers native startup recall and bounded stop checkpoints. +Automatic lifecycle hooks require CLI `0.15.6` or newer. It does not run a +background service, scrape chats, capture transcripts, or ship a networked MCP +server. The active agent evaluates durable outcomes before any capture. ## What It Adds @@ -153,3 +154,60 @@ memory, logs, source references, and ordinary worker environments. See [PRIVACY.md](PRIVACY.md), [TERMS.md](TERMS.md), and [SECURITY.md](SECURITY.md) for data handling, use terms, and disclosures. + +## Automatic Lifecycle Hooks + +The repository plugin registers exactly `SessionStart`, `SubagentStart`, +`Stop`, and `SubagentStop`. Each hook forwards its event JSON directly to the +local CLI and waits synchronously for at most 10 seconds. It does not register +prompt, tool, compaction, or `SessionEnd` hooks; run a background service; +scrape chats; or ship an MCP server. + +Session start covers startup, resume, and compaction rehydration when the host +reports those sources. Subagent start gives each worker an independent, +receipt-backed preflight. Codex requires review and trust of the installed hook +definition before it runs. Claude Code loads the hook with the enabled plugin. + +Startup recall loads a bounded brief of shared project guidance and this +agent's durable memories, including captures from earlier sessions. Workflow +and session memories remain limited to their matching scope. It does not +depend on memories containing special startup keywords. Use targeted recall +when the task changes; the startup brief is not an exhaustive search. + +Stop and subagent-stop enforce one agent-mediated memory checkpoint. The +lifecycle parser uses only stable harness identity and project fields; it never +inspects or persists `transcript_path`, `last_assistant_message`, prompts, or +transcript content. The checkpoint asks the active agent to evaluate its +already-grounded work. If and only if that evaluation yields a concise, +durable, normal-sensitivity candidate, the agent automatically runs the exact +strict `tree-ring capture` command template returned by the lifecycle handler. +Strict capture fixes agent scope, requires identity and provenance, adds an +automatic-capture tag, and rejects sensitive candidates. No candidate means no +memory write. This is one bounded checkpoint, not a recorder or automatic +summary of every turn. + +The hook wrapper resolves the Git project root when available, prefers that +project's `.tree-ring/bin/tree-ring`, and otherwise uses `tree-ring` from +`PATH`. It then invokes the shared lifecycle entry point with the project-local +`.tree-ring` root. An unavailable or incompatible CLI is not active-harness +proof and cannot be reported as a successful checkpoint or capture. + +When project activation has already installed the managed lifecycle definition +in `.codex/hooks.json` or `.claude/settings.json`, that project definition owns +recall and stop checkpoints. The marketplace wrapper detects the exact managed +marker and exits without invoking the CLI, preventing duplicate context, +receipts, checkpoint continuations, or capture attempts when the host merges +project and plugin hooks. + +`integrations status --verbose` reports the last validated recall's result +count and query class. A zero-result receipt proves the check ran; it does not +prove that useful context was found. A skills-only plugin installation has no +automatic lifecycle hooks; enable the repository plugin or configure the +project with the CLI to obtain them. A newly configured Codex hook still needs +the host's trust flow and a new session before automatic execution can be +verified. + +Installer onboarding requires CLI 0.15.7 or newer to create the activation +manifest and native project hooks in the same install action. On earlier 0.15 +CLIs, run `tree-ring init` explicitly after the installer. Onboarding readiness +is configuration; a fresh host receipt is required to prove automatic recall. diff --git a/SECURITY.md b/SECURITY.md index d352259..96c475c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,26 +1,48 @@ # Security Policy -Tree Ring Memory Claude Code Plugin is an instructions-only wrapper around the -open-source Tree Ring Memory CLI. +## Supported Versions -## What This Plugin Does Not Do +The `main` branch is the supported version of this agent plugin package. -- It does not run background services. -- It does not define Claude Code hooks. -- It does not ship a networked MCP server. -- It does not collect analytics. -- It does not ask for credentials, tokens, or API keys. -- It does not store raw chat transcripts. +The Tree Ring Memory framework and CLI are maintained in the canonical +repository: -## Privacy Guidance + -Only store concise, useful memory. Do not store secrets, credentials, tokens, -private keys, raw chain-of-thought, temporary scratchpad notes, unverified claims -as durable truth, or private health, financial, legal, or personal identifier -details without explicit user instruction. +## Reporting A Vulnerability + +Report vulnerabilities privately through the canonical repository's GitHub +security advisory form: + + + +Use the canonical issue tracker only for non-sensitive support: + + + +Never include vulnerability details, secrets, tokens, private memory contents, +or personal data in a public issue. + +## Data Handling + +This wrapper plugin contains guidance files and bounded local lifecycle-hook +registrations. It does not run a background service, include remote MCP +servers, collect telemetry, or store credentials. The hooks run only at +`SessionStart`, `SubagentStart`, `Stop`, and `SubagentStop`, forward standard +input directly to the local Tree Ring CLI, and do not persist prompts, +transcripts, `last_assistant_message`, or hook input. They are synchronous, +bounded to 10 seconds, and never run as a `SessionEnd` or background recorder. -Use `tree-ring audit`, `tree-ring forget`, redaction, and supersession when -memory is wrong, sensitive, stale, or replaced by newer evidence. +Stop hooks enforce one agent-mediated checkpoint. They may supply an exact +strict `tree-ring capture` template only for concise durable candidates. Strict +capture fixes agent scope, requires harness identity and provenance, accepts +only normal sensitivity, and tags the result as automatic capture. A missing, +sensitive, ambiguous, or ungrounded candidate must not be stored. + +Tree Ring Memory is designed for explicit agent-mediated memory actions. Store +only concise decisions, lessons, warnings, and evidence references that are +useful, source-linked, and privacy-safe. Do not store raw transcripts, secrets, +private keys, tokens, or raw chain-of-thought. ## Coordinated Mode @@ -41,16 +63,3 @@ Tree Ring Memory v0.15 reports harness readiness from project-local configuration plus privacy-safe preflight receipts. Configuration alone is not activation. The plugin does not manufacture receipts, alter global trust, or hand-author Agent Zero activation capability descriptors. - -## Reporting A Vulnerability - -Open a private vulnerability report on the canonical framework repository: - - - -Use the canonical issue tracker only for non-sensitive support: - - - -Never include vulnerability details, secrets, tokens, private memory contents, -or personal data in a public issue. diff --git a/SUBMISSION.md b/SUBMISSION.md index df58728..d8179a6 100644 --- a/SUBMISSION.md +++ b/SUBMISSION.md @@ -5,7 +5,7 @@ - Name: Tree Ring Memory - Plugin identifier: `tree-ring-memory` - Source: -- Release: `v0.3.2` +- Release: `v0.3.4` - Category: Developer Tools / Workflow Orchestration - License: MIT - Homepage: @@ -24,7 +24,8 @@ Description: - One provider-neutral Tree Ring Memory skill. - Slash commands for recall, capture, read-only audit, and receipt-backed harness status. -- No hooks, remote MCP server, background service, analytics, credentials, or +- Native SessionStart, SubagentStart, Stop, and SubagentStop hooks; CLI >=0.15.6. +- No remote MCP server, background service, analytics, credentials, or installation-time scripts. ## Validation @@ -34,6 +35,7 @@ claude plugin validate . --strict claude plugin validate .claude-plugin/plugin.json --strict claude plugin validate .claude-plugin/marketplace.json --strict python3 scripts/validate.py +python3 scripts/validate-lifecycle.py bash scripts/smoke_v015.sh ``` diff --git a/TERMS.md b/TERMS.md index fc4c9f5..74e39b7 100644 --- a/TERMS.md +++ b/TERMS.md @@ -2,16 +2,16 @@ Effective August 23, 2026 -The Tree Ring Memory Claude Code plugin is open-source software distributed -under the MIT License. By using it, you agree to the license and these -operational terms. +The Tree Ring Memory plugin is open-source software distributed under the MIT +License. By using it, you agree to the license and these operational terms. -The plugin provides agent instructions only. It is not a hosted memory service, -backup service, access-control system, medical or legal record system, or -guarantee that an AI host will follow every instruction. The user is responsible -for choosing what to store, maintaining backups, controlling local filesystem -access, reviewing agent actions, and complying with applicable policies and -law. +The repository plugin provides agent instructions and local lifecycle-hook +registrations. The separate OpenAI upload artifact is skills-only. Neither is a +hosted memory service, backup service, access-control system, medical or legal +record system, or guarantee that an AI host will follow every instruction. The +user is responsible for choosing what to store, maintaining backups, controlling +local filesystem access, reviewing agent actions and hook trust, and complying +with applicable policies and law. Tree Ring Memory is provided without warranty, including warranties of fitness, availability, accuracy, non-infringement, or data durability, to the maximum diff --git a/commands/tree-ring-capture.md b/commands/tree-ring-capture.md index ae8a291..3ef3f44 100644 --- a/commands/tree-ring-capture.md +++ b/commands/tree-ring-capture.md @@ -9,6 +9,43 @@ allowed-tools: ["Bash"] Capture only durable, useful memory. Do not store transcripts, secrets, credentials, raw chain-of-thought, or unverified claims as truth. +## Lifecycle Stop Checkpoint + +`Stop` and `SubagentStop` hooks enforce one agent-mediated checkpoint. The hook +never inspects or persists `transcript_path`, `last_assistant_message`, prompts, +or transcript content. Evaluate the grounded work already in the active agent's +context. If there is no durable, normal-sensitivity candidate, do not write a +memory. + +For each qualifying candidate, use the exact identity-bound template returned +by the lifecycle handler. Its strict shape is: + +```bash +tree-ring --root .tree-ring capture "" \ + --event-type \ + --ring \ + --project \ + --agent-profile \ + --workflow-id \ + --session-id \ + --operation-id auto--<1..3> \ + --source-ref agent-checkpoint: \ + [--tag ] +``` + +Do not invent or edit the supplied identity, checkpoint, operation, or source +values. Strict capture fixes `scope=agent`, requires identity and provenance, +adds the automatic-capture tag, and accepts normal sensitivity only. Use no more +than three candidates in the single checkpoint. Never replace this with a raw +transcript summary, `remember`, `evidence`, or an import. + +Use `cambium` for preferences, decisions, lessons, and corrections; `scar` for +warnings; and `seed` for future work. A candidate still must be durable and +grounded regardless of its ring. + +The manual command flow below remains available for an explicit user-directed +capture outside a lifecycle checkpoint. + Read project-local `.tree-ring/SKILL.md` and `.tree-ring/CLI.md` first when 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 diff --git a/hooks/claude-hook.sh b/hooks/claude-hook.sh new file mode 100755 index 0000000..ee39783 --- /dev/null +++ b/hooks/claude-hook.sh @@ -0,0 +1,26 @@ +#!/bin/sh +set -eu + +if command -v git >/dev/null 2>&1; then + project_root=$(git rev-parse --show-toplevel 2>/dev/null || true) + if [ -n "$project_root" ]; then + cd "$project_root" + fi +fi + +# Project activation owns lifecycle recall and checkpoints when its managed hook +# is present. The marketplace hook stands down to prevent duplicate handling. +if [ -f .claude/settings.json ] && { + grep -Fq 'Tree Ring Memory managed lifecycle v2"' .claude/settings.json || + grep -Fq 'Tree Ring Memory managed lifecycle v3"' .claude/settings.json || + grep -Fq 'Tree Ring Memory managed lifecycle v4"' .claude/settings.json +}; then + exit 0 +fi + +tree_ring=tree-ring +if [ -x .tree-ring/bin/tree-ring ]; then + tree_ring=.tree-ring/bin/tree-ring +fi + +exec "$tree_ring" --root .tree-ring integrations hook --harness claude-code --input-json-stdin diff --git a/hooks/claude-hooks.json b/hooks/claude-hooks.json new file mode 100644 index 0000000..a491f84 --- /dev/null +++ b/hooks/claude-hooks.json @@ -0,0 +1,57 @@ +{ + "description": "Load bounded Tree Ring context at startup and enforce one agent-mediated memory checkpoint at stop.", + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/claude-hook.sh", + "args": [], + "timeout": 10, + "statusMessage": "Loading Tree Ring context" + } + ] + } + ], + "SubagentStart": [ + { + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/claude-hook.sh", + "args": [], + "timeout": 10, + "statusMessage": "Loading Tree Ring worker context" + } + ] + } + ], + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/claude-hook.sh", + "args": [], + "timeout": 10, + "statusMessage": "Checking Tree Ring memory" + } + ] + } + ], + "SubagentStop": [ + { + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/claude-hook.sh", + "args": [], + "timeout": 10, + "statusMessage": "Checking Tree Ring worker memory" + } + ] + } + ] + } +} diff --git a/scripts/smoke_v015.sh b/scripts/smoke_v015.sh index 5a417cc..143820f 100755 --- a/scripts/smoke_v015.sh +++ b/scripts/smoke_v015.sh @@ -7,7 +7,7 @@ if [[ "${tree_ring_bin}" == */* ]]; then else command -v "${tree_ring_bin}" >/dev/null fi -test "$("${tree_ring_bin}" --version)" = "tree-ring 0.15.0" +test "$("${tree_ring_bin}" --version)" = "tree-ring 0.15.7" smoke_base="${RUNNER_TEMP:-${TMPDIR:-/tmp}}" smoke_base="${smoke_base%/}" diff --git a/scripts/validate-lifecycle.py b/scripts/validate-lifecycle.py new file mode 100644 index 0000000..6ee5110 --- /dev/null +++ b/scripts/validate-lifecycle.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +"""Verify packaged lifecycle events, stdin forwarding, and managed-hook ownership.""" +import json, os, subprocess, tempfile +from pathlib import Path +from typing import Any +ROOT = Path(__file__).resolve().parents[1] +LIFECYCLE_EVENTS = {"SessionStart", "SubagentStart", "Stop", "SubagentStop"} + +def load_json(path: Path) -> dict[str, Any]: + value = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(value, dict): + raise SystemExit(f"{path.relative_to(ROOT)} must contain a JSON object") + return value + +def require(condition: bool, message: str) -> None: + if not condition: + raise SystemExit(message) + +def validate_hook_config(path: Path, *, command: str, expect_exec_form: bool) -> None: + config = load_json(path) + events = config.get("hooks") + require(isinstance(events, dict), f"{path.relative_to(ROOT)} hooks object is required") + require(set(events) == LIFECYCLE_EVENTS, f"{path.relative_to(ROOT)} must register the exact lifecycle contract") + + for event in sorted(LIFECYCLE_EVENTS): + groups = events[event] + require(isinstance(groups, list) and len(groups) == 1, f"{path.relative_to(ROOT)} {event} group is invalid") + require("matcher" not in groups[0], f"{path.relative_to(ROOT)} {event} must handle every start source") + handlers = groups[0].get("hooks") + require(isinstance(handlers, list) and len(handlers) == 1, f"{path.relative_to(ROOT)} {event} handler is invalid") + handler = handlers[0] + require(handler.get("type") == "command", f"{path.relative_to(ROOT)} {event} must use a command hook") + require(handler.get("command") == command, f"{path.relative_to(ROOT)} {event} command is stale") + require(handler.get("timeout") == 10, f"{path.relative_to(ROOT)} {event} timeout must remain bounded") + require(handler.get("async") in (None, False), f"{path.relative_to(ROOT)} {event} must not run in the background") + if expect_exec_form: + require(handler.get("args") == [], f"{path.relative_to(ROOT)} {event} must use safe exec form") + else: + require("args" not in handler, f"{path.relative_to(ROOT)} {event} uses unsupported Codex args") + require( + handler.get("additionalContextLimit") == 6000, + f"{path.relative_to(ROOT)} {event} context limit is stale", + ) + +def validate_hook_script(path: Path, harness: str) -> None: + text = path.read_text(encoding="utf-8") + require(os.access(path, os.X_OK), f"{path.relative_to(ROOT)} must be executable") + require(".tree-ring/bin/tree-ring" in text, f"{path.relative_to(ROOT)} must prefer the project-local CLI") + require("git rev-parse --show-toplevel" in text, f"{path.relative_to(ROOT)} must resolve the project root") + managed_hook = ".codex/hooks.json" if harness == "codex" else ".claude/settings.json" + require(managed_hook in text, f"{path.relative_to(ROOT)} must detect the project-managed hook") + for version in (2, 3, 4): + require( + f'Tree Ring Memory managed lifecycle v{version}"' in text, + f"{path.relative_to(ROOT)} must recognize managed lifecycle v{version}", + ) + require( + text.index(managed_hook) < text.index('exec "$tree_ring"'), + f"{path.relative_to(ROOT)} must enforce ownership before invoking the CLI", + ) + require( + f'--root .tree-ring integrations hook --harness {harness} --input-json-stdin' in text, + f"{path.relative_to(ROOT)} does not invoke the {harness} lifecycle entry point", + ) + require("PLUGIN_DATA" not in text, f"{path.relative_to(ROOT)} must not persist lifecycle input") + require("CLAUDE_PLUGIN_DATA" not in text, f"{path.relative_to(ROOT)} must not persist lifecycle input") + require(">>" not in text and "tee " not in text, f"{path.relative_to(ROOT)} must not append lifecycle input") + + events = { + "SessionStart": b'{"hook_event_name":"SessionStart","session_id":"validation-session"}\n', + "SubagentStart": b'{"hook_event_name":"SubagentStart","session_id":"validation-session","agent_id":"worker-1","agent_type":"worker"}\n', + "Stop": b'{"hook_event_name":"Stop","session_id":"validation-session","stop_hook_active":false,"transcript_path":"/private/transcript.jsonl","last_assistant_message":"must remain opaque"}\n', + "SubagentStop": b'{"hook_event_name":"SubagentStop","session_id":"validation-session","agent_id":"worker-1","agent_type":"worker","transcript_path":"/private/worker.jsonl","last_assistant_message":"must remain opaque"}\n', + } + with tempfile.TemporaryDirectory() as temporary: + project = Path(temporary) + cli = project / ".tree-ring" / "bin" / "tree-ring" + cli.parent.mkdir(parents=True) + cli.write_text( + "#!/bin/sh\n" + "printf '%s\\n' \"$@\" > \"$TREE_RING_TEST_ARGS\"\n" + "cat > \"$TREE_RING_TEST_STDIN\"\n" + "printf '%s\\n' '{\"hookSpecificOutput\":{\"additionalContext\":\"validated\"}}'\n", + encoding="utf-8", + ) + cli.chmod(0o755) + args_capture = project / "args" + stdin_capture = project / "stdin" + environment = os.environ.copy() + environment["TREE_RING_TEST_ARGS"] = str(args_capture) + environment["TREE_RING_TEST_STDIN"] = str(stdin_capture) + for event_name in sorted(LIFECYCLE_EVENTS): + event = events[event_name] + result = subprocess.run( + [str(path)], + cwd=project, + env=environment, + input=event, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + ) + require(stdin_capture.read_bytes() == event, f"{path.relative_to(ROOT)} changed {event_name} JSON on stdin") + require( + args_capture.read_text(encoding="utf-8").splitlines() + == ["--root", ".tree-ring", "integrations", "hook", "--harness", harness, "--input-json-stdin"], + f"{path.relative_to(ROOT)} passed unexpected lifecycle arguments", + ) + require(b"validated" in result.stdout, f"{path.relative_to(ROOT)} did not forward CLI output") + args_capture.unlink() + stdin_capture.unlink() + + managed_path = project / managed_hook + managed_path.parent.mkdir(parents=True, exist_ok=True) + for version in (2, 3, 4): + managed_path.write_text( + f'{{"description":"Tree Ring Memory managed lifecycle v{version}"}}\n', + encoding="utf-8", + ) + duplicate = subprocess.run( + [str(path)], + cwd=project, + env=environment, + input=events["Stop"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + ) + require(duplicate.stdout == b"", f"{path.relative_to(ROOT)} emitted duplicate v{version} context") + require(not args_capture.exists(), f"{path.relative_to(ROOT)} invoked the CLI for managed v{version}") + require(not stdin_capture.exists(), f"{path.relative_to(ROOT)} persisted managed v{version} input") + + managed_path.write_text( + '{"description":"Tree Ring Memory managed lifecycle v5"}\n', + encoding="utf-8", + ) + unsupported = subprocess.run( + [str(path)], + cwd=project, + env=environment, + input=events["SessionStart"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + ) + require(args_capture.exists(), f"{path.relative_to(ROOT)} incorrectly accepted managed lifecycle v5") + require(stdin_capture.read_bytes() == events["SessionStart"], f"{path.relative_to(ROOT)} dropped v5 fallback input") + require(b"validated" in unsupported.stdout, f"{path.relative_to(ROOT)} did not run the v5 fallback") + +validate_hook_config(ROOT / "hooks/claude-hooks.json", command='${CLAUDE_PLUGIN_ROOT}/hooks/claude-hook.sh', expect_exec_form=True) +validate_hook_script(ROOT / "hooks/claude-hook.sh", 'claude-code') +print("Lifecycle hook package verified.") diff --git a/scripts/validate.py b/scripts/validate.py index c11514b..a75267a 100644 --- a/scripts/validate.py +++ b/scripts/validate.py @@ -9,7 +9,7 @@ ROOT = Path(__file__).resolve().parents[1] -EXPECTED_WRAPPER_VERSION = "0.3.2" +EXPECTED_WRAPPER_VERSION = "0.3.4" UNSAFE_TOKEN_EXPORT = "export TREE_RING_COORDINATOR_TOKEN='<" CANONICAL_ISSUES = "https://github.com/TerminallyLazy/Tree-Ring-Memory/issues" CANONICAL_ADVISORY = "https://github.com/TerminallyLazy/Tree-Ring-Memory/security/advisories/new" @@ -243,7 +243,7 @@ def validate_security_boundary() -> None: require_markers( "SUBMISSION.md", [ - "v0.3.2", + "v0.3.4", "claude plugin validate . --strict", "smoke_v015.sh", ], @@ -270,8 +270,8 @@ def validate_workflow() -> None: ".github/workflows/validate.yml", [ "actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd", - 'TREE_RING_VERSION: "0.15.0"', - "9b47873268dbb94712a49b02bd785cc69507facee1e879e46e6922778b4afbe6", + 'TREE_RING_VERSION: "0.15.7"', + "5c9da887e212b1a7222205d2a661910276171a927d7677f09ec097f2cddd26b7", "sha256sum --check --status", "bash scripts/smoke_v015.sh", ], @@ -279,7 +279,7 @@ def validate_workflow() -> None: require_markers( "scripts/smoke_v015.sh", [ - "tree-ring 0.15.0", + "tree-ring 0.15.7", "integrations status --json --verbose", "fresh configuration must not report active", "TREE_RING_COORDINATOR_TOKEN", diff --git a/skills/tree-ring-memory/SKILL.md b/skills/tree-ring-memory/SKILL.md index 9a82aa4..9953ac3 100644 --- a/skills/tree-ring-memory/SKILL.md +++ b/skills/tree-ring-memory/SKILL.md @@ -31,6 +31,9 @@ working directory by accident. 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. + Lifecycle hooks need CLI 0.15.6 or newer; a skills-only plugin package has no + automatic hooks. Use `integrations status --verbose` to inspect the last + recall count and query class, and distinguish no receipt from zero results. 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. @@ -91,8 +94,10 @@ Use this sequence for meaningful project work: changed and defer to current source files, tests, policies, and user input. 4. Do the work. Do not write memory merely because a session is active. 5. At a natural checkpoint or closeout, capture only durable decisions, - corrections, validated lessons, warnings, or future seeds. Never store raw - transcripts, secrets, or sensitive data. + corrections, validated lessons, warnings, preferences, or future seeds. + Never store raw transcripts, secrets, or sensitive data. Repository + lifecycle integrations enforce one agent-mediated checkpoint at `Stop` or + `SubagentStop`; they do not derive memory from hook payloads. 6. Observe the command result and report the actual outcome. A proposed memory, dry run, bridge file, or generated marker is not proof that a durable write, sync, activation, correction, or deletion occurred. @@ -484,6 +489,23 @@ Tree Ring Memory does not autonomously scrape chats or write durable memory in the background. The active agent is responsible for deciding when a Tree Ring command is warranted, then calling the CLI deliberately. +Repository lifecycle integrations register exactly `SessionStart`, +`SubagentStart`, `Stop`, and `SubagentStop`. Start events perform bounded recall. +Each stop event enforces one synchronous, agent-mediated memory checkpoint. The +hook parser uses stable harness identity and project fields only; it never +inspects or persists `transcript_path`, `last_assistant_message`, prompts, or +transcript content. It does not register a prompt, tool, or `SessionEnd` hook. + +At the checkpoint, evaluate the grounded work already in your context. If there +are no durable candidates, finish without a memory write. For up to three +concise candidates that are durable, source-grounded, and normal sensitivity, +automatically run the exact strict `tree-ring capture` command template supplied +by the lifecycle handler. Do not edit its project, agent, workflow, session, +checkpoint, operation, or source identity fields. Strict capture fixes +`scope=agent`, adds the automatic-capture tag, and rejects sensitive content. +Never substitute `remember`, `evidence`, an import, or a transcript summary for +that checkpoint command. + Use bridge files only to discover Tree Ring and its command reference: - project-level bridges should point to `.tree-ring/SKILL.md` and @@ -513,7 +535,8 @@ Never keep known-wrong memory merely because it was previously recalled. ## Closeout Habit -At the end of meaningful work, ask: +At the end of meaningful work, or when a stop hook requests the single +agent-mediated checkpoint, ask: - What did we decide? - What did we learn? @@ -522,4 +545,6 @@ At the end of meaningful work, ask: - Is there a future seed worth revisiting? - Is any memory sensitive and better left unstored? -Only remember the answers that will materially improve future work. +Only remember the answers that will materially improve future work and pass the +normal-sensitivity gate. During a lifecycle checkpoint, use only the supplied +strict `tree-ring capture` template.