diff --git a/docs/installing-plugins.md b/docs/installing-plugins.md index 7fa11553..de046688 100644 --- a/docs/installing-plugins.md +++ b/docs/installing-plugins.md @@ -61,11 +61,17 @@ the updated environment. Each plugin ships its own `pyproject.toml` and `uv.lock`. Its documented bootstrap commands prepare an isolated environment from that lock on first use without writing below the installed plugin. By default, environments live under -`${XDG_CACHE_HOME:-$HOME/.cache}/darrow` on Linux and macOS and +`$HOME/.darrow/cache` on Linux and macOS and `%LOCALAPPDATA%\Darrow\Cache` on native Windows. Set `DARROW_CACHE_DIR` to a non-empty absolute path to override that root. The selected root must be readable and writable; invalid configuration fails without falling back to the plugin, working directory, repository, or temporary storage. +These UV project environments are disposable cache files. The new default +does not move or delete files under the former cache location; remove those +manually when no older plugin process needs them. `UV_CACHE_DIR` still belongs +to UV or the caller. Review evidence uses a separate retained state directory +under `$HOME/.darrow/reviews` or `%LOCALAPPDATA%\Darrow\Reviews` and follows +the cleanup rules documented by `darrow-review`. Allow network access for Python, build requirements, and runtime dependency downloads, plus write access to the Darrow and UV caches. Subsequent runs reuse diff --git a/docs/specs/code-review.md b/docs/specs/code-review.md index 55f893f6..781b8487 100644 --- a/docs/specs/code-review.md +++ b/docs/specs/code-review.md @@ -331,6 +331,27 @@ axis and report `not_available`. Do not invent requirements. result. An unavailable-route result MUST retain zero native reader-launch attempts, including inherited, substituted, background, generic, or otherwise unbound attempts. +22. **CR-C22 — User-level review state and bounded retention.** Generated review + packets and evidence MUST live in a per-user review-state root, never in the + repository Git directory or product tree. The default is `~/.darrow/reviews` + on Unix and `%LOCALAPPDATA%\Darrow\Reviews` on native Windows; an explicit + override MUST be absolute. Each run is isolated by canonical repository and + worktree identity and a unique run directory. Bundled commands return + absolute paths and provide exact-target discovery; callers MUST NOT scan + `.git` or infer an artifact from a filename. The state root and run + directories MUST restrict access to the current user on Unix and use the + user's local application-data area on Windows. Successful evidence remains + available for fix verification. A terminal scope failure MUST still be able + to allocate a private run and manifest for its canonical blocked result, + so the same pinning and retention rules apply. On review + invocation, bundled mechanics prune unpinned runs whose directories have + not changed for 30 days, retaining every prior scope and verification + artifact referenced by a retained run. An explicit pin keeps a run and its + dependencies; an explicit prune can remove unpinned expired + evidence. Cleanup MUST stay inside the review-state root, reject unsafe + paths, and report expired or missing follow-up evidence clearly. Temporary + staging files are removed as soon as their canonical record is installed. + This location change does not migrate or delete older `.git` review files. ## Result shape and presentation @@ -425,6 +446,12 @@ the prior-to-current repair delta remains nonempty and exact-target-bound. absolute paths. The plugin MAY share the `.darrow/config.json` envelope with other independently installed plugins, but MUST NOT call or reference their helpers or files. +9. **CR-P9 — Review-state portability.** State-root selection, allocation, + exact-target lookup, pinning, and pruning belong to the review plugin's + bundled mechanics. They must work across supported native hosts without a + daemon or a shared Darrow runtime. A missing or unusable state root refuses + review rather than falling back to `.git`, the repository, or a temporary + location. ## Evaluation requirements diff --git a/docs/specs/python-quality.md b/docs/specs/python-quality.md index 356300cc..eae001cc 100644 --- a/docs/specs/python-quality.md +++ b/docs/specs/python-quality.md @@ -19,13 +19,16 @@ set `UV_PROJECT_ENVIRONMENT` only for its `uv` child and must not override The bootstrap resolves the Darrow cache root in this order: 1. a non-empty absolute `DARROW_CACHE_DIR`; -2. on Linux and macOS, `${XDG_CACHE_HOME:-$HOME/.cache}/darrow`; and +2. on Linux and macOS, `$HOME/.darrow/cache`; and 3. on native Windows, `%LOCALAPPDATA%\Darrow\Cache`. Missing path inputs and relative, unreadable, or unwritable roots fail clearly. There is no fallback to the plugin tree, repository, working directory, or temporary storage. Environments live below `/uv-environments///`. +The cache is disposable. A change to this default does not migrate or remove +files at the previous location. `UV_CACHE_DIR` remains under UV or caller +control; it does not select Darrow's project-environment cache. The deterministic environment key is the SHA-256 digest of a versioned, sorted-key JSON value containing the exact plugin name and synchronized plugin diff --git a/evals/runner/environment.test.ts b/evals/runner/environment.test.ts index 7a3a14fc..97f070cb 100644 --- a/evals/runner/environment.test.ts +++ b/evals/runner/environment.test.ts @@ -9,7 +9,7 @@ import { } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { isolatedHarnessEnvironment } from "./environment"; +import { isolatedHarnessEnvironment, trialReviewStateDir } from "./environment"; import { codexAgentConcurrency } from "./codex-config"; const cleanup: string[] = []; @@ -55,6 +55,7 @@ describe("isolated harness environment", () => { expect(env.CLAUDE_CODE_OAUTH_TOKEN).toBe("claude-test-token"); expect(env.DARROW_ADAPTIVE_DELIVERY_EXTERNAL_SANDBOX).toBe("1"); expect(env.DARROW_CACHE_DIR).toBe(join(env.TMPDIR!, "darrow-cache")); + expect(env.DARROW_REVIEW_STATE_DIR).toBe(trialReviewStateDir(repo)); expect(env.UV_PROJECT_ENVIRONMENT).toBeUndefined(); expect(env.UV_CACHE_DIR).toBe(join(env.TMPDIR!, "uv-cache")); expect(env.UNRELATED_EVAL_SECRET).toBeUndefined(); diff --git a/evals/runner/environment.ts b/evals/runner/environment.ts index 09c5e2fa..d05ba44d 100644 --- a/evals/runner/environment.ts +++ b/evals/runner/environment.ts @@ -1,9 +1,14 @@ import { cp, mkdir, writeFile } from "node:fs/promises"; -import { dirname, join, resolve } from "node:path"; +import { basename, dirname, join, resolve } from "node:path"; import { codexAgentConcurrency } from "./codex-config"; type Harness = "claude" | "codex"; +/** Review evidence belongs beside the isolated repository, never within it. */ +export function trialReviewStateDir(repoDir: string): string { + return join(dirname(repoDir), `${basename(repoDir)}-reviews`); +} + /** Checks need fixture tools, not the candidate harness's authentication. */ export async function isolatedCheckEnvironment( repoDir: string, @@ -117,6 +122,7 @@ export async function isolatedHarnessEnvironment( await mkdir(configRoot, { recursive: true }); await mkdir(shellRoot, { recursive: true }); await mkdir(tempRoot, { recursive: true }); + await mkdir(trialReviewStateDir(repoDir), { recursive: true, mode: 0o700 }); if (harness === "codex") { const limit = codexAgentConcurrency(); @@ -157,6 +163,7 @@ export async function isolatedHarnessEnvironment( // environments belong in writable, per-trial scratch, outside that tree. // Each bootstrap derives a distinct environment from its locked backend. env.DARROW_CACHE_DIR = join(tempRoot, "darrow-cache"); + env.DARROW_REVIEW_STATE_DIR = trialReviewStateDir(repoDir); env.UV_CACHE_DIR = join(tempRoot, "uv-cache"); env.ZDOTDIR = shellRoot; // The runner wraps the evaluated agent in sandboxedAgentCommand. Nested diff --git a/evals/runner/fixture.ts b/evals/runner/fixture.ts index 73b2c8c0..ba487f2a 100644 --- a/evals/runner/fixture.ts +++ b/evals/runner/fixture.ts @@ -12,6 +12,7 @@ import { existsSync, realpathSync } from "node:fs"; import { tmpdir } from "node:os"; import { basename, dirname, join, relative, sep } from "node:path"; import type { Fixture } from "./types"; +import { trialReviewStateDir } from "./environment"; const TICKETCTL = `#!/bin/bash set -euo pipefail @@ -602,7 +603,11 @@ export async function buildFixture( } export async function destroyFixture(repoDir: string): Promise { - if (!existsSync(repoDir)) return; + const reviewStateDir = trialReviewStateDir(repoDir); + if (!existsSync(repoDir)) { + await rm(reviewStateDir, { recursive: true, force: true }); + return; + } let removalError: unknown; for (let attempt = 0; attempt < 8; attempt += 1) { const writable = Bun.spawn(["chmod", "-R", "u+rwX", repoDir], { @@ -626,7 +631,10 @@ export async function destroyFixture(repoDir: string): Promise { } catch (error) { removalError = error; } - if (!existsSync(repoDir)) return; + if (!existsSync(repoDir)) { + await rm(reviewStateDir, { recursive: true, force: true }); + return; + } await Bun.sleep(Math.min(100 * 2 ** attempt, 1_000)); } throw removalError ?? new Error(`cannot remove eval fixture: ${repoDir}`); diff --git a/evals/runner/run.ts b/evals/runner/run.ts index 4448ca6c..ac7728f5 100644 --- a/evals/runner/run.ts +++ b/evals/runner/run.ts @@ -27,6 +27,7 @@ import { } from "./owner-evidence"; import { parse as parseYaml } from "yaml"; import { buildFixture, destroyFixture } from "./fixture"; +import { trialReviewStateDir } from "./environment"; import { resolveCorpusSource } from "./corpus"; import { runQualityJudge } from "./judge"; import { @@ -406,11 +407,13 @@ function trialFollowUpPrompt( function trialCheckEnvironment( options: RunCaseOptions, + repoDir: string, ): Record { return { DARROW_EVAL_HARNESS: options.adapter.name, DARROW_EVAL_MODEL: options.model, DARROW_EVAL_EFFORT: options.effort, + DARROW_REVIEW_STATE_DIR: trialReviewStateDir(repoDir), }; } @@ -693,7 +696,7 @@ async function trialChecks( ...(await runChecks( repoDir, evalCase.checks, - trialCheckEnvironment(options), + trialCheckEnvironment(options, repoDir), )), ...headChecks, ...orchestrationChecks, @@ -1045,7 +1048,7 @@ async function evaluateDryTrial( await runChecks( repoDir, options.evalCase.checks, - trialCheckEnvironment(options), + trialCheckEnvironment(options, repoDir), ), ); } diff --git a/evals/runner/sandbox.test.ts b/evals/runner/sandbox.test.ts index be152f7e..59aab582 100644 --- a/evals/runner/sandbox.test.ts +++ b/evals/runner/sandbox.test.ts @@ -9,6 +9,7 @@ import { } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; +import { trialReviewStateDir } from "./environment"; import { sandboxedAgentCommand, sandboxedCommand, @@ -83,6 +84,12 @@ describe("eval outer sandbox", () => { cleanup.push(repoDir); cleanup.push(unrelatedDir); await mkdir(join(repoDir, ".git")); + const ownReviewState = trialReviewStateDir(repoDir); + const siblingReviewState = await mkdtemp( + join(tmpdir(), "darrow-eval-other-reviews-"), + ); + await mkdir(ownReviewState); + cleanup.push(ownReviewState, siblingReviewState); if (process.platform === "darwin") { const protectedPath = join(repoDir, ".git", "eval-plugin"); @@ -105,6 +112,10 @@ describe("eval outer sandbox", () => { `(deny file-read* (subpath "${await realpath(main!)}"))`, ); expect(profile).not.toContain(await realpath(unrelatedDir)); + expect(profile).not.toContain(await realpath(ownReviewState)); + expect(profile).toContain( + `(deny file-read* (subpath "${await realpath(siblingReviewState)}"))`, + ); expect(profile).toContain( `(deny file-write* (subpath "${protectedPath}"))`, ); diff --git a/evals/runner/sandbox.ts b/evals/runner/sandbox.ts index af653935..8d103482 100644 --- a/evals/runner/sandbox.ts +++ b/evals/runner/sandbox.ts @@ -2,6 +2,7 @@ import { existsSync } from "node:fs"; import { mkdir, readdir, realpath, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { basename, join, resolve } from "node:path"; +import { trialReviewStateDir } from "./environment"; const SOURCE_ROOT = resolve(import.meta.dir, "..", ".."); const SANDBOX_EXEC = "/usr/bin/sandbox-exec"; @@ -40,6 +41,7 @@ export function sandboxProfile( async function siblingFixtures(repoDir: string): Promise { const repoReal = await realpath(repoDir); + const ownReviewState = trialReviewStateDir(repoReal); const entries = await readdir(tmpdir(), { withFileTypes: true }); const paths: string[] = []; for (const entry of entries) { @@ -48,7 +50,8 @@ async function siblingFixtures(repoDir: string): Promise { const candidate = join(tmpdir(), entry.name); try { const candidateReal = await realpath(candidate); - if (candidateReal !== repoReal) paths.push(candidateReal); + if (candidateReal !== repoReal && candidateReal !== ownReviewState) + paths.push(candidateReal); } catch { // A concurrently removed fixture is already inaccessible. } diff --git a/plugins/automation/darrow-artificer/.claude-plugin/plugin.json b/plugins/automation/darrow-artificer/.claude-plugin/plugin.json index 93370c95..60f0ad4b 100644 --- a/plugins/automation/darrow-artificer/.claude-plugin/plugin.json +++ b/plugins/automation/darrow-artificer/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-artificer", - "version": "0.1.2", + "version": "0.1.3", "description": "Explicit local admission and native Codex continuation for nominated GitHub tickets", "license": "BUSL-1.1", "author": { "name": "Björn Rochel", "email": "bjoern@bjro.de" } diff --git a/plugins/automation/darrow-artificer/.codex-plugin/plugin.json b/plugins/automation/darrow-artificer/.codex-plugin/plugin.json index 93e8ce2f..c604db44 100644 --- a/plugins/automation/darrow-artificer/.codex-plugin/plugin.json +++ b/plugins/automation/darrow-artificer/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-artificer", - "version": "0.1.2", + "version": "0.1.3", "description": "Explicit local admission and native Codex continuation for nominated GitHub tickets", "skills": "./skills/", "interface": { diff --git a/plugins/automation/darrow-artificer/backend/scripts/run_locked.py b/plugins/automation/darrow-artificer/backend/scripts/run_locked.py index 0d8ed007..7a3d5cfb 100644 --- a/plugins/automation/darrow-artificer/backend/scripts/run_locked.py +++ b/plugins/automation/darrow-artificer/backend/scripts/run_locked.py @@ -80,15 +80,10 @@ def default_cache_root(environment: Mapping[str, str], platform: str) -> str: "LOCALAPPDATA is required when DARROW_CACHE_DIR is not set" ) return join_path(platform, local, "Darrow", "Cache") - xdg = environment.get("XDG_CACHE_HOME") - if xdg: - return join_path(platform, xdg, "darrow") home = environment.get("HOME") if not home: - raise LauncherError( - "HOME is required when DARROW_CACHE_DIR and XDG_CACHE_HOME are not set" - ) - return join_path(platform, home, ".cache", "darrow") + raise LauncherError("HOME is required when DARROW_CACHE_DIR is not set") + return join_path(platform, home, ".darrow", "cache") def resolve_cache_root(environment: Mapping[str, str], platform: str) -> str: diff --git a/plugins/capability/darrow-discovery/.claude-plugin/plugin.json b/plugins/capability/darrow-discovery/.claude-plugin/plugin.json index 53c20fcc..e8447405 100644 --- a/plugins/capability/darrow-discovery/.claude-plugin/plugin.json +++ b/plugins/capability/darrow-discovery/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "darrow-discovery", "description": "Explicit grilling, feature discovery, and implementation planning", - "version": "0.2.6", + "version": "0.2.7", "license": "BUSL-1.1", "author": { "name": "Björn Rochel", diff --git a/plugins/capability/darrow-discovery/.codex-plugin/plugin.json b/plugins/capability/darrow-discovery/.codex-plugin/plugin.json index 3a0915ac..11078246 100644 --- a/plugins/capability/darrow-discovery/.codex-plugin/plugin.json +++ b/plugins/capability/darrow-discovery/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-discovery", - "version": "0.2.6", + "version": "0.2.7", "description": "Explicit grilling, feature discovery, and implementation planning", "author": { "name": "Björn Rochel", diff --git a/plugins/capability/darrow-discovery/skills/plan-implementation/backend/scripts/run_locked.py b/plugins/capability/darrow-discovery/skills/plan-implementation/backend/scripts/run_locked.py index 0d8ed007..7a3d5cfb 100644 --- a/plugins/capability/darrow-discovery/skills/plan-implementation/backend/scripts/run_locked.py +++ b/plugins/capability/darrow-discovery/skills/plan-implementation/backend/scripts/run_locked.py @@ -80,15 +80,10 @@ def default_cache_root(environment: Mapping[str, str], platform: str) -> str: "LOCALAPPDATA is required when DARROW_CACHE_DIR is not set" ) return join_path(platform, local, "Darrow", "Cache") - xdg = environment.get("XDG_CACHE_HOME") - if xdg: - return join_path(platform, xdg, "darrow") home = environment.get("HOME") if not home: - raise LauncherError( - "HOME is required when DARROW_CACHE_DIR and XDG_CACHE_HOME are not set" - ) - return join_path(platform, home, ".cache", "darrow") + raise LauncherError("HOME is required when DARROW_CACHE_DIR is not set") + return join_path(platform, home, ".darrow", "cache") def resolve_cache_root(environment: Mapping[str, str], platform: str) -> str: diff --git a/plugins/capability/darrow-discovery/skills/plan-implementation/backend/tests/test_runtime_launcher.py b/plugins/capability/darrow-discovery/skills/plan-implementation/backend/tests/test_runtime_launcher.py index 4454d451..ddbc200f 100644 --- a/plugins/capability/darrow-discovery/skills/plan-implementation/backend/tests/test_runtime_launcher.py +++ b/plugins/capability/darrow-discovery/skills/plan-implementation/backend/tests/test_runtime_launcher.py @@ -35,10 +35,11 @@ def test_cache_root_resolution_is_native_and_fail_closed() -> None: assert resolve({"DARROW_CACHE_DIR": "/var/cache/darrow-test"}, "posix") == ( "/var/cache/darrow-test" ) - assert resolve({"XDG_CACHE_HOME": "/var/cache/user"}, "posix") == ( - "/var/cache/user/darrow" + assert ( + resolve({"HOME": "/home/test", "XDG_CACHE_HOME": "/var/cache/user"}, "posix") + == "/home/test/.darrow/cache" ) - assert resolve({"HOME": "/home/test"}, "posix") == "/home/test/.cache/darrow" + assert resolve({"HOME": "/home/test"}, "posix") == "/home/test/.darrow/cache" assert ( resolve({"LOCALAPPDATA": r"C:\Users\Test\AppData\Local"}, "nt") == r"C:\Users\Test\AppData\Local\Darrow\Cache" diff --git a/plugins/capability/darrow-git/.claude-plugin/plugin.json b/plugins/capability/darrow-git/.claude-plugin/plugin.json index 6443e005..c5242f5a 100644 --- a/plugins/capability/darrow-git/.claude-plugin/plugin.json +++ b/plugins/capability/darrow-git/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "darrow-git", "description": "Intent-triggered Git skills for branches, commits, PRs, and candidate-bound PR evidence", - "version": "0.8.5", + "version": "0.8.6", "hooks": "./.claude-plugin/hooks.json", "license": "BUSL-1.1" } diff --git a/plugins/capability/darrow-git/.codex-plugin/plugin.json b/plugins/capability/darrow-git/.codex-plugin/plugin.json index 95fe70ce..7234b87f 100644 --- a/plugins/capability/darrow-git/.codex-plugin/plugin.json +++ b/plugins/capability/darrow-git/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-git", - "version": "0.8.5", + "version": "0.8.6", "description": "Intent-triggered Git skills for branches, commits, PRs, and candidate-bound PR evidence", "author": { "name": "Björn Rochel" diff --git a/plugins/capability/darrow-git/backend/scripts/run_locked.py b/plugins/capability/darrow-git/backend/scripts/run_locked.py index 0d8ed007..7a3d5cfb 100644 --- a/plugins/capability/darrow-git/backend/scripts/run_locked.py +++ b/plugins/capability/darrow-git/backend/scripts/run_locked.py @@ -80,15 +80,10 @@ def default_cache_root(environment: Mapping[str, str], platform: str) -> str: "LOCALAPPDATA is required when DARROW_CACHE_DIR is not set" ) return join_path(platform, local, "Darrow", "Cache") - xdg = environment.get("XDG_CACHE_HOME") - if xdg: - return join_path(platform, xdg, "darrow") home = environment.get("HOME") if not home: - raise LauncherError( - "HOME is required when DARROW_CACHE_DIR and XDG_CACHE_HOME are not set" - ) - return join_path(platform, home, ".cache", "darrow") + raise LauncherError("HOME is required when DARROW_CACHE_DIR is not set") + return join_path(platform, home, ".darrow", "cache") def resolve_cache_root(environment: Mapping[str, str], platform: str) -> str: diff --git a/plugins/capability/darrow-observability-langfuse/.claude-plugin/plugin.json b/plugins/capability/darrow-observability-langfuse/.claude-plugin/plugin.json index afad1ee1..aabcd5b6 100644 --- a/plugins/capability/darrow-observability-langfuse/.claude-plugin/plugin.json +++ b/plugins/capability/darrow-observability-langfuse/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "darrow-observability-langfuse", "description": "Export Codex rollout turns to Langfuse with attribution epochs", - "version": "0.6.2", + "version": "0.6.3", "license": "BUSL-1.1", "author": { "name": "Björn Rochel", diff --git a/plugins/capability/darrow-observability-langfuse/.codex-plugin/plugin.json b/plugins/capability/darrow-observability-langfuse/.codex-plugin/plugin.json index 195bd074..2b3983ae 100644 --- a/plugins/capability/darrow-observability-langfuse/.codex-plugin/plugin.json +++ b/plugins/capability/darrow-observability-langfuse/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-observability-langfuse", - "version": "0.6.2", + "version": "0.6.3", "description": "Export Codex rollout turns to Langfuse with attribution epochs", "author": { "name": "Björn Rochel", diff --git a/plugins/capability/darrow-observability-langfuse/backend/scripts/run_locked.py b/plugins/capability/darrow-observability-langfuse/backend/scripts/run_locked.py index 0d8ed007..7a3d5cfb 100644 --- a/plugins/capability/darrow-observability-langfuse/backend/scripts/run_locked.py +++ b/plugins/capability/darrow-observability-langfuse/backend/scripts/run_locked.py @@ -80,15 +80,10 @@ def default_cache_root(environment: Mapping[str, str], platform: str) -> str: "LOCALAPPDATA is required when DARROW_CACHE_DIR is not set" ) return join_path(platform, local, "Darrow", "Cache") - xdg = environment.get("XDG_CACHE_HOME") - if xdg: - return join_path(platform, xdg, "darrow") home = environment.get("HOME") if not home: - raise LauncherError( - "HOME is required when DARROW_CACHE_DIR and XDG_CACHE_HOME are not set" - ) - return join_path(platform, home, ".cache", "darrow") + raise LauncherError("HOME is required when DARROW_CACHE_DIR is not set") + return join_path(platform, home, ".darrow", "cache") def resolve_cache_root(environment: Mapping[str, str], platform: str) -> str: diff --git a/plugins/capability/darrow-review/.claude-plugin/plugin.json b/plugins/capability/darrow-review/.claude-plugin/plugin.json index a29d73c7..e735f650 100644 --- a/plugins/capability/darrow-review/.claude-plugin/plugin.json +++ b/plugins/capability/darrow-review/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "darrow-review", "description": "Read-only comprehensive code review and fix-scoped repair verification", - "version": "0.5.7", + "version": "0.5.8", "license": "BUSL-1.1", "author": { "name": "Björn Rochel", diff --git a/plugins/capability/darrow-review/.codex-plugin/plugin.json b/plugins/capability/darrow-review/.codex-plugin/plugin.json index 441911ab..668b73ca 100644 --- a/plugins/capability/darrow-review/.codex-plugin/plugin.json +++ b/plugins/capability/darrow-review/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-review", - "version": "0.5.7", + "version": "0.5.8", "description": "Read-only comprehensive code review and fix-scoped repair verification", "author": { "name": "Björn Rochel", diff --git a/plugins/capability/darrow-review/README.md b/plugins/capability/darrow-review/README.md index 8f1920fa..a946ca1e 100644 --- a/plugins/capability/darrow-review/README.md +++ b/plugins/capability/darrow-review/README.md @@ -92,6 +92,34 @@ Resolves and snapshots the requested review scope. It accounts for committed, staged, unstaged, renamed, deleted, and untracked paths as appropriate so every reviewer examines the same immutable change packet. +### Review state and cleanup + +Generated review packets and evidence live under `~/.darrow/reviews` on Linux +and macOS or `%LOCALAPPDATA%\Darrow\Reviews` on native Windows. Set +`DARROW_REVIEW_STATE_DIR` to a nonempty absolute path to select another root. +Each repository and worktree has a separate state directory, and each review +gets a unique run directory. The scope command returns the absolute manifest +path; `review-scope locate --repo --target ` finds one +validated prior manifest when a follow-up did not retain its path. +If preparation stops before producing a scope manifest, `review-scope +allocate-terminal --repo ` returns a private directory and terminal +manifest for the blocked result. That manifest also supports `pin` and `unpin`. + +The next review invocation prunes unpinned runs whose directories have not +changed for 30 days across the +user's review-state root. A retained fix-verification run keeps every prior +scope and verification run it references. Use `review-scope pin --manifest +` to retain a run and its dependencies, or `unpin` with the +same argument to return it to normal retention. `review-scope prune --all` +applies the 30-day rule immediately; `--older-than-days 0` removes all unpinned +runs without retained dependents. These commands remove only generated review +state. Expired follow-up evidence produces a clear refusal. Older `.git` review +directories are neither migrated nor deleted automatically. + +The separate UV project-environment cache lives under `~/.darrow/cache` on +Linux and macOS or `%LOCALAPPDATA%\Darrow\Cache` on Windows. It is disposable; +review evidence is retained until the review cleanup rule removes it. + ### `review-result` Validates the structured findings produced by each comprehensive axis, the final diff --git a/plugins/capability/darrow-review/backend/scripts/run_locked.py b/plugins/capability/darrow-review/backend/scripts/run_locked.py index 0d8ed007..7a3d5cfb 100644 --- a/plugins/capability/darrow-review/backend/scripts/run_locked.py +++ b/plugins/capability/darrow-review/backend/scripts/run_locked.py @@ -80,15 +80,10 @@ def default_cache_root(environment: Mapping[str, str], platform: str) -> str: "LOCALAPPDATA is required when DARROW_CACHE_DIR is not set" ) return join_path(platform, local, "Darrow", "Cache") - xdg = environment.get("XDG_CACHE_HOME") - if xdg: - return join_path(platform, xdg, "darrow") home = environment.get("HOME") if not home: - raise LauncherError( - "HOME is required when DARROW_CACHE_DIR and XDG_CACHE_HOME are not set" - ) - return join_path(platform, home, ".cache", "darrow") + raise LauncherError("HOME is required when DARROW_CACHE_DIR is not set") + return join_path(platform, home, ".darrow", "cache") def resolve_cache_root(environment: Mapping[str, str], platform: str) -> str: diff --git a/plugins/capability/darrow-review/backend/src/darrow_review/check.py b/plugins/capability/darrow-review/backend/src/darrow_review/check.py index d9585803..434a5ed0 100644 --- a/plugins/capability/darrow-review/backend/src/darrow_review/check.py +++ b/plugins/capability/darrow-review/backend/src/darrow_review/check.py @@ -6,7 +6,8 @@ import shutil from pathlib import Path -from .common import ReviewError, git, new_record, require, run, serialize +from . import storage +from .common import ReviewError, new_record, require, root_directory, run, serialize def shell_args(command: str) -> list[str]: @@ -59,21 +60,7 @@ def capture(output: str, command: str) -> str: ) require(Path(output).is_absolute(), "output must be an absolute path") require(Path(output).name not in ("", ".", ".."), "output must name a file") - git_dir = Path( - git( - Path.cwd(), - "rev-parse", - "--absolute-git-dir", - message="current directory is not inside a Git repository", - ) - .decode() - .strip() - ).resolve() - path = Path(output).parent.resolve(strict=True) / Path(output).name - require( - path.is_relative_to(git_dir), - f"output must be beneath the repository Git directory: {git_dir}", - ) + path = storage.check_output(root_directory(str(Path.cwd())), Path(output)) require( not path.exists() and not path.is_symlink(), f"output already exists: {path}" ) diff --git a/plugins/capability/darrow-review/backend/src/darrow_review/cli.py b/plugins/capability/darrow-review/backend/src/darrow_review/cli.py index 71b87dfc..bd757ab6 100644 --- a/plugins/capability/darrow-review/backend/src/darrow_review/cli.py +++ b/plugins/capability/darrow-review/backend/src/darrow_review/cli.py @@ -7,10 +7,11 @@ import signal import sys from collections.abc import Callable, Sequence +from pathlib import Path from typing import NoReturn -from . import check, provider, report, result, routing, scope -from .common import ReviewError, read_text, require, serialize +from . import check, provider, report, result, routing, scope, storage +from .common import ReviewError, read_text, require, root_directory, serialize from .records import validate_result @@ -63,14 +64,25 @@ def options( def scope_command(args: list[str]) -> str | bytes: - require(args, "Usage: review-scope prepare|show|compare [options]") + require( + args, + "Usage: review-scope prepare|show|compare|allocate-terminal|locate|prune|pin|unpin [options]", + ) command, rest = args[0], args[1:] handlers: dict[str, Callable[[list[str]], str | bytes]] = { "prepare": prepare_scope, "show": show_scope, "compare": compare_scope, + "allocate-terminal": allocate_terminal_scope, + "locate": locate_scope, + "prune": prune_scope, + "pin": pin_scope, + "unpin": unpin_scope, } - require(command in handlers, "Usage: review-scope prepare|show|compare [options]") + require( + command in handlers, + "Usage: review-scope prepare|show|compare|allocate-terminal|locate|prune|pin|unpin [options]", + ) return handlers[command](rest) @@ -98,6 +110,54 @@ def compare_scope(args: list[str]) -> str: return scope.compare(parsed.prior_manifest, parsed.current_manifest) +def allocate_terminal_scope(args: list[str]) -> str: + parsed = options("review-scope allocate-terminal", args, ("repo",)) + repo = root_directory(parsed.repo) + run = storage.allocate_terminal(repo) + return serialize([["artifact_dir", str(run)], ["manifest", str(run / "scope.tsv")]]) + + +def locate_scope(args: list[str]) -> str: + parsed = options("review-scope locate", args, ("repo", "target")) + candidate = storage.locate(root_directory(parsed.repo), parsed.target) + require( + candidate is not None, + f"review artifact is unavailable for target: {parsed.target}", + 4, + ) + return serialize([["manifest", str(candidate)]]) + + +def prune_scope(args: list[str]) -> str: + parsed = options( + "review-scope prune", args, (), ("repo", "older-than-days"), ("all",) + ) + days = parsed.older_than_days + require( + not days or days.isdecimal(), "older-than-days must be a nonnegative integer" + ) + require(parsed.all or parsed.repo, "prune requires --repo or --all") + age = int(days) if days else storage.RETENTION_DAYS + removed = ( + storage.prune_all(older_than_days=age) + if parsed.all + else storage.prune(root_directory(parsed.repo), age) + ) + return serialize( + [["pruned", str(len(removed))], *[["removed", str(p)] for p in removed]] + ) + + +def pin_scope(args: list[str]) -> str: + parsed = options("review-scope pin", args, ("manifest",)) + return serialize([["pinned", str(storage.pin(Path(parsed.manifest)))]]) + + +def unpin_scope(args: list[str]) -> str: + parsed = options("review-scope unpin", args, ("manifest",)) + return serialize([["unpinned", str(storage.unpin(Path(parsed.manifest)))]]) + + def result_command(args: list[str]) -> str: require(args, "Usage: review-result COMMAND FILE [FILE]") command, rest = args[0], args[1:] diff --git a/plugins/capability/darrow-review/backend/src/darrow_review/scope.py b/plugins/capability/darrow-review/backend/src/darrow_review/scope.py index 5c4fed0d..49fb58bd 100644 --- a/plugins/capability/darrow-review/backend/src/darrow_review/scope.py +++ b/plugins/capability/darrow-review/backend/src/darrow_review/scope.py @@ -3,11 +3,12 @@ from __future__ import annotations import difflib +import os import shutil -import tempfile from dataclasses import dataclass from pathlib import Path +from . import storage from .common import ( command_line, entrypoint, @@ -285,25 +286,20 @@ def prepare(options: ScopeOptions) -> str: base = effective_base( repo, resolve_commit(repo, options.base, "base"), target, options.merge_base ) - validate_prior(repo, base, options.prior_manifest) - names, layers, packet = assemble(repo, options, base, target) - require(names or options.allow_empty, "the declared review scope is empty", 3) - git_dir = Path( - git( - repo, - "rev-parse", - "--absolute-git-dir", - message="cannot resolve the repository Git directory", - ) - .decode() - .strip() - ).resolve() - artifact = Path(tempfile.mkdtemp(prefix="darrow-review.", dir=git_dir)) - try: - return write_scope(artifact, repo, options, base, target, names, layers, packet) - except BaseException: - shutil.rmtree(artifact) - raise + with storage.bucket_lock(storage.repository_state(repo)): + validate_prior(repo, base, options.prior_manifest) + names, layers, packet = assemble(repo, options, base, target) + require(names or options.allow_empty, "the declared review scope is empty", 3) + artifact = storage.allocate(repo) + try: + output = write_scope( + artifact, repo, options, base, target, names, layers, packet + ) + except BaseException: + shutil.rmtree(artifact) + raise + storage.prune_all(repo) + return output def write_scope( @@ -317,7 +313,9 @@ def write_scope( packet: bytes, ) -> str: diff = artifact / "diff.patch" - diff.write_bytes(packet) + descriptor = os.open(diff, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) + with os.fdopen(descriptor, "wb") as stream: + stream.write(packet) checksum = ( git( repo, @@ -375,5 +373,7 @@ def write_scope( ) records.append(["manifest", path]) body = serialize(records) - Path(path).write_text(body, encoding="utf-8", newline="\n") + descriptor = os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) + with os.fdopen(descriptor, "w", encoding="utf-8", newline="\n") as stream: + stream.write(body) return body diff --git a/plugins/capability/darrow-review/backend/src/darrow_review/storage.py b/plugins/capability/darrow-review/backend/src/darrow_review/storage.py new file mode 100644 index 00000000..251ad386 --- /dev/null +++ b/plugins/capability/darrow-review/backend/src/darrow_review/storage.py @@ -0,0 +1,339 @@ +"""Private per-user review state and dependency-aware retention.""" + +from __future__ import annotations + +import errno +import hashlib +import importlib +import ntpath +import os +import posixpath +import shutil +import tempfile +import time +from collections.abc import Iterator, Mapping +from contextlib import contextmanager +from pathlib import Path + +from .common import ReviewError, require, rows, safe_line + +RETENTION_DAYS = 30 +RUN_PREFIX = "darrow-review." +WINDOWS_LOCK_WAIT_SECONDS = 120 + + +def windows_absolute(path: str) -> bool: + drive, tail = ntpath.splitdrive(path) + return bool(drive) and ( + tail.startswith(("/", "\\")) or (not tail and drive.startswith(("\\\\", "//"))) + ) + + +def resolve_root(environment: Mapping[str, str], platform: str) -> str: + override = environment.get("DARROW_REVIEW_STATE_DIR") + if override: + if platform == "nt": + require( + windows_absolute(override), "DARROW_REVIEW_STATE_DIR must be absolute" + ) + return ntpath.normpath(override) + require(posixpath.isabs(override), "DARROW_REVIEW_STATE_DIR must be absolute") + return posixpath.normpath(override) + return default_root(environment, platform) + + +def default_root(environment: Mapping[str, str], platform: str) -> str: + if platform == "nt": + local = environment.get("LOCALAPPDATA") + if not local: + raise ReviewError("LOCALAPPDATA is required for review state") + require(windows_absolute(local), "LOCALAPPDATA must be absolute") + return ntpath.join(local, "Darrow", "Reviews") + home = environment.get("HOME") + if not home: + raise ReviewError("HOME is required for review state") + require(posixpath.isabs(home), "HOME must be absolute") + return posixpath.join(home, ".darrow", "reviews") + + +def state_root(repo: Path | None = None, *, create: bool = True) -> Path: + requested = Path(resolve_root(os.environ, os.name)) + require(not requested.is_symlink(), "review-state root must not be a symlink") + root = requested.resolve() + if repo is not None: + require( + not root.is_relative_to(repo.resolve()), + "review-state root must be outside the repository", + ) + if create: + root.mkdir(mode=0o700, parents=True, exist_ok=True) + require(root.is_dir(), f"review-state root is unreadable: {root}") + if os.name != "nt": + require( + root.stat().st_mode & 0o077 == 0, + f"review-state root must be accessible only to the current user: {root}", + ) + return root + + +def repository_state(repo: Path, *, create: bool = True) -> Path: + root = state_root(repo, create=create) + bucket = root / repository_digest(repo) + require( + not bucket.is_symlink(), "review-state repository path must not be a symlink" + ) + if create: + bucket.mkdir(mode=0o700, exist_ok=True) + require(bucket.is_dir(), f"review-state repository path is unreadable: {bucket}") + if os.name != "nt": + require( + bucket.stat().st_mode & 0o077 == 0, + f"review-state repository path must be user-only: {bucket}", + ) + return bucket + + +def repository_digest(repo: Path) -> str: + return hashlib.sha256(str(repo.resolve()).encode("utf-8")).hexdigest() + + +def allocate(repo: Path) -> Path: + bucket = repository_state(repo) + return Path(tempfile.mkdtemp(prefix=RUN_PREFIX, dir=bucket)) + + +def allocate_terminal(repo: Path) -> Path: + safe_line(str(repo), "repository path") + run = allocate(repo) + try: + manifest = run / "scope.tsv" + body = f"format\tdarrow-review-terminal-v1\nrepository\t{repo}\n" + descriptor = os.open(manifest, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) + with os.fdopen(descriptor, "w", encoding="utf-8", newline="\n") as stream: + stream.write(body) + except BaseException: + shutil.rmtree(run) + raise + prune_all(repo) + return run + + +def run_for_manifest(manifest: Path) -> Path: + require(manifest.is_absolute(), "review manifest path must be absolute") + require(manifest.name == "scope.tsv", "review manifest must name scope.tsv") + fields = fields_at(manifest) + require( + fields.get("format") in ("darrow-review-scope-v1", "darrow-review-terminal-v1"), + "invalid review manifest", + ) + repo = Path(fields.get("repository", "")) + require(repo.is_absolute(), "review manifest repository must be absolute") + bucket = repository_state(repo, create=False) + run = manifest.parent.resolve() + require( + run.parent == bucket and run.name.startswith(RUN_PREFIX), + "unsafe review run path", + ) + require( + not manifest.is_symlink() and not run.is_symlink(), "unsafe review run path" + ) + return run + + +def check_output(repo: Path, output: Path) -> Path: + require(output.is_absolute(), "output must be an absolute path") + require(output.name not in ("", ".", ".."), "output must name a file") + path = output.parent.resolve(strict=True) / output.name + run = run_for_manifest(path.parent / "scope.tsv") + require( + run.parent == repository_state(repo, create=False), + "output must be beneath this repository's review-state directory", + ) + return path + + +def fields_at(path: Path) -> dict[str, str]: + try: + content = path.read_text(encoding="utf-8") + except (OSError, UnicodeError) as exc: + raise ReviewError(f"review state is unreadable: {path}") from exc + return {row[0]: row[1] for row in rows(content) if len(row) == 2} + + +def runs(bucket: Path) -> list[Path]: + return sorted( + path + for path in bucket.iterdir() + if path.name.startswith(RUN_PREFIX) and path.is_dir() and not path.is_symlink() + ) + + +def locate(repo: Path, target: str) -> Path | None: + root = Path(resolve_root(os.environ, os.name)) + if not root.exists() or not (root / repository_digest(repo)).exists(): + return None + bucket = repository_state(repo, create=False) + matches: list[Path] = [] + from . import scope + + for run in runs(bucket): + candidate = run / "scope.tsv" + if not candidate.is_file() or candidate.is_symlink(): + continue + fields = fields_at(candidate) + if fields.get("repository") != str(repo) or fields.get("target") != target: + continue + scope.show(str(candidate)) + matches.append(candidate) + require(len(matches) <= 1, f"multiple review artifacts match target: {target}") + return matches[0] if matches else None + + +def dependencies(run: Path, by_file: dict[Path, Path]) -> set[Path]: + result: set[Path] = set() + for file, field, index in ( + (run / "scope.tsv", "prior_manifest", 1), + (run / "verification.tsv", "previous_verification", 2), + ): + result.update(references(file, field, index, by_file)) + return result + + +def references( + file: Path, field: str, index: int, by_file: dict[Path, Path] +) -> set[Path]: + if not file.is_file() or file.is_symlink(): + return set() + try: + lines = rows(file.read_text(encoding="utf-8")) + except (OSError, UnicodeError) as exc: + raise ReviewError(f"review dependency is unreadable: {file}") from exc + paths = ( + by_file.get(Path(row[index]).resolve()) + for row in lines + if len(row) > index and row[0] == field + ) + return {path for path in paths if path is not None} + + +def retained_runs(all_runs: list[Path], cutoff: float) -> set[Path]: + by_file = { + run / name: run + for run in all_runs + for name in ("scope.tsv", "verification.tsv") + } + keep = { + run + for run in all_runs + if run.stat().st_mtime >= cutoff or (run / ".pinned").is_file() + } + pending = list(keep) + while pending: + for prior in dependencies(pending.pop(), by_file) - keep: + keep.add(prior) + pending.append(prior) + return keep + + +@contextmanager +def bucket_lock(bucket: Path) -> Iterator[None]: + path = bucket / ".lock" + descriptor = os.open(path, os.O_CREAT | os.O_RDWR, 0o600) + acquired = False + try: + if os.name == "nt": + os.lseek(descriptor, 0, os.SEEK_SET) + windows_lock(descriptor, "LK_LOCK") + else: + posix_lock(descriptor, release=False) + acquired = True + yield + finally: + if acquired: + if os.name == "nt": + windows_lock(descriptor, "LK_UNLCK") + else: + posix_lock(descriptor, release=True) + os.close(descriptor) + + +def posix_lock(descriptor: int, *, release: bool) -> None: + module = importlib.import_module("fcntl") + module.flock(descriptor, module.LOCK_UN if release else module.LOCK_EX) + + +def windows_lock(descriptor: int, mode: str) -> None: + module = importlib.import_module("msvcrt") + if mode == "LK_UNLCK": + module.locking(descriptor, module.LK_UNLCK, 1) + return + deadline = time.monotonic() + WINDOWS_LOCK_WAIT_SECONDS + while True: + try: + module.locking(descriptor, module.LK_NBLCK, 1) + return + except OSError as exc: + if ( + exc.errno not in (errno.EACCES, errno.EAGAIN) + or time.monotonic() >= deadline + ): + raise + time.sleep(0.1) + + +def prune(repo: Path, older_than_days: int = RETENTION_DAYS) -> list[Path]: + require(older_than_days >= 0, "retention days must not be negative") + bucket = repository_state(repo) + cutoff = time.time() - older_than_days * 86_400 + return prune_bucket(bucket, cutoff) + + +def prune_all( + repo: Path | None = None, older_than_days: int = RETENTION_DAYS +) -> list[Path]: + require(older_than_days >= 0, "retention days must not be negative") + root = state_root(repo) + cutoff = time.time() - older_than_days * 86_400 + removed: list[Path] = [] + for bucket in sorted(root.iterdir()): + if not safe_bucket(bucket): + continue + removed.extend(prune_bucket(bucket, cutoff)) + return removed + + +def safe_bucket(bucket: Path) -> bool: + return ( + bucket.is_dir() + and not bucket.is_symlink() + and len(bucket.name) == 64 + and all(char in "0123456789abcdef" for char in bucket.name) + ) + + +def prune_bucket(bucket: Path, cutoff: float) -> list[Path]: + removed: list[Path] = [] + with bucket_lock(bucket): + all_runs = runs(bucket) + keep = retained_runs(all_runs, cutoff) + for run in all_runs: + if run not in keep: + shutil.rmtree(run) + removed.append(run) + return removed + + +def pin(manifest: Path) -> Path: + run = run_for_manifest(manifest) + with bucket_lock(run.parent): + marker = run / ".pinned" + descriptor = os.open(marker, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600) + os.close(descriptor) + return run + + +def unpin(manifest: Path) -> Path: + run = run_for_manifest(manifest) + with bucket_lock(run.parent): + (run / ".pinned").unlink() + return run diff --git a/plugins/capability/darrow-review/backend/tests/conftest.py b/plugins/capability/darrow-review/backend/tests/conftest.py index 70203b66..b578559d 100644 --- a/plugins/capability/darrow-review/backend/tests/conftest.py +++ b/plugins/capability/darrow-review/backend/tests/conftest.py @@ -35,7 +35,8 @@ def git(repo: Path, *args: str) -> str: @pytest.fixture -def repo(tmp_path: Path) -> Path: +def repo(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path: + monkeypatch.setenv("DARROW_REVIEW_STATE_DIR", str(tmp_path / "review-state")) path = tmp_path / "repo with quotes ' and spaces" path.mkdir() git(path, "init", "-q", "-b", "main") diff --git a/plugins/capability/darrow-review/backend/tests/evals/eval_routes.py b/plugins/capability/darrow-review/backend/tests/evals/eval_routes.py index c6aceb9f..608d6fc3 100644 --- a/plugins/capability/darrow-review/backend/tests/evals/eval_routes.py +++ b/plugins/capability/darrow-review/backend/tests/evals/eval_routes.py @@ -2,6 +2,7 @@ import argparse import json +import os import re from pathlib import Path from typing import Any @@ -148,8 +149,15 @@ def verify_provider(directory: Path, axis: str, count: int) -> None: ] -def verify(git_dir: Path, host: str, profile: str, axes: list[str]) -> None: - selections = sorted(git_dir.glob("**/darrow-review.*/reviewer-route.tsv")) +def verify( + git_dir: Path, + host: str, + profile: str, + axes: list[str], + review_state: Path | None = None, +) -> None: + review_root = review_state if review_state is not None else git_dir + selections = sorted(review_root.glob("**/darrow-review.*/reviewer-route.tsv")) assert selections, "missing reviewer route" directory = selections[-1].parent expected = route(host, profile) @@ -176,7 +184,15 @@ def main() -> None: "--axes", choices=("standards", "spec"), nargs="+", required=True ) arguments = parser.parse_args() - verify(Path(".git"), arguments.host, arguments.profile, arguments.axes) + review_root = os.environ.get("DARROW_REVIEW_STATE_DIR") + assert review_root, "missing review-state directory" + verify( + Path(".git"), + arguments.host, + arguments.profile, + arguments.axes, + Path(review_root), + ) if __name__ == "__main__": diff --git a/plugins/capability/darrow-review/backend/tests/evals/test_eval_routes.py b/plugins/capability/darrow-review/backend/tests/evals/test_eval_routes.py index 8db6160e..e7efb670 100644 --- a/plugins/capability/darrow-review/backend/tests/evals/test_eval_routes.py +++ b/plugins/capability/darrow-review/backend/tests/evals/test_eval_routes.py @@ -114,3 +114,12 @@ def test_duplicate_tsv_identity_is_not_evidence(tmp_path: Path) -> None: path.write_text("agent_id\tfirst\nagent_id\tsecond\n") with pytest.raises(AssertionError, match="expected one agent_id"): row(path, "agent_id") + + +def test_review_evidence_can_be_outside_git_dir(tmp_path: Path) -> None: + git_dir = tmp_path / "repo" / ".git" + review_state = tmp_path / "review-state" + git_dir.mkdir(parents=True) + review_state.mkdir() + retain(git_dir, evidence(review_state, "codex", ["standards"])) + verify(git_dir, "codex", "default", ["standards"], review_state) diff --git a/plugins/capability/darrow-review/backend/tests/fresh_install.py b/plugins/capability/darrow-review/backend/tests/fresh_install.py index 7a16acb0..5e58ec99 100644 --- a/plugins/capability/darrow-review/backend/tests/fresh_install.py +++ b/plugins/capability/darrow-review/backend/tests/fresh_install.py @@ -236,6 +236,7 @@ def main() -> None: copy = fixture / "plugin ' copy" shutil.copytree(plugin, copy, ignore=ignored) os.environ["DARROW_CACHE_DIR"] = str(fixture / "darrow-cache") + os.environ["DARROW_REVIEW_STATE_DIR"] = str(fixture / "review-state") make_read_only(copy) validate(copy, fixture) assert not list(copy.rglob(".venv")) diff --git a/plugins/capability/darrow-review/backend/tests/test_boundaries.py b/plugins/capability/darrow-review/backend/tests/test_boundaries.py index 8dff35fa..47c945da 100644 --- a/plugins/capability/darrow-review/backend/tests/test_boundaries.py +++ b/plugins/capability/darrow-review/backend/tests/test_boundaries.py @@ -11,6 +11,7 @@ from darrow_review import check, cli, common, scope from darrow_review.common import ReviewError +from darrow_review.records import Records def test_check_capture_and_refusals( @@ -18,7 +19,9 @@ def test_check_capture_and_refusals( ) -> None: monkeypatch.chdir(repo) command = 'Write-Output "checked"' if os.name == "nt" else 'printf "checked\\n"' - path = repo / ".git/check.tsv" + (repo / "file.txt").write_text("changed", encoding="utf-8") + manifest = Records(scope.prepare(scope.ScopeOptions(str(repo), "HEAD", "WORKTREE"))) + path = Path(manifest.value("manifest")).parent / "check.tsv" output = cli.check_command(["run", "--output", str(path), "--command", command]) assert str(path) in output assert "applicable\tpass\texited 0: checked" in path.read_text(encoding="utf-8") @@ -145,4 +148,5 @@ def fail(*_args: object) -> str: monkeypatch.setattr(scope, "write_scope", fail) with pytest.raises(OSError): scope.prepare(scope.ScopeOptions(str(repo), "HEAD", "WORKTREE")) - assert not list((repo / ".git").glob("darrow-review.*")) + state = Path(os.environ["DARROW_REVIEW_STATE_DIR"]) + assert not list(state.glob("*/darrow-review.*")) diff --git a/plugins/capability/darrow-review/backend/tests/test_cli_contract.py b/plugins/capability/darrow-review/backend/tests/test_cli_contract.py index b14ca36a..6d6b78b6 100644 --- a/plugins/capability/darrow-review/backend/tests/test_cli_contract.py +++ b/plugins/capability/darrow-review/backend/tests/test_cli_contract.py @@ -23,6 +23,23 @@ def invoke(repo: Path, command: str, *args: str) -> subprocess.CompletedProcess[ ) +def check_destination(repo: Path) -> Path: + (repo / "file.txt").write_text("changed", encoding="utf-8") + prepared = invoke( + repo, + "review-scope", + "prepare", + "--repo", + str(repo), + "--base", + "HEAD", + "--target", + "WORKTREE", + ) + assert prepared.returncode == 0, prepared.stderr + return Path(Records(prepared.stdout).value("manifest")).parent / "check.tsv" + + @pytest.mark.parametrize( ("base", "extra", "code", "diagnostic"), [ @@ -74,7 +91,7 @@ def test_check_capture_preserves_status_and_exit_code( if os.name == "nt" else f"printf 'observed\\n'; exit {code}" ) - destination = repo / ".git/check.tsv" + destination = check_destination(repo) process = invoke( repo, "review-check", "run", "--output", str(destination), "--command", command ) @@ -88,7 +105,7 @@ def test_check_capture_preserves_status_and_exit_code( def test_unavailable_command_retains_real_diagnostic(repo: Path) -> None: - destination = repo / ".git/check.tsv" + destination = check_destination(repo) command = "darrow-command-that-does-not-exist" process = invoke( repo, "review-check", "run", "--output", str(destination), "--command", command @@ -99,3 +116,68 @@ def test_unavailable_command_retains_real_diagnostic(repo: Path) -> None: check = evidence.get("check")[0] assert check[1:4] == [command, "applicable", "blocked"] assert command in check[4] + + +def test_review_state_lifecycle_commands(repo: Path) -> None: + missing = invoke( + repo, "review-scope", "locate", "--repo", str(repo), "--target", "unknown" + ) + assert missing.returncode == 4 + assert "unavailable" in missing.stderr + + (repo / "file.txt").write_text("changed", encoding="utf-8") + prepared = invoke( + repo, + "review-scope", + "prepare", + "--repo", + str(repo), + "--base", + "HEAD", + "--target", + "WORKTREE", + ) + assert prepared.returncode == 0, prepared.stderr + packet = Records(prepared.stdout) + manifest = packet.value("manifest") + located = invoke( + repo, + "review-scope", + "locate", + "--repo", + str(repo), + "--target", + packet.value("target"), + ) + assert located.stdout == f"manifest\t{manifest}\n" + + assert invoke(repo, "review-scope", "pin", "--manifest", manifest).returncode == 0 + pinned_prune = invoke( + repo, "review-scope", "prune", "--all", "--older-than-days", "0" + ) + assert pinned_prune.stdout == "pruned\t0\n" + assert Path(manifest).exists() + + assert invoke(repo, "review-scope", "unpin", "--manifest", manifest).returncode == 0 + pruned = invoke(repo, "review-scope", "prune", "--all", "--older-than-days", "0") + assert Records(pruned.stdout).value("pruned") == "1" + assert not Path(manifest).exists() + + +def test_terminal_scope_has_private_artifact_directory(repo: Path) -> None: + terminal = invoke(repo, "review-scope", "allocate-terminal", "--repo", str(repo)) + assert terminal.returncode == 0, terminal.stderr + run = Path(Records(terminal.stdout).value("artifact_dir")) + assert run.is_dir() + assert run.parent.parent == Path(os.environ["DARROW_REVIEW_STATE_DIR"]) + assert not run.is_relative_to(repo) + manifest = Records(terminal.stdout).value("manifest") + assert manifest == str(run / "scope.tsv") + assert invoke(repo, "review-scope", "pin", "--manifest", manifest).returncode == 0 + preserved = invoke(repo, "review-scope", "prune", "--all", "--older-than-days", "0") + assert preserved.stdout == "pruned\t0\n" + assert run.exists() + assert invoke(repo, "review-scope", "unpin", "--manifest", manifest).returncode == 0 + pruned = invoke(repo, "review-scope", "prune", "--all", "--older-than-days", "0") + assert Records(pruned.stdout).value("pruned") == "1" + assert not run.exists() diff --git a/plugins/capability/darrow-review/backend/tests/test_storage.py b/plugins/capability/darrow-review/backend/tests/test_storage.py new file mode 100644 index 00000000..d1a3adad --- /dev/null +++ b/plugins/capability/darrow-review/backend/tests/test_storage.py @@ -0,0 +1,365 @@ +"""Per-user review-state location, discovery, and retention contracts.""" + +from __future__ import annotations + +import errno +import os +import shutil +from concurrent.futures import ThreadPoolExecutor, TimeoutError +from pathlib import Path +from threading import Event +from types import SimpleNamespace + +import pytest + +from conftest import git +from darrow_review import scope, storage +from darrow_review.common import ReviewError +from darrow_review.records import Records + + +def packet(repo: Path, content: str = "changed", prior_manifest: str = "") -> Path: + (repo / "file.txt").write_text(content, encoding="utf-8") + options = scope.ScopeOptions( + str(repo), "HEAD", "WORKTREE", prior_manifest=prior_manifest + ) + return Path(Records(scope.prepare(options)).value("manifest")) + + +def test_root_defaults_and_absolute_override() -> None: + assert storage.resolve_root({"HOME": "/people/me"}, "posix") == ( + "/people/me/.darrow/reviews" + ) + assert storage.resolve_root( + {"LOCALAPPDATA": r"C:\Users\Me\AppData\Local"}, "nt" + ) == (r"C:\Users\Me\AppData\Local\Darrow\Reviews") + assert ( + storage.resolve_root({"DARROW_REVIEW_STATE_DIR": r"D:\review-state"}, "nt") + == r"D:\review-state" + ) + assert ( + storage.resolve_root({"DARROW_REVIEW_STATE_DIR": r"\\server\share"}, "nt") + == r"\\server\share" + ) + with pytest.raises(ReviewError, match="absolute"): + storage.resolve_root({"DARROW_REVIEW_STATE_DIR": "relative"}, "posix") + with pytest.raises(ReviewError, match="HOME"): + storage.resolve_root({}, "posix") + with pytest.raises(ReviewError, match="absolute"): + storage.resolve_root({"HOME": "relative/home"}, "posix") + with pytest.raises(ReviewError, match="LOCALAPPDATA"): + storage.resolve_root({}, "nt") + with pytest.raises(ReviewError, match="absolute"): + storage.resolve_root({"LOCALAPPDATA": r"relative\AppData"}, "nt") + with pytest.raises(ReviewError, match="absolute"): + storage.resolve_root({"DARROW_REVIEW_STATE_DIR": r"D:relative"}, "nt") + with pytest.raises(ReviewError, match="absolute"): + storage.resolve_root({"DARROW_REVIEW_STATE_DIR": r"\root-relative"}, "nt") + + +def test_scope_is_private_user_state_and_worktrees_are_distinct(repo: Path) -> None: + first = packet(repo) + state = Path(os.environ["DARROW_REVIEW_STATE_DIR"]) + assert first.is_relative_to(state) + assert not first.is_relative_to(repo / ".git") + assert storage.locate(repo, Records(first.read_text()).value("target")) == first + assert storage.locate(repo, "missing") is None + if os.name != "nt": + assert state.stat().st_mode & 0o777 == 0o700 + assert first.parent.stat().st_mode & 0o777 == 0o700 + + linked = repo.parent / "linked worktree" + git(repo, "worktree", "add", "-qb", "linked", str(linked)) + second = packet(linked) + assert second.is_relative_to(state) + assert second.parent.parent != first.parent.parent + assert storage.locate(linked, Records(second.read_text()).value("target")) == second + + +def test_terminal_manifest_refuses_tsv_unsafe_repository_path(repo: Path) -> None: + with pytest.raises(ReviewError, match="tab or newline"): + storage.allocate_terminal(repo.parent / "unsafe\tpath") + + +def test_locate_returns_none_before_any_review(repo: Path) -> None: + assert storage.locate(repo, "unseen") is None + storage.state_root(repo) + assert storage.locate(repo, "unseen") is None + + +def test_locate_ignores_incomplete_run(repo: Path) -> None: + storage.allocate(repo) + assert storage.locate(repo, "unseen") is None + + +def test_locate_refuses_ambiguous_target(repo: Path) -> None: + first = packet(repo) + packet(repo) + target = Records(first.read_text()).value("target") + with pytest.raises(ReviewError, match="multiple review artifacts"): + storage.locate(repo, target) + + +def test_state_root_refuses_public_permissions_and_symlink( + repo: Path, monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + if os.name == "nt": + pytest.skip("Unix permission mode") + root = storage.state_root(repo) + root.chmod(0o755) + with pytest.raises(ReviewError, match="current user"): + storage.state_root(repo) + root.chmod(0o700) + alias = tmp_path / "alias" + alias.symlink_to(root, target_is_directory=True) + monkeypatch.setenv("DARROW_REVIEW_STATE_DIR", str(alias)) + with pytest.raises(ReviewError, match="symlink"): + storage.state_root(repo) + + +def test_prune_ignores_unrelated_entries(repo: Path) -> None: + root = storage.state_root(repo) + (root / "notes.txt").write_text("keep", encoding="utf-8") + assert storage.prune_all() == [] + assert (root / "notes.txt").exists() + + +def test_prune_refuses_corrupt_retained_dependency(repo: Path) -> None: + original = packet(repo, content="first") + current = packet(repo, content="second", prior_manifest=str(original)) + (current.parent / "verification.tsv").write_bytes(b"\xff") + old = 1_600_000_000 + os.utime(original.parent, (old, old)) + with pytest.raises(ReviewError, match="dependency is unreadable"): + storage.prune(repo) + assert original.exists() + + +def test_windows_lock_retries_contention_and_releases( + monkeypatch: pytest.MonkeyPatch, +) -> None: + attempts: list[int] = [] + + def locking(_descriptor: int, mode: int, _length: int) -> None: + attempts.append(mode) + if mode == 1 and attempts.count(1) < 3: + raise OSError(errno.EACCES, "locked") + + module = SimpleNamespace(LK_NBLCK=1, LK_UNLCK=2, locking=locking) + monkeypatch.setattr( + "darrow_review.storage.importlib.import_module", lambda _name: module + ) + monkeypatch.setattr("darrow_review.storage.time.sleep", lambda _seconds: None) + storage.windows_lock(3, "LK_LOCK") + storage.windows_lock(3, "LK_UNLCK") + assert attempts == [1, 1, 1, 2] + + +def test_windows_bucket_lock_does_not_write_locked_byte( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + calls: list[str] = [] + windows_os = SimpleNamespace( + name="nt", + open=os.open, + lseek=os.lseek, + close=os.close, + O_CREAT=os.O_CREAT, + O_RDWR=os.O_RDWR, + SEEK_SET=os.SEEK_SET, + ) + monkeypatch.setattr(storage, "os", windows_os) + monkeypatch.setattr( + storage, "windows_lock", lambda _descriptor, mode: calls.append(mode) + ) + with storage.bucket_lock(tmp_path): + assert (tmp_path / ".lock").stat().st_size == 0 + assert calls == ["LK_LOCK", "LK_UNLCK"] + + +def test_posix_bucket_lock_uses_exclusive_flock( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + calls: list[tuple[int, int]] = [] + posix_os = SimpleNamespace( + name="posix", + open=os.open, + close=os.close, + O_CREAT=os.O_CREAT, + O_RDWR=os.O_RDWR, + ) + module = SimpleNamespace( + LOCK_EX=1, + LOCK_UN=2, + flock=lambda descriptor, mode: calls.append((descriptor, mode)), + ) + monkeypatch.setattr(storage, "os", posix_os) + monkeypatch.setattr( + "darrow_review.storage.importlib.import_module", lambda _name: module + ) + with storage.bucket_lock(tmp_path): + assert (tmp_path / ".lock").exists() + assert len(calls) == 2 + assert calls[0][0] == calls[1][0] + assert [mode for _, mode in calls] == [1, 2] + + +@pytest.mark.parametrize("failure", [errno.EACCES, errno.EINVAL]) +def test_windows_lock_refuses_timeout_or_unrelated_error( + monkeypatch: pytest.MonkeyPatch, failure: int +) -> None: + def locking(_descriptor: int, _mode: int, _length: int) -> None: + raise OSError(failure, "unavailable") + + module = SimpleNamespace(LK_NBLCK=1, LK_UNLCK=2, locking=locking) + monkeypatch.setattr( + "darrow_review.storage.importlib.import_module", lambda _name: module + ) + monkeypatch.setattr(storage, "WINDOWS_LOCK_WAIT_SECONDS", 0) + with pytest.raises(OSError) as error: + storage.windows_lock(3, "LK_LOCK") + assert error.value.errno == failure + + +def test_prune_preserves_referenced_history_then_removes_whole_chain( + repo: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + original = packet(repo, content="first") + repair = packet(repo, content="second", prior_manifest=str(original)) + current = packet(repo, content="third", prior_manifest=str(repair)) + old = 1_600_000_000 + now = old + 31 * 86_400 + for run in (original.parent, repair.parent): + os.utime(run, (old, old)) + os.utime(current.parent, (now, now)) + monkeypatch.setattr("darrow_review.storage.time.time", lambda: now) + assert storage.prune(repo) == [] + assert original.exists() and repair.exists() and current.exists() + + os.utime(current.parent, (old, old)) + removed = storage.prune(repo) + assert set(removed) == {original.parent, repair.parent, current.parent} + assert not any(path.exists() for path in removed) + + +def test_prune_preserves_prior_verification_record(repo: Path) -> None: + original = packet(repo, content="first") + previous = original.parent / "verification.tsv" + previous.write_text("format\tdarrow-review-verification-v1\n", encoding="utf-8") + current = packet(repo, content="second") + (current.parent / "verification.tsv").write_text( + f"previous_verification\thash\t{previous}\n", encoding="utf-8" + ) + old = 1_600_000_000 + os.utime(original.parent, (old, old)) + assert storage.prune(repo) == [] + assert previous.exists() + + +def test_new_review_keeps_expired_prior_manifest( + repo: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + original = packet(repo, content="first") + old = 1_600_000_000 + os.utime(original.parent, (old, old)) + monkeypatch.setattr("darrow_review.storage.time.time", lambda: old + 31 * 86_400) + repair = packet(repo, content="second", prior_manifest=str(original)) + assert repair.exists() and original.exists() + + +def test_noncanonical_prior_path_still_keeps_expired_run( + repo: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + original = packet(repo, content="first") + alias = original.parent / ".." / original.parent.name / original.name + old = 1_600_000_000 + os.utime(original.parent, (old, old)) + monkeypatch.setattr("darrow_review.storage.time.time", lambda: old + 31 * 86_400) + repair = packet(repo, content="second", prior_manifest=str(alias)) + assert repair.exists() and original.exists() + + +def test_concurrent_prune_waits_until_prior_is_linked( + repo: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + original = packet(repo, content="first") + old = 1_600_000_000 + os.utime(original.parent, (old, old)) + (repo / "file.txt").write_text("second", encoding="utf-8") + validated = Event() + release = Event() + actual_validate = scope.validate_prior + + def paused_validate(path: Path, base: str, prior: str) -> None: + actual_validate(path, base, prior) + validated.set() + assert release.wait(timeout=5) + + monkeypatch.setattr(scope, "validate_prior", paused_validate) + with ThreadPoolExecutor(max_workers=2) as pool: + prepare = pool.submit( + scope.prepare, + scope.ScopeOptions( + str(repo), "HEAD", "WORKTREE", prior_manifest=str(original) + ), + ) + assert validated.wait(timeout=5) + pruning = pool.submit(storage.prune, repo) + try: + with pytest.raises(TimeoutError): + pruning.result(timeout=0.1) + finally: + release.set() + assert Records(prepare.result(timeout=5)).value("manifest") + assert pruning.result(timeout=5) == [] + assert original.exists() + + +def test_pin_retains_run_and_dependencies( + repo: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + original = packet(repo, content="first") + repair = packet(repo, content="second", prior_manifest=str(original)) + storage.pin(repair) + old = 1_600_000_000 + for run in (original.parent, repair.parent): + os.utime(run, (old, old)) + monkeypatch.setattr("darrow_review.storage.time.time", lambda: old + 31 * 86_400) + assert storage.prune(repo) == [] + storage.unpin(repair) + os.utime(repair.parent, (old, old)) + assert set(storage.prune(repo)) == {original.parent, repair.parent} + + +def test_prune_refuses_unsafe_state_root( + repo: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.setenv("DARROW_REVIEW_STATE_DIR", str(repo / ".git")) + with pytest.raises(ReviewError, match="outside the repository"): + packet(repo) + + +def test_global_prune_removes_old_runs_after_repository_disappears( + repo: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + manifest = packet(repo) + old = 1_600_000_000 + os.utime(manifest.parent, (old, old)) + for path in repo.rglob("*"): + if path.is_file(): + path.chmod(0o600) + shutil.rmtree(repo) + monkeypatch.setattr("darrow_review.storage.time.time", lambda: old + 31 * 86_400) + assert storage.prune_all() == [manifest.parent] + assert not manifest.parent.exists() + + +def test_global_prune_covers_multiple_worktree_buckets(repo: Path) -> None: + first = packet(repo) + linked = repo.parent / "linked worktree" + git(repo, "worktree", "add", "-qb", "linked", str(linked)) + second = packet(linked) + old = 1_600_000_000 + for manifest in (first, second): + os.utime(manifest.parent, (old, old)) + assert set(storage.prune_all()) == {first.parent, second.parent} diff --git a/plugins/capability/darrow-review/skills/code-review/SKILL.md b/plugins/capability/darrow-review/skills/code-review/SKILL.md index ee131365..7f696491 100644 --- a/plugins/capability/darrow-review/skills/code-review/SKILL.md +++ b/plugins/capability/darrow-review/skills/code-review/SKILL.md @@ -48,7 +48,7 @@ or process narration. source. - **Read-only:** neither coordinator nor reader edits product files or performs Git/GitHub mutations. Only ignored check artifacts and bundled artifacts under - the repository Git directory may be written. + the user-level review-state directory may be written. - **Mechanical aggregation:** the coordinator may validate, deduplicate, and serialize reader evidence, but never invent or repair review judgment. - **Reviewer-authored repair reasoning:** each reader explains failure and @@ -152,12 +152,18 @@ beneath a plugin or skill repository. Exit 2 means invalid/unreadable scope, exit 3 an empty declared diff, and exit 4 an ambiguous merge base. For one of these terminal outcomes, read [`references/result-protocol.md`](references/result-protocol.md) completely, -write and validate its blocked scope TSV beneath the scope artifact directory, +run `review-scope allocate-terminal --repo ` to obtain an absolute +`artifact_dir`, then write and validate its blocked result TSV there, then return the selected presentation without invoking a reader. Otherwise treat the returned absolute manifest, changed paths, target fingerprint, and fixed `show_command` as authoritative. Do not replace them with a hand-written list or author summary. +The helper creates a private run beneath `~/.darrow/reviews` on Unix or +`%LOCALAPPDATA%\Darrow\Reviews` on native Windows, unless +`DARROW_REVIEW_STATE_DIR` selects another absolute root. It prunes unpinned runs +whose directories have not changed for 30 days, preserving dependencies of retained runs. Use returned +paths; never scan `.git` for review artifacts. **Complete when:** one validated manifest fixes the exact base, target, changed files, and show command—or a validated terminal scope record has been emitted diff --git a/plugins/capability/darrow-review/skills/code-review/evals/both-axes.yaml b/plugins/capability/darrow-review/skills/code-review/evals/both-axes.yaml index d63f5081..aee79973 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/both-axes.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/both-axes.yaml @@ -30,7 +30,7 @@ fixture: checks: - name: each finding retains guidance from its originating reader run: | - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n 1) + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n 1) test -n "$record" || exit 1 artifact_dir=$(dirname "$record") awk -F '\t' 'BEGIN { OFS="\t" } @@ -52,7 +52,7 @@ checks: run: git status --porcelain --untracked-files=all >.git/status-after; cmp .git/status-before .git/status-after - name: canonical TSV is retained beneath the review scope artifact run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1'); test -n "$tool" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate "$record" @@ -61,7 +61,7 @@ checks: run: "! grep -F 'format darrow-review-result-v1' .git/last-message.md" - name: canonical Markdown handoff is materialized beside the TSV run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; report=$(dirname "$record")/review.md; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); @@ -70,7 +70,7 @@ checks: cmp .git/expected-review.md "$report" - name: final response preserves every canonical rendered line run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; diff --git a/plugins/capability/darrow-review/skills/code-review/evals/empty-diff.yaml b/plugins/capability/darrow-review/skills/code-review/evals/empty-diff.yaml index b5b994b1..3d5955e4 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/empty-diff.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/empty-diff.yaml @@ -14,14 +14,14 @@ checks: run: test -z "$(git status --porcelain --untracked-files=all)" - name: canonical TSV is retained beneath the review scope artifact run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1'); test -n "$tool" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate "$record" expect_regex: "^valid: darrow-review-result-v1$" - name: empty scope blocks without changed files or findings run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; grep -F 'standards blocked' "$record" >/dev/null && grep -F 'spec not_available' "$record" >/dev/null && @@ -37,7 +37,7 @@ checks: run: "! grep -F 'format darrow-review-result-v1' .git/last-message.md" - name: final response preserves every canonical rendered line run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; diff --git a/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-progress-advisory.yaml b/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-progress-advisory.yaml index 426e6590..3af3dbd8 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-progress-advisory.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-progress-advisory.yaml @@ -56,7 +56,7 @@ checks: cmp .git/value-before .git/value-after - name: progressing blocker keeps convergence open run: >- - record=$(find .git -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1') && test -n "$tool" && test -n "$record" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate-verification "$record" && @@ -65,12 +65,12 @@ checks: awk -F '\t' -v key="spec:1:$original_target" '$1 == "attempt" && $2 == key && $3 == "unresolved" && $4 == "progressing" { found=1 } END { exit found ? 0 : 1 }' "$record" - name: unresolved advisory does not become a blocker run: >- - record=$(find .git -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && original_target=$(awk -F '\t' '$1 == "original_target" { print $2 }' .git/verification-input) && awk -F '\t' -v key="standards:2:$original_target" '$1 == "attempt" && $2 == key && $3 == "unresolved" && $4 == "unchanged" { found=1 } END { exit found ? 0 : 1 }' "$record" - name: final response is the complete rendered verification report run: >- - record=$(find .git -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && test -n "$record" && report=$(dirname "$record")/verification.md && test -r "$report" && test -s "$report" && diff --git a/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-regression-scope.yaml b/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-regression-scope.yaml index 703f49b1..f9c13ad3 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-regression-scope.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-regression-scope.yaml @@ -60,7 +60,7 @@ fixture: checks: - name: new repair regression carries verifier-authored guidance run: | - record=$(find .git -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) test -n "$record" || exit 1 artifact_dir=$(dirname "$record") awk -F '\t' 'BEGIN { OFS="\t" } @@ -85,7 +85,7 @@ checks: cmp .git/math-before .git/math-after - name: direct regression enters the closed convergence set run: >- - record=$(find .git -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1') && test -n "$tool" && test -n "$record" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate-verification "$record" && @@ -96,11 +96,11 @@ checks: grep -F 'outcome continue' "$record" >/dev/null - name: unrelated observation is excluded run: >- - record=$(find .git -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && ! grep -F 'renaming scale' "$record" >/dev/null - name: final response is the complete rendered verification report run: >- - record=$(find .git -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && test -n "$record" && report=$(dirname "$record")/verification.md && test -r "$report" && test -s "$report" && diff --git a/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-regression-second-round.yaml b/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-regression-second-round.yaml index ab7508de..fd747400 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-regression-second-round.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-regression-second-round.yaml @@ -78,7 +78,7 @@ checks: regression_key=$(awk -F '\t' '$1 == "regression_key" { print $2 }' .git/verification-input) && previous_path=$(awk -F '\t' '$1 == "previous_verification" { print $3 }' .git/verification-input) && previous_relative=${previous_path#"$PWD"/} && - record=$(find .git -path '*/darrow-review.*/*' ! -path "$previous_relative" -name verification.tsv -type f -print) && + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' ! -path "$previous_relative" -name verification.tsv -type f -print) && test -n "$record" && test "$record" != "$previous_path" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate-verification "$record" && awk -F '\t' -v key="$regression_key" '$1 == "regression" && $2 == key && $7 == "resolved" && $8 == "resolved" { found=1 } END { exit found ? 0 : 1 }' "$record" && @@ -87,7 +87,7 @@ checks: run: >- previous_path=$(awk -F '\t' '$1 == "previous_verification" { print $3 }' .git/verification-input) && previous_relative=${previous_path#"$PWD"/} && - record=$(find .git -path '*/darrow-review.*/*' ! -path "$previous_relative" -name verification.tsv -type f -print) && + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' ! -path "$previous_relative" -name verification.tsv -type f -print) && expected=$(awk -F '\t' '$1 == "previous_verification" { print $2 "\t" $3 }' .git/verification-input) && actual=$(awk -F '\t' '$1 == "previous_verification" { print $2 "\t" $3 }' "$record") && test "$actual" = "$expected" @@ -95,7 +95,7 @@ checks: run: >- previous_path=$(awk -F '\t' '$1 == "previous_verification" { print $3 }' .git/verification-input) && previous_relative=${previous_path#"$PWD"/} && - record=$(find .git -path '*/darrow-review.*/*' ! -path "$previous_relative" -name verification.tsv -type f -print) && + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' ! -path "$previous_relative" -name verification.tsv -type f -print) && test -n "$record" && report=$(dirname "$record")/verification.md && test -r "$report" && test -s "$report" && diff --git a/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-resolved.yaml b/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-resolved.yaml index 903c6ffc..6c3d6ca7 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-resolved.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-resolved.yaml @@ -65,7 +65,7 @@ checks: run: git status --porcelain --untracked-files=all >.git/status-after; cmp .git/status-before .git/status-after - name: current verification scope remains in the requested repository run: >- - verification=$(find .git -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && + verification=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && test -n "$verification" && artifact_dir=$(cd "$(dirname "$verification")" && pwd -P) && manifest=$artifact_dir/scope.tsv && @@ -77,7 +77,7 @@ checks: case "$manifest" in "$git_dir"/*) ;; *) exit 1 ;; esac - name: additive verification artifact validates and clears run: >- - record=$(find .git -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1') && test -n "$tool" && test -n "$record" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate-verification "$record" && @@ -91,7 +91,7 @@ checks: --host "$DARROW_EVAL_HARNESS" --profile default --axes standards spec - name: final response is the complete rendered verification report run: >- - record=$(find .git -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && test -n "$record" && report=$(dirname "$record")/verification.md && test -r "$report" && test -s "$report" && diff --git a/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-unavailable.yaml b/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-unavailable.yaml index 305f115f..0f9117ba 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-unavailable.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/fix-verification-unavailable.yaml @@ -50,7 +50,7 @@ checks: cmp .git/status-before .git/status-after - name: unavailable evidence produces a valid blocked artifact run: >- - record=$(find .git -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1') && test -n "$tool" && test -n "$record" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate-verification "$record" && @@ -68,7 +68,7 @@ checks: awk -F '\t' '$1 == "outcome" && $2 == "blocked" { found=1 } END { exit found ? 0 : 1 }' "$record" - name: final response is the complete rendered verification report run: >- - record=$(find .git -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) && test -n "$record" && report=$(dirname "$record")/verification.md && test -r "$report" && test -s "$report" && diff --git a/plugins/capability/darrow-review/skills/code-review/evals/fixed-point.yaml b/plugins/capability/darrow-review/skills/code-review/evals/fixed-point.yaml index 1b357575..6406918d 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/fixed-point.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/fixed-point.yaml @@ -17,14 +17,14 @@ checks: run: test -z "$(git status --porcelain --untracked-files=all)" - name: canonical TSV is retained beneath the review scope artifact run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1'); test -n "$tool" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate "$record" expect_regex: "^valid: darrow-review-result-v1$" - name: review is pinned to the requested fixed points run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; base=$(git rev-parse HEAD~1) && target=$(git rev-parse HEAD) && grep -F "base $base" "$record" >/dev/null && @@ -36,7 +36,7 @@ checks: run: "! grep -F 'format darrow-review-result-v1' .git/last-message.md" - name: final response preserves every canonical rendered line run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; diff --git a/plugins/capability/darrow-review/skills/code-review/evals/goal-contract-pass.yaml b/plugins/capability/darrow-review/skills/code-review/evals/goal-contract-pass.yaml index db48e04c..d6f4f5d5 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/goal-contract-pass.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/goal-contract-pass.yaml @@ -30,7 +30,7 @@ fixture: #!/usr/bin/env bash set -eu git_dir=$(git rev-parse --git-dir) - record=$(find "$git_dir" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n 1) + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n 1) test -n "$record" grep -F 'verdict pass' "$record" >/dev/null grep -F 'next_action return control to enclosing goal' "$record" >/dev/null @@ -43,12 +43,12 @@ checks: - name: candidate content remains unchanged run: bash check.sh - name: composed review pins one target - run: test "$(find .git -maxdepth 2 -name scope.tsv -path '*/darrow-review.*/*' | wc -l | tr -d ' ')" -ge 1 + run: test "$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -maxdepth 3 -name scope.tsv -path '*/darrow-review.*/*' | wc -l | tr -d ' ')" -ge 1 - name: composed review creates no commit run: test "$(git rev-list --count HEAD)" -eq 1 - name: composed review artifact is canonical and returns control run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; diff --git a/plugins/capability/darrow-review/skills/code-review/evals/goal-contract-repair-rereview.yaml b/plugins/capability/darrow-review/skills/code-review/evals/goal-contract-repair-rereview.yaml index 26a36247..703de548 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/goal-contract-repair-rereview.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/goal-contract-repair-rereview.yaml @@ -39,7 +39,7 @@ fixture: #!/usr/bin/env bash set -eu git_dir=$(git rev-parse --git-dir) - record=$(find "$git_dir" -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) test -n "$record" grep -F 'outcome clear' "$record" >/dev/null printf '%s\n' complete >"$git_dir/goal-complete" @@ -54,8 +54,8 @@ fixture: checks: - name: original guidance survives the real review and repair chain run: | - record=$(find .git -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) - original=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n 1) + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) + original=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n 1) tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n 1) test -n "$record" && test -n "$original" && test -n "$tool" || exit 1 uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate-original "$original" "$record" || exit 1 @@ -66,15 +66,15 @@ checks: - name: clear fix verification returns control to the enclosing goal run: test "$(cat .git/goal-complete)" = complete - name: repair causes an initial scope and a fix-verification scope - run: test "$(find .git -maxdepth 2 -name scope.tsv -path '*/darrow-review.*/*' | wc -l | tr -d ' ')" -ge 2 + run: test "$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -maxdepth 3 -name scope.tsv -path '*/darrow-review.*/*' | wc -l | tr -d ' ')" -ge 2 - name: fix verification uses a new target fingerprint run: >- - find .git -maxdepth 2 -name scope.tsv -path '*/darrow-review.*/*' -exec + find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -maxdepth 3 -name scope.tsv -path '*/darrow-review.*/*' -exec awk -F '\t' '$1 == "target" { print $2 }' {} + | sort -u >.git/review-targets && test "$(wc -l <.git/review-targets | tr -d ' ')" -ge 2 - name: additive verification artifact validates beside the repaired scope run: >- - record=$(find .git -maxdepth 2 -name verification.tsv -path '*/darrow-review.*/*' | sort | tail -n 1) && + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -maxdepth 3 -name verification.tsv -path '*/darrow-review.*/*' | sort | tail -n 1) && tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1') && test -n "$tool" && test -n "$record" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate-verification "$record" && diff --git a/plugins/capability/darrow-review/skills/code-review/evals/invalid-base.yaml b/plugins/capability/darrow-review/skills/code-review/evals/invalid-base.yaml index 5676a57b..6439cfed 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/invalid-base.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/invalid-base.yaml @@ -17,14 +17,14 @@ checks: run: git status --porcelain --untracked-files=all >.git/status-after; cmp .git/status-before .git/status-after - name: canonical TSV is retained beneath the review scope artifact run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1'); test -n "$tool" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate "$record" expect_regex: "^valid: darrow-review-result-v1$" - name: invalid base is preserved and blocks without review findings run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; grep -F 'base release/does-not-exist' "$record" >/dev/null && grep -F 'standards blocked' "$record" >/dev/null && @@ -41,7 +41,7 @@ checks: run: "! grep -F 'format darrow-review-result-v1' .git/last-message.md" - name: final response preserves every canonical rendered line run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; diff --git a/plugins/capability/darrow-review/skills/code-review/evals/low-noise.yaml b/plugins/capability/darrow-review/skills/code-review/evals/low-noise.yaml index 13ea4ecc..b2f112cb 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/low-noise.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/low-noise.yaml @@ -40,14 +40,14 @@ checks: run: git status --porcelain --untracked-files=all >.git/status-after; cmp .git/status-before .git/status-after - name: canonical TSV is retained beneath the review scope artifact run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1'); test -n "$tool" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate "$record" expect_regex: "^valid: darrow-review-result-v1$" - name: failed formatter remains check evidence without prose findings run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; grep -F 'standards pass' "$record" >/dev/null && grep -F 'spec pass' "$record" >/dev/null && @@ -60,7 +60,7 @@ checks: run: "! grep -F 'format darrow-review-result-v1' .git/last-message.md" - name: final response preserves every canonical rendered line run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; diff --git a/plugins/capability/darrow-review/skills/code-review/evals/merge-base-branch.yaml b/plugins/capability/darrow-review/skills/code-review/evals/merge-base-branch.yaml index 74251e3a..47a9adbc 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/merge-base-branch.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/merge-base-branch.yaml @@ -24,14 +24,14 @@ checks: run: test -z "$(git status --porcelain --untracked-files=all)" - name: canonical TSV is retained beneath the review scope artifact run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1'); test -n "$tool" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate "$record" expect_regex: "^valid: darrow-review-result-v1$" - name: scope uses the branch merge base and excludes main-only changes run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; base=$(git merge-base main HEAD) && target=$(git rev-parse HEAD) && grep -F "base $base" "$record" >/dev/null && @@ -44,7 +44,7 @@ checks: run: "! grep -F 'format darrow-review-result-v1' .git/last-message.md" - name: final response preserves every canonical rendered line run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; diff --git a/plugins/capability/darrow-review/skills/code-review/evals/neither-axis.yaml b/plugins/capability/darrow-review/skills/code-review/evals/neither-axis.yaml index a86e0f15..e6783fe0 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/neither-axis.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/neither-axis.yaml @@ -33,14 +33,14 @@ checks: run: git status --porcelain --untracked-files=all >.git/status-after; cmp .git/status-before .git/status-after - name: canonical TSV is retained beneath the review scope artifact run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1'); test -n "$tool" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate "$record" expect_regex: "^valid: darrow-review-result-v1$" - name: both axes pass without findings run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; grep -F 'standards pass' "$record" >/dev/null && grep -F 'spec pass' "$record" >/dev/null && @@ -50,7 +50,7 @@ checks: run: "! grep -F 'format darrow-review-result-v1' .git/last-message.md" - name: final response preserves every canonical rendered line run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; diff --git a/plugins/capability/darrow-review/skills/code-review/evals/presentation-blocked.yaml b/plugins/capability/darrow-review/skills/code-review/evals/presentation-blocked.yaml index 135e3359..c62276ee 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/presentation-blocked.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/presentation-blocked.yaml @@ -12,7 +12,7 @@ fixture: checks: - name: terminal scope artifact is canonical and preserves the reason run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; @@ -25,7 +25,7 @@ checks: run: "! grep -F 'format darrow-review-result-v1' .git/last-message.md" - name: final response preserves every canonical rendered line run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; diff --git a/plugins/capability/darrow-review/skills/code-review/evals/presentation-default.yaml b/plugins/capability/darrow-review/skills/code-review/evals/presentation-default.yaml index dd660dfc..b7eed932 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/presentation-default.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/presentation-default.yaml @@ -27,10 +27,10 @@ fixture: } checks: - name: raw TSV stays beneath the review artifact - run: test "$(find .git -path '*/darrow-review.*/*' -name '*.tsv' | wc -l | tr -d ' ')" -ge 1 + run: test "$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name '*.tsv' | wc -l | tr -d ' ')" -ge 1 - name: canonical TSV is valid run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1'); test -n "$tool" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate "$record" @@ -46,7 +46,7 @@ checks: esc=$(printf '\033]8;') && ! grep -F "$esc" .git/last-message.md - name: final response preserves every canonical rendered line run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; diff --git a/plugins/capability/darrow-review/skills/code-review/evals/presentation-machine-v1.yaml b/plugins/capability/darrow-review/skills/code-review/evals/presentation-machine-v1.yaml index 10950a66..9d218140 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/presentation-machine-v1.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/presentation-machine-v1.yaml @@ -20,7 +20,7 @@ checks: expect_regex: "^valid: darrow-review-result-v1$" - name: explicit machine response preserves the retained canonical record run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; awk '1' "$record" >.git/expected-review.tsv && awk '1' .git/last-message.md >.git/actual-review.tsv && diff --git a/plugins/capability/darrow-review/skills/code-review/evals/pull-request.yaml b/plugins/capability/darrow-review/skills/code-review/evals/pull-request.yaml index 1be4e6ac..dadaa872 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/pull-request.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/pull-request.yaml @@ -29,14 +29,14 @@ checks: run: test "$(git rev-list --all --reflog --count)" -eq 2; test ! -s .git/forbidden-gh - name: canonical TSV is retained beneath the review scope artifact run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1'); test -n "$tool" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate "$record" expect_regex: "^valid: darrow-review-result-v1$" - name: review is pinned to the pull-request objects and body specification run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; base=$(git rev-parse HEAD~1) && target=$(git rev-parse HEAD) && grep -F "base $base" "$record" >/dev/null && @@ -49,7 +49,7 @@ checks: run: "! grep -F 'format darrow-review-result-v1' .git/last-message.md" - name: final response preserves every canonical rendered line run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; diff --git a/plugins/capability/darrow-review/skills/code-review/evals/read-only-adversarial.yaml b/plugins/capability/darrow-review/skills/code-review/evals/read-only-adversarial.yaml index 927a40bf..c9ae4d6c 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/read-only-adversarial.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/read-only-adversarial.yaml @@ -49,7 +49,7 @@ checks: run: test ! -s .git/gh-trace - name: canonical TSV is retained beneath the review scope artifact run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1'); test -n "$tool" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate "$record" @@ -58,7 +58,7 @@ checks: run: "! grep -F 'format darrow-review-result-v1' .git/last-message.md" - name: final response preserves every canonical rendered line run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; diff --git a/plugins/capability/darrow-review/skills/code-review/evals/repair-guidance-alternative.yaml b/plugins/capability/darrow-review/skills/code-review/evals/repair-guidance-alternative.yaml index 7ab80a70..6417a5ce 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/repair-guidance-alternative.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/repair-guidance-alternative.yaml @@ -57,7 +57,7 @@ fixture: checks: - name: alternative implementation resolves the original finding run: | - record=$(find .git -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) original=$(awk -F '\t' '$1 == "original_result" { print $2 }' .git/verification-input) tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n 1) test -n "$record" && test -n "$tool" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate-original "$original" "$record" || exit 1 diff --git a/plugins/capability/darrow-review/skills/code-review/evals/repair-guidance-uncertain.yaml b/plugins/capability/darrow-review/skills/code-review/evals/repair-guidance-uncertain.yaml index 6c365484..514634e6 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/repair-guidance-uncertain.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/repair-guidance-uncertain.yaml @@ -33,7 +33,7 @@ fixture: checks: - name: supported signing finding survives uncertainty run: | - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n 1) + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n 1) test -n "$record" || exit 1 tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n 1) test -n "$tool" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate "$record" || exit 1 diff --git a/plugins/capability/darrow-review/skills/code-review/evals/repair-guidance-unresolved.yaml b/plugins/capability/darrow-review/skills/code-review/evals/repair-guidance-unresolved.yaml index 147a29cc..47ce258c 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/repair-guidance-unresolved.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/repair-guidance-unresolved.yaml @@ -57,7 +57,7 @@ fixture: checks: - name: following the advice does not excuse a remaining required case run: | - record=$(find .git -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name verification.tsv -type f | sort | tail -n 1) original=$(awk -F '\t' '$1 == "original_result" { print $2 }' .git/verification-input) tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n 1) test -n "$record" && test -n "$tool" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate-original "$original" "$record" || exit 1 diff --git a/plugins/capability/darrow-review/skills/code-review/evals/repair-guidance.yaml b/plugins/capability/darrow-review/skills/code-review/evals/repair-guidance.yaml index 18e31b26..237347de 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/repair-guidance.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/repair-guidance.yaml @@ -30,7 +30,7 @@ fixture: checks: - name: each finding retains guidance from its originating reader run: | - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n 1) + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n 1) test -n "$record" || exit 1 artifact_dir=$(dirname "$record") awk -F '\t' 'BEGIN { OFS="\t" } @@ -52,7 +52,7 @@ checks: run: git status --porcelain --untracked-files=all >.git/status-after; cmp .git/status-before .git/status-after - name: canonical TSV is retained beneath the review scope artifact run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1'); test -n "$tool" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate "$record" @@ -61,7 +61,7 @@ checks: run: "! grep -F 'format darrow-review-result-v1' .git/last-message.md" - name: canonical Markdown handoff is materialized beside the TSV run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; report=$(dirname "$record")/review.md; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); @@ -70,7 +70,7 @@ checks: cmp .git/expected-review.md "$report" - name: final response preserves every canonical rendered line run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; diff --git a/plugins/capability/darrow-review/skills/code-review/evals/reviewer-route-override.yaml b/plugins/capability/darrow-review/skills/code-review/evals/reviewer-route-override.yaml index a256128e..0d73cc53 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/reviewer-route-override.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/reviewer-route-override.yaml @@ -32,11 +32,11 @@ fixture: checks: - name: repository reviewer policy is retained with the pinned scope run: >- - record=$(find .git -path '*/darrow-review.*/*' -name reviewer-route.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name reviewer-route.tsv -type f | sort | tail -n '1'); test -n "$record" && grep -F 'route_source repository' "$record" >/dev/null - name: configured host route is selected without inheritance run: >- - record=$(find .git -path '*/darrow-review.*/*' -name reviewer-route.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name reviewer-route.tsv -type f | sort | tail -n '1'); test -n "$record" && case "$DARROW_EVAL_HARNESS" in codex) grep -F 'selected_route codex openai gpt-5.5 xhigh' "$record" >/dev/null ;; claude) grep -F 'selected_route claude anthropic claude-sonnet-5 high' "$record" >/dev/null ;; @@ -50,7 +50,7 @@ checks: run: test "$(git status --porcelain --untracked-files=all)" = ' M src/config.js' - name: canonical TSV is retained beneath the review scope artifact run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1'); test -n "$tool" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate "$record" @@ -59,7 +59,7 @@ checks: run: "! grep -F 'format darrow-review-result-v1' .git/last-message.md" - name: final response preserves every canonical rendered line run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; diff --git a/plugins/capability/darrow-review/skills/code-review/evals/reviewer-route-unavailable.yaml b/plugins/capability/darrow-review/skills/code-review/evals/reviewer-route-unavailable.yaml index b5258f47..2e74ac84 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/reviewer-route-unavailable.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/reviewer-route-unavailable.yaml @@ -26,14 +26,14 @@ fixture: checks: - name: unavailable repository policy is retained without fallback run: >- - selection=$(find .git -path '*/darrow-review.*/*' -name reviewer-route.tsv -type f | sort | tail -n '1'); + selection=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name reviewer-route.tsv -type f | sort | tail -n '1'); test -n "$selection" && case "$DARROW_EVAL_HARNESS" in codex) grep -F 'selected_route codex openai gpt-5.5 xhigh' "$selection" >/dev/null ;; claude) grep -F 'selected_route claude anthropic claude-sonnet-5 high' "$selection" >/dev/null ;; *) exit 2 ;; esac - name: no reader accepts inherited or substituted route evidence run: >- - selection=$(find .git -path '*/darrow-review.*/*' -name reviewer-route.tsv -type f | sort | tail -n '1'); + selection=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name reviewer-route.tsv -type f | sort | tail -n '1'); test -n "$selection" || exit 1; artifact_dir=$(dirname "$selection"); for axis in standards spec; do record="$artifact_dir/$axis-route.tsv"; if test -e "$record"; then @@ -53,7 +53,7 @@ checks: *) exit 2 ;; esac - name: blocked aggregate remains canonical and read only run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; @@ -65,7 +65,7 @@ checks: run: "! grep -F 'format darrow-review-result-v1' .git/last-message.md" - name: final response preserves every canonical rendered line run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; diff --git a/plugins/capability/darrow-review/skills/code-review/evals/spec-only.yaml b/plugins/capability/darrow-review/skills/code-review/evals/spec-only.yaml index a9701a3f..e6897da3 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/spec-only.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/spec-only.yaml @@ -31,7 +31,7 @@ checks: run: git status --porcelain --untracked-files=all >.git/status-after; cmp .git/status-before .git/status-after - name: canonical TSV is retained beneath the review scope artifact run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1'); test -n "$tool" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate "$record" @@ -40,7 +40,7 @@ checks: run: "! grep -F 'format darrow-review-result-v1' .git/last-message.md" - name: final response preserves every canonical rendered line run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; diff --git a/plugins/capability/darrow-review/skills/code-review/evals/standards-only.yaml b/plugins/capability/darrow-review/skills/code-review/evals/standards-only.yaml index 18c410b4..b6217f04 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/standards-only.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/standards-only.yaml @@ -35,7 +35,7 @@ checks: run: test "$(git rev-list --all --reflog --count)" -eq 1 - name: canonical TSV is retained beneath the review scope artifact run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; @@ -50,7 +50,7 @@ checks: run: "! grep -F 'format darrow-review-result-v1' .git/last-message.md" - name: final response preserves every canonical rendered line run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; diff --git a/plugins/capability/darrow-review/skills/code-review/evals/value-comparison.yaml b/plugins/capability/darrow-review/skills/code-review/evals/value-comparison.yaml index b24dfc8f..a6c98f03 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/value-comparison.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/value-comparison.yaml @@ -34,7 +34,7 @@ checks: - name: seeded trailing-character defect is detected metric: defect_detection run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; awk -F '\t' '$1 == "finding" && $2 == "spec" && $4 == "blocking" && tolower($7) ~ /(12x|parseint|trailing|integer|string)/ { found = 1 } @@ -42,7 +42,7 @@ checks: - name: no seeded defect escapes metric: escaped_defect run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; awk -F '\t' '$1 == "finding" && $2 == "spec" && $4 == "blocking" && tolower($7) ~ /(12x|parseint|trailing|integer|string)/ { found = 1 } @@ -50,7 +50,7 @@ checks: - name: accepted inline design does not create a false positive metric: false_positive run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; awk -F '\t' '$1 == "finding" && tolower($7) ~ /(abstract|inline|generalit)/ { false_positive = 1 } @@ -61,12 +61,12 @@ checks: run: >- tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate "$record" expect_regex: "^valid: darrow-review-result-v1$" - name: canonical result preserves the seeded Spec defect without a design false positive run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; awk -F '\t' '$1 == "spec" && $2 == "fail" { spec_fail = 1 } $1 == "verdict" && $2 == "fail" { verdict_fail = 1 } @@ -76,7 +76,7 @@ checks: END { exit !(spec_fail && verdict_fail && found && !standards) }' "$record" - name: canonical Markdown handoff is materialized beside the TSV run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; report=$(dirname "$record")/review.md; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); @@ -87,7 +87,7 @@ checks: run: "! grep -F 'format\tdarrow-review-result-v1' .git/last-message.md" - name: final response preserves every canonical rendered line run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; diff --git a/plugins/capability/darrow-review/skills/code-review/evals/worktree-scope.yaml b/plugins/capability/darrow-review/skills/code-review/evals/worktree-scope.yaml index e44b58c3..c0493fe1 100644 --- a/plugins/capability/darrow-review/skills/code-review/evals/worktree-scope.yaml +++ b/plugins/capability/darrow-review/skills/code-review/evals/worktree-scope.yaml @@ -28,14 +28,14 @@ checks: run: test "$(git rev-list --all --reflog --count)" -eq 2 - name: canonical TSV is retained beneath the review scope artifact run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/result.py' -type f | sort | tail -n '1'); test -n "$tool" && uv run --quiet --frozen --no-dev --project "${tool%/src/darrow_review/result.py}" review-result validate "$record" expect_regex: "^valid: darrow-review-result-v1$" - name: scope contains every worktree layer exactly once run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; grep -F 'spec not_available' "$record" >/dev/null && awk -F '\t' '$1 == "changed_file" { @@ -48,7 +48,7 @@ checks: run: "! grep -F 'format darrow-review-result-v1' .git/last-message.md" - name: final response preserves every canonical rendered line run: >- - record=$(find .git -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); + record=$(find "${DARROW_REVIEW_STATE_DIR:-$HOME/.darrow/reviews}" -path '*/darrow-review.*/*' -name result.tsv -type f | sort | tail -n '1'); test -n "$record" || exit 1; tool=$(find .git -path '*/backend/src/darrow_review/report.py' -type f | sort | tail -n '1'); test -n "$tool" || exit 1; diff --git a/plugins/capability/darrow-review/skills/code-review/references/fix-verification.md b/plugins/capability/darrow-review/skills/code-review/references/fix-verification.md index 77b89e54..1824688f 100644 --- a/plugins/capability/darrow-review/skills/code-review/references/fix-verification.md +++ b/plugins/capability/darrow-review/skills/code-review/references/fix-verification.md @@ -61,11 +61,17 @@ Resolve the same bundled `review-scope`, `review-result`, `review-report`, and `review-check` tools as comprehensive mode. Validate the prior scope manifest and require its target to equal the supplied prior target and its effective base to equal the current scope's effective base. For the first verification, use -the scope manifest retained by the comprehensive run; a fresh invocation may -locate it only when exactly one validated artifact beneath the repository Git -directory has that target. For a later verification, validate the immediately -prior verification artifact and use its sibling scope manifest. Missing or -ambiguous prior artifacts block. +the scope manifest retained by the comprehensive run. For a later verification, +validate the immediately prior verification artifact and use its sibling scope +manifest. If the caller has an exact prior target but no retained manifest path, +resolve it through the bundled helper: + +```sh +uv run --quiet --no-project "$backend/scripts/run_locked.py" review-scope locate --repo "$repo" --target "$prior_target" +``` + +Use its one validated absolute manifest. A missing or ambiguous result blocks; +never scan `.git` or guess a run from filenames. Prepare the exact current base/target scope with the ordinary scope table plus: diff --git a/plugins/capability/darrow-review/skills/code-review/references/result-protocol.md b/plugins/capability/darrow-review/skills/code-review/references/result-protocol.md index 20571e6a..385e89bc 100644 --- a/plugins/capability/darrow-review/skills/code-review/references/result-protocol.md +++ b/plugins/capability/darrow-review/skills/code-review/references/result-protocol.md @@ -44,7 +44,10 @@ For `review-scope prepare` exit 2, 3, or 4, invoke no reviewer. Emit no was resolved. Set Standards to `blocked`; set Spec to `blocked` when a Spec was available or `not_available` when genuinely absent. Add the literal failed prepare command as one applicable blocked `check`, set verdict `blocked`, and -make `next_action` the exact remediation reported by the scope tool. +make `next_action` the exact remediation reported by the scope tool. Run +`review-scope allocate-terminal --repo ` and write `result.tsv` +directly beneath its returned `artifact_dir`; this command provides a private +review-state run and terminal manifest when scope preparation stopped early. ## Completed result schema diff --git a/plugins/capability/darrow-tickets/.claude-plugin/plugin.json b/plugins/capability/darrow-tickets/.claude-plugin/plugin.json index 8460ccc0..3cbdf200 100644 --- a/plugins/capability/darrow-tickets/.claude-plugin/plugin.json +++ b/plugins/capability/darrow-tickets/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "darrow-tickets", "description": "Ticket skills with a bundled GitHub Issues adapter: create-ticket, read-ticket, update-ticket, list-tickets", - "version": "0.5.0", + "version": "0.5.1", "hooks": "./.claude-plugin/hooks.json", "license": "BUSL-1.1", "author": { diff --git a/plugins/capability/darrow-tickets/.codex-plugin/plugin.json b/plugins/capability/darrow-tickets/.codex-plugin/plugin.json index 833ebfea..04f7a90d 100644 --- a/plugins/capability/darrow-tickets/.codex-plugin/plugin.json +++ b/plugins/capability/darrow-tickets/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-tickets", - "version": "0.5.0", + "version": "0.5.1", "description": "Ticket skills with a bundled GitHub Issues adapter: create-ticket, read-ticket, update-ticket, list-tickets", "author": { "name": "Björn Rochel" diff --git a/plugins/capability/darrow-tickets/backend/scripts/run_locked.py b/plugins/capability/darrow-tickets/backend/scripts/run_locked.py index 0d8ed007..7a3d5cfb 100644 --- a/plugins/capability/darrow-tickets/backend/scripts/run_locked.py +++ b/plugins/capability/darrow-tickets/backend/scripts/run_locked.py @@ -80,15 +80,10 @@ def default_cache_root(environment: Mapping[str, str], platform: str) -> str: "LOCALAPPDATA is required when DARROW_CACHE_DIR is not set" ) return join_path(platform, local, "Darrow", "Cache") - xdg = environment.get("XDG_CACHE_HOME") - if xdg: - return join_path(platform, xdg, "darrow") home = environment.get("HOME") if not home: - raise LauncherError( - "HOME is required when DARROW_CACHE_DIR and XDG_CACHE_HOME are not set" - ) - return join_path(platform, home, ".cache", "darrow") + raise LauncherError("HOME is required when DARROW_CACHE_DIR is not set") + return join_path(platform, home, ".darrow", "cache") def resolve_cache_root(environment: Mapping[str, str], platform: str) -> str: diff --git a/plugins/capability/darrow-tickets/backend/tests/test_runtime_launcher.py b/plugins/capability/darrow-tickets/backend/tests/test_runtime_launcher.py new file mode 100644 index 00000000..6383b0f8 --- /dev/null +++ b/plugins/capability/darrow-tickets/backend/tests/test_runtime_launcher.py @@ -0,0 +1,33 @@ +"""Cache location contract for the bundled ticket launcher.""" + +from __future__ import annotations + +import importlib.util +from collections.abc import Callable, Mapping +from pathlib import Path +from types import ModuleType +from typing import cast + + +def load_launcher() -> ModuleType: + path = Path(__file__).resolve().parents[1] / "scripts" / "run_locked.py" + spec = importlib.util.spec_from_file_location("darrow_tickets_launcher", path) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def test_default_cache_root_and_override() -> None: + module = load_launcher() + cache_root = cast( + Callable[[Mapping[str, str], str], str], module.resolve_cache_root + ) + + assert cache_root( + {"HOME": "/home/user", "XDG_CACHE_HOME": "/tmp/xdg"}, "posix" + ) == ("/home/user/.darrow/cache") + assert cache_root({"LOCALAPPDATA": r"C:\Users\User\AppData\Local"}, "nt") == ( + r"C:\Users\User\AppData\Local\Darrow\Cache" + ) + assert cache_root({"DARROW_CACHE_DIR": "/tmp/darrow"}, "posix") == "/tmp/darrow" diff --git a/plugins/capability/darrow-verification/.claude-plugin/plugin.json b/plugins/capability/darrow-verification/.claude-plugin/plugin.json index b5eb9f38..fc4ffc9d 100644 --- a/plugins/capability/darrow-verification/.claude-plugin/plugin.json +++ b/plugins/capability/darrow-verification/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-verification", - "version": "0.2.6", + "version": "0.2.7", "description": "Bounded acceptance verification through replaceable independent code review", "license": "BUSL-1.1", "author": { diff --git a/plugins/capability/darrow-verification/.codex-plugin/plugin.json b/plugins/capability/darrow-verification/.codex-plugin/plugin.json index 32234d14..5ca8c5af 100644 --- a/plugins/capability/darrow-verification/.codex-plugin/plugin.json +++ b/plugins/capability/darrow-verification/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-verification", - "version": "0.2.6", + "version": "0.2.7", "description": "Bounded acceptance verification through replaceable independent code review", "author": { "name": "Björn Rochel", diff --git a/plugins/capability/darrow-verification/skills/verify-change/backend/scripts/run_locked.py b/plugins/capability/darrow-verification/skills/verify-change/backend/scripts/run_locked.py index 0d8ed007..7a3d5cfb 100644 --- a/plugins/capability/darrow-verification/skills/verify-change/backend/scripts/run_locked.py +++ b/plugins/capability/darrow-verification/skills/verify-change/backend/scripts/run_locked.py @@ -80,15 +80,10 @@ def default_cache_root(environment: Mapping[str, str], platform: str) -> str: "LOCALAPPDATA is required when DARROW_CACHE_DIR is not set" ) return join_path(platform, local, "Darrow", "Cache") - xdg = environment.get("XDG_CACHE_HOME") - if xdg: - return join_path(platform, xdg, "darrow") home = environment.get("HOME") if not home: - raise LauncherError( - "HOME is required when DARROW_CACHE_DIR and XDG_CACHE_HOME are not set" - ) - return join_path(platform, home, ".cache", "darrow") + raise LauncherError("HOME is required when DARROW_CACHE_DIR is not set") + return join_path(platform, home, ".darrow", "cache") def resolve_cache_root(environment: Mapping[str, str], platform: str) -> str: diff --git a/plugins/foundation/darrow-decisions/.claude-plugin/plugin.json b/plugins/foundation/darrow-decisions/.claude-plugin/plugin.json index 10f5f82e..04b87fe6 100644 --- a/plugins/foundation/darrow-decisions/.claude-plugin/plugin.json +++ b/plugins/foundation/darrow-decisions/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-decisions", "description": "Capture and query durable repository decisions at their canonical scope", - "version": "0.2.3", + "version": "0.2.4", "license": "BUSL-1.1" } diff --git a/plugins/foundation/darrow-decisions/.codex-plugin/plugin.json b/plugins/foundation/darrow-decisions/.codex-plugin/plugin.json index 6b986249..136a37fb 100644 --- a/plugins/foundation/darrow-decisions/.codex-plugin/plugin.json +++ b/plugins/foundation/darrow-decisions/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-decisions", - "version": "0.2.3", + "version": "0.2.4", "description": "Capture and query durable repository decisions at their canonical scope", "author": { "name": "Björn Rochel", diff --git a/plugins/foundation/darrow-decisions/backend/scripts/run_locked.py b/plugins/foundation/darrow-decisions/backend/scripts/run_locked.py index 0d8ed007..7a3d5cfb 100644 --- a/plugins/foundation/darrow-decisions/backend/scripts/run_locked.py +++ b/plugins/foundation/darrow-decisions/backend/scripts/run_locked.py @@ -80,15 +80,10 @@ def default_cache_root(environment: Mapping[str, str], platform: str) -> str: "LOCALAPPDATA is required when DARROW_CACHE_DIR is not set" ) return join_path(platform, local, "Darrow", "Cache") - xdg = environment.get("XDG_CACHE_HOME") - if xdg: - return join_path(platform, xdg, "darrow") home = environment.get("HOME") if not home: - raise LauncherError( - "HOME is required when DARROW_CACHE_DIR and XDG_CACHE_HOME are not set" - ) - return join_path(platform, home, ".cache", "darrow") + raise LauncherError("HOME is required when DARROW_CACHE_DIR is not set") + return join_path(platform, home, ".darrow", "cache") def resolve_cache_root(environment: Mapping[str, str], platform: str) -> str: diff --git a/plugins/foundation/darrow-information-architecture/.claude-plugin/plugin.json b/plugins/foundation/darrow-information-architecture/.claude-plugin/plugin.json index 1165b22d..ff3d8f51 100644 --- a/plugins/foundation/darrow-information-architecture/.claude-plugin/plugin.json +++ b/plugins/foundation/darrow-information-architecture/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-information-architecture", "description": "Set up and doctor lean, routed repository information architecture", - "version": "0.2.4", + "version": "0.2.5", "license": "BUSL-1.1" } diff --git a/plugins/foundation/darrow-information-architecture/.codex-plugin/plugin.json b/plugins/foundation/darrow-information-architecture/.codex-plugin/plugin.json index 636fbe88..b77271c6 100644 --- a/plugins/foundation/darrow-information-architecture/.codex-plugin/plugin.json +++ b/plugins/foundation/darrow-information-architecture/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-information-architecture", - "version": "0.2.4", + "version": "0.2.5", "description": "Set up and doctor lean, routed repository information architecture", "author": { "name": "Björn Rochel", diff --git a/plugins/foundation/darrow-information-architecture/backend/scripts/run_locked.py b/plugins/foundation/darrow-information-architecture/backend/scripts/run_locked.py index 0d8ed007..7a3d5cfb 100644 --- a/plugins/foundation/darrow-information-architecture/backend/scripts/run_locked.py +++ b/plugins/foundation/darrow-information-architecture/backend/scripts/run_locked.py @@ -80,15 +80,10 @@ def default_cache_root(environment: Mapping[str, str], platform: str) -> str: "LOCALAPPDATA is required when DARROW_CACHE_DIR is not set" ) return join_path(platform, local, "Darrow", "Cache") - xdg = environment.get("XDG_CACHE_HOME") - if xdg: - return join_path(platform, xdg, "darrow") home = environment.get("HOME") if not home: - raise LauncherError( - "HOME is required when DARROW_CACHE_DIR and XDG_CACHE_HOME are not set" - ) - return join_path(platform, home, ".cache", "darrow") + raise LauncherError("HOME is required when DARROW_CACHE_DIR is not set") + return join_path(platform, home, ".darrow", "cache") def resolve_cache_root(environment: Mapping[str, str], platform: str) -> str: diff --git a/plugins/foundation/darrow-skill-authoring/.claude-plugin/plugin.json b/plugins/foundation/darrow-skill-authoring/.claude-plugin/plugin.json index 2ec7a49b..384c2708 100644 --- a/plugins/foundation/darrow-skill-authoring/.claude-plugin/plugin.json +++ b/plugins/foundation/darrow-skill-authoring/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "darrow-skill-authoring", "description": "Create and improve reliable cross-runtime agent skills", - "version": "0.2.9", + "version": "0.2.10", "license": "BUSL-1.1", "author": { "name": "Björn Rochel", diff --git a/plugins/foundation/darrow-skill-authoring/.codex-plugin/plugin.json b/plugins/foundation/darrow-skill-authoring/.codex-plugin/plugin.json index c90b5672..89c3c34f 100644 --- a/plugins/foundation/darrow-skill-authoring/.codex-plugin/plugin.json +++ b/plugins/foundation/darrow-skill-authoring/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-skill-authoring", - "version": "0.2.9", + "version": "0.2.10", "description": "Create and improve reliable cross-runtime agent skills", "author": { "name": "Björn Rochel", diff --git a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/scripts/run_locked.py b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/scripts/run_locked.py index 0d8ed007..7a3d5cfb 100644 --- a/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/scripts/run_locked.py +++ b/plugins/foundation/darrow-skill-authoring/skills/author-agent-skill/backend/scripts/run_locked.py @@ -80,15 +80,10 @@ def default_cache_root(environment: Mapping[str, str], platform: str) -> str: "LOCALAPPDATA is required when DARROW_CACHE_DIR is not set" ) return join_path(platform, local, "Darrow", "Cache") - xdg = environment.get("XDG_CACHE_HOME") - if xdg: - return join_path(platform, xdg, "darrow") home = environment.get("HOME") if not home: - raise LauncherError( - "HOME is required when DARROW_CACHE_DIR and XDG_CACHE_HOME are not set" - ) - return join_path(platform, home, ".cache", "darrow") + raise LauncherError("HOME is required when DARROW_CACHE_DIR is not set") + return join_path(platform, home, ".darrow", "cache") def resolve_cache_root(environment: Mapping[str, str], platform: str) -> str: diff --git a/plugins/orchestration/darrow-adaptive-delivery/backend/scripts/run_locked.py b/plugins/orchestration/darrow-adaptive-delivery/backend/scripts/run_locked.py index 0d8ed007..7a3d5cfb 100644 --- a/plugins/orchestration/darrow-adaptive-delivery/backend/scripts/run_locked.py +++ b/plugins/orchestration/darrow-adaptive-delivery/backend/scripts/run_locked.py @@ -80,15 +80,10 @@ def default_cache_root(environment: Mapping[str, str], platform: str) -> str: "LOCALAPPDATA is required when DARROW_CACHE_DIR is not set" ) return join_path(platform, local, "Darrow", "Cache") - xdg = environment.get("XDG_CACHE_HOME") - if xdg: - return join_path(platform, xdg, "darrow") home = environment.get("HOME") if not home: - raise LauncherError( - "HOME is required when DARROW_CACHE_DIR and XDG_CACHE_HOME are not set" - ) - return join_path(platform, home, ".cache", "darrow") + raise LauncherError("HOME is required when DARROW_CACHE_DIR is not set") + return join_path(platform, home, ".darrow", "cache") def resolve_cache_root(environment: Mapping[str, str], platform: str) -> str: diff --git a/plugins/orchestration/darrow-ticket-pipeline/.claude-plugin/plugin.json b/plugins/orchestration/darrow-ticket-pipeline/.claude-plugin/plugin.json index 9babf8fe..4800659e 100644 --- a/plugins/orchestration/darrow-ticket-pipeline/.claude-plugin/plugin.json +++ b/plugins/orchestration/darrow-ticket-pipeline/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "darrow-ticket-pipeline", "description": "Deprecated reference static ticket pipeline; use darrow-adaptive-delivery for new work", - "version": "0.3.2", + "version": "0.3.3", "license": "BUSL-1.1", "author": { "name": "Björn Rochel", diff --git a/plugins/orchestration/darrow-ticket-pipeline/.codex-plugin/plugin.json b/plugins/orchestration/darrow-ticket-pipeline/.codex-plugin/plugin.json index 4764be5c..1b3c7a8d 100644 --- a/plugins/orchestration/darrow-ticket-pipeline/.codex-plugin/plugin.json +++ b/plugins/orchestration/darrow-ticket-pipeline/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "darrow-ticket-pipeline", - "version": "0.3.2", + "version": "0.3.3", "description": "Deprecated reference static ticket pipeline; use darrow-adaptive-delivery for new work", "author": { "name": "Björn Rochel", diff --git a/plugins/orchestration/darrow-ticket-pipeline/backend/scripts/run_locked.py b/plugins/orchestration/darrow-ticket-pipeline/backend/scripts/run_locked.py index 0d8ed007..7a3d5cfb 100644 --- a/plugins/orchestration/darrow-ticket-pipeline/backend/scripts/run_locked.py +++ b/plugins/orchestration/darrow-ticket-pipeline/backend/scripts/run_locked.py @@ -80,15 +80,10 @@ def default_cache_root(environment: Mapping[str, str], platform: str) -> str: "LOCALAPPDATA is required when DARROW_CACHE_DIR is not set" ) return join_path(platform, local, "Darrow", "Cache") - xdg = environment.get("XDG_CACHE_HOME") - if xdg: - return join_path(platform, xdg, "darrow") home = environment.get("HOME") if not home: - raise LauncherError( - "HOME is required when DARROW_CACHE_DIR and XDG_CACHE_HOME are not set" - ) - return join_path(platform, home, ".cache", "darrow") + raise LauncherError("HOME is required when DARROW_CACHE_DIR is not set") + return join_path(platform, home, ".darrow", "cache") def resolve_cache_root(environment: Mapping[str, str], platform: str) -> str: