What is whisper
whisper (the turu binary) makes the incitaciones whisper skill deterministic.
The whisper skill asks an agent to manage ~/.whisper/ — canonical repo keys,
branch slugs, worktree slots, and scope-based knowledge routing — by hand-rolling
shell pipelines. Every invocation is a fresh chance for the model to mis-derive
a path. This tool turns that mechanical layer into a binary: the skill prompts
call the CLI, and the same inputs always produce the same outputs.
Why: agents accumulate operational knowledge across sessions, but “where does this note go?” is a question models answer unreliably. Canonical key derivation is a pure function of the remote URL — the same repo always yields the same key on every machine — so routing stops depending on prompt bookkeeping.
Install:
cargo install whisper-vibes
The command is turu — following the song’s turututu riff. turututu and
whisper run as aliases, so existing scripts and skill prompts keep working.
Part of the charly-vibes tool suite; built on genesis-vibes for the JSON envelope, config, and CLI conventions shared across the family.
Ecosystem
whisper is part of the charly-vibes Tool Ecosystem — the map shows how it relates to its sibling tools.
Documentation provenance
Rendered docs are generated artifacts; the repository sources are authoritative — edit the repo sources, not the rendered output.
Release Status
Generated artifact (docs.yml, from Cargo.toml + git ref). Do not hand-edit — the repo source files remain authoritative.
- Version: v0.11.0
- Built from: main @ 9b5fa40
Getting started
Install
cargo install whisper-vibes
Or from source:
git clone git@cv:charly-vibes/whisper.git && cd whisper && just install
Create the workspace
turu init # create the ~/.whisper layout (never overwrites)
Daily loop
Derive where you are, write what you learned, recall what you need:
turu key # repo key / branch slug / worktree slot
turu resolve branch # the exact destination path for a scope
turu append repo --text "deploy requires vault login" --topic deploy
turu recall repo --topic deploy --budget 400 # slice, newest first, boundary reported
Every read and write is one JSON envelope; recall reports the context-horizon
boundary (served_bytes, budget_unused, entries_skipped).
Share knowledge through git
By default, repo and branch scopes write into the checkout’s committed
.whisper/ directory — the repo’s own git carries the shared knowledge.
Read-only checkouts (CI, no-push contributors) use the --global escape hatch.
Machine-specific knowledge (local paths, hostnames, credentials) belongs in the
private zone: turu append <scope> --private.
Wire it into agents
turu sync # inject the routing map into AGENTS.md as a TURU managed block
turu doctor # verify layout, groups, managed block, privacy contract
Agents in any repo with a TURU managed block read the routing map straight
from AGENTS.md — no prompt bookkeeping required.
Configuration
Where knowledge gets written is layered. Precedence: repo > group > global.
Global
~/.config/whisper/config.toml:
workspace_root = "~/.whisper" # default
[groups.cv-tools]
root = "~/para/areas/dev/gh/charly/knowledge"
repos = ["cv/charly-vibes/whisper"] # explicit membership (optional)
A group routes knowledge from a set of repos into one shared workspace directory instead of each repo’s private slot.
Repo (private)
<repo>/.whisper/config.toml (gitignored — never committed):
group = "cv-tools" # join a group defined in the global config
workspace_root = "/path/to/dir" # or fully override the workspace root
A repo can join a group two ways: privately, via its own .whisper/config.toml,
or centrally, by listing its canonical key in the group’s repos array.
Note: a repo-private
workspace_rootoverrides everything — including the global scope, so the repo’srules.mdresolves inside the private root instead of the shared workspace.turu doctorreports this shadowing and warns when the tworules.mdfiles diverge. A relativeworkspace_rootis anchored to the directory containing the repo config (not the process cwd), so calls from any subdirectory resolve to the same root.
Knowledge transport & the private zone
| Scope | In a checkout (default) | --global escape hatch |
|---|---|---|
repo | <repo>/.whisper/env.md (committed) | <workspace>/repos/<key>/env.md |
branch | <repo>/.whisper/branches/<slug>/notes.md (committed) | <workspace>/repos/<key>/branches/<slug>/notes.md |
--private repo/branch | <repo>/.whisper/private/… — gitignored, never pushed | — |
Transport decision: the repo’s own git carries the shared knowledge. Repo-local
publication assumes a pushable checkout — --global is the fallback for
read-only checkouts (CI, no-push contributors).
The private zone is enforced by turu doctor with three checks:
turu.private-ignore— the zone is effectively ignored (warning + fix hint otherwise)turu.private-tracked— fails prominently if any file underprivate/is trackedturu.private-leaks— advisory lint for machine paths, hostnames, and token-shaped strings in public repo-local files (surfaces viawarnings[])
Git history is forever — prevention only. No turu verb can retract committed knowledge; that is exactly why the private zone exists and why the tracked-files check fails instead of warning.
CLI reference
All commands emit a genesis JSON envelope. Scopes: global, repo, branch,
worktree, group (recall also accepts all).
turu key
Canonical repo key, branch slug, and worktree slot for the current checkout (pure determinism core — a pure function of the remote URL).
turu resolve <scope>
The exact destination path for a knowledge scope.
--global— write to the machine-local store instead of the checkout’s repo-local.whisper/(repo/branch scopes only)--private— write to the checkout’s private zone.whisper/private/— readable locally, structurally never pushed (repo/branch only)
turu append <scope> --text "..."
Append text to a scope’s file (extend, don’t duplicate). Entries are structured and idempotent: sha2 ids over scope + second-precision UTC + text; freeform content preserved verbatim.
--text— text to append (repeatable)--stdin— read the text from stdin instead of--text--topic <k>— optional topic key (bare word, no whitespace/parens/#)--supersedes <id>— mark the entry with this id as superseded by the new entry--global/--private— asresolve
turu recall <scope>
Serve a ranked, budget-bounded slice of a scope’s entries. Whole entries only
(never truncated); reports served_bytes, budget_unused, entries_skipped.
- scope may be
all— composes scopes in precedence order --topic <k>— only entries with this topic key--budget <bytes>— byte budget--include-superseded— include superseded entries--read-only— skip usage telemetry (sidecar untouched)
turu distill <scope>
Two-phase distill contract: snapshot, agent rewrites, guarded commit.
--begin— snapshot the current state and return the working paths--commit --revision <id>— install the distilled working file for this revision- Guards the commit against concurrent drift between snapshot and commit
turu bundle pack|unpack
Deterministic, transport-agnostic knowledge bundles keyed by repo key. Unpack merges extend-without-duplicate and never overwrites.
turu init
Create the workspace layout for this checkout (never overwrites).
turu status
One envelope with every relevant path and existence flag.
turu check
Detect legacy key variants, undefined groups, and missing files.
turu consolidate
Migrate legacy repo-key directories into the canonical key.
turu doctor
Deep workspace diagnostics: layout integrity, group health, legacy keys,
managed block, skill pack staleness, and the private-zone contract
(private-ignore, private-tracked, private-leaks).
turu sync
Inject/refresh the turu managed block (<!-- TURU:START -->) in the
agent-facing file (default: AGENTS.md at the repo root), so agents read
paths from a file instead of re-deriving them.
turu feedback <kind>
File a well-contexted GitHub issue (bug, feature, question, chore):
redacted context bundle, optional --from-last-error scratch recall,
--dry-run preview (via genesis::feedback).
turu skill install
Ship the whisper skill from this repo: router + sub-skills (including manual
fallbacks), content-hashed so turu doctor flags staleness. Targets
~/.claude/skills or any dir; incitaciones keeps a thin pointer to it.
Status
whisper current release — Status: beta. Core (key/resolve/append/recall/distill)
works and is used in anger by other ecosystem tools; surface may still shift
before 1.0.
Implemented
| Command | Status | Notes |
|---|---|---|
turu key | stable | pure function of remote URL; determinism core |
turu resolve | stable | all five scopes + --global/--private |
turu append | stable | idempotent sha2 ids, topics, supersede chain |
turu recall | stable | budget-bounded slices, all precedence composition, horizon boundary reported |
turu distill | stable | two-phase begin/commit with drift guard |
turu bundle | stable | pack/unpack, extend-without-duplicate merge |
turu init | stable | never overwrites |
turu status | stable | single envelope, existence flags |
turu check | stable | legacy keys, undefined groups, missing files |
turu consolidate | beta | migration of legacy repo-key dirs |
turu doctor | stable | layout, groups, managed block, skill staleness, privacy contract |
turu sync | stable | TURU managed block in AGENTS.md |
turu feedback | beta | genesis::feedback, kinds bug/feature/question/chore |
turu skill install | beta | router + sub-skills, content-hashed staleness check |
In progress
- Consolidate rollout across repos with legacy key directories (see the whisper workspace hygiene item in incitaciones’ inbox).
Mapped to specs
- Envelope/config/feedback conventions: genesis-vibes
- Skill contract: incitaciones
whisperskill (thin pointer toturu skill installoutput)
Dogfooding
- incitaciones — the distilled
whisperskill is a thin pointer to this repo’s skill pack (turu skill install) - session/renew skill (org agent config) — calls
turu key,turu recall repo/branchfor session history before journal greps - whisper’s own CI runs
just ci(fmt, clippy, tests, locked build); pinned ecosystem installs tracked inversions.ddl.toml
config-precedence Specification
Purpose
Baseline spec for existing behavior: layered configuration — global (~/.config/whisper/config.toml, or $XDG_CONFIG_HOME), group workspaces, and private per-repo overrides. Precedence: repo > group > global.
Requirements
Requirement: Layered resolution with repo > group > global
Configuration SHALL resolve in layers: the global file provides the default workspace root and named [groups.*] workspaces; a group routes a set of repos into one shared knowledge directory; the private repo config (.whisper/config.toml, found by walking up from the working directory) may join a group or override the workspace root. Repo overrides SHALL win over global defaults.
Scenario: Repo joins a group
- WHEN the repo config sets
group = "team"and the global config defines[groups.team]with a root - THEN repo, branch, and worktree scopes resolve under the group root
- AND the
globalscope still resolves to the workspace root
Scenario: Repo overrides the workspace root
- WHEN the repo config sets
workspace_rootand no group is active - THEN all scopes resolve under that private root
Requirement: Repo config discovery walks up
The repo config SHALL be discovered by walking up from the working directory to the first .whisper/config.toml; files above that first hit are not consulted for repo overrides.
Scenario: Config found in a parent
- WHEN the working directory is
<repo>/src/moduleand.whisper/config.tomlexists at<repo>/ - THEN that config is applied
Scenario: Nested config shadows the repo root
- WHEN
.whisper/config.tomlexists both in the current working directory’s subtree root and at the repo root - THEN the first one found walking up from the working directory wins
- AND the repo root’s config is not consulted for that invocation
Requirement: Global default root
With no repo config and no group membership, the workspace root SHALL default to ~/.whisper (honoring a workspace_root set in the global config, with ~ expansion).
Scenario: Fresh machine
- WHEN no config files exist anywhere
- THEN scopes resolve under
~/.whisper
knowledge-append Specification
Purpose
Baseline spec for existing behavior: turu append — extending a scope’s knowledge file without duplication or clobbering. (Structured entry ids, idempotency, and supersede marks are proposed separately in add-entry-model.)
Requirements
Requirement: Append creates then extends
turu append <scope> SHALL create the scope’s file (with parent directories) if missing, and otherwise append to it in place. Existing content SHALL never be rewritten or truncated.
Scenario: First append creates the file
- WHEN the scope’s file does not exist
- THEN
turu append --text "first fact"creates it containing the text - AND the envelope reports the target
pathandappended_bytes
Scenario: Subsequent appends extend
- WHEN the scope’s file already contains content
- THEN
turu append --text "second fact"adds the text after the existing content - AND existing content is byte-identical to before
Scenario: Empty input rejected
- WHEN
turu appendruns with blank--textor empty stdin - THEN the command fails with an error and a next-step suggestion
- AND the scope file is unchanged
Requirement: Newline normalization
Appended text SHALL be trimmed of trailing whitespace and written with exactly one trailing newline.
Scenario: Trailing whitespace collapse
- WHEN
turu append --text "fact\n\n"runs - THEN the file ends with
factand a single newline
Requirement: Stdin input
turu append SHALL accept text from stdin via --stdin as an alternative to repeatable --text.
Scenario: Piped input
- WHEN text is piped to
turu append repo --stdin - THEN the piped text is appended exactly as
--textwould append it
managed-block Specification
Purpose
Baseline spec for existing behavior: the <!-- TURU:START --> managed block injected into the agent-facing file — the contract that lets agents read deterministic routing paths from a file instead of re-deriving them.
Requirements
Requirement: Inject or refresh the managed block
turu sync SHALL inject the <!-- TURU:START --> / <!-- TURU:END --> block into AGENTS.md at the repo root (or the --file override), containing the workspace root, repo key, branch slug, worktree slot, and the deterministic routing map for all scopes. Content outside the block SHALL never be modified.
Scenario: First sync creates the block
- WHEN the target file exists without a managed block
- THEN the block is injected and the envelope reports the action
- AND existing file content is preserved
Scenario: Target file missing
- WHEN the target file does not exist
- THEN
turu synccreates it containing the managed block - AND the envelope reports the action as created
Scenario: Re-sync updates in place
- WHEN the target file already contains a managed block and routing facts changed (e.g. new branch)
- THEN only the block content is replaced
- AND content outside the block is byte-identical to before
Scenario: File override
- WHEN
turu sync --file CLAUDE.mdruns - THEN the block is injected into that file instead of
AGENTS.md
Requirement: Block content is generated, never hand-edited
The managed block SHALL be regenerated only by turu sync; its content is derived entirely from checkout facts and resolved config.
Scenario: Content matches resolve
- WHEN the managed block lists a scope path
- THEN it is identical to the path
turu resolve <scope>returns for the same checkout
shared-store Specification
Purpose
TBD - created by archiving change add-shared-bundle. Update Purpose after archive.
Requirements
Requirement: Deterministic knowledge bundle
turu bundle pack <scope> SHALL produce a single archive keyed by the canonical repo key containing the scope’s entries with ids, ordered deterministically such that packing identical state yields identical bytes.
Scenario: Deterministic pack
- WHEN the same workspace state is packed twice
- THEN the two bundles are byte-identical
Scenario: Round trip into a fresh workspace
- WHEN a bundle is packed and then unpacked into a fresh workspace
- THEN entries are present with identical ids
Requirement: Non-destructive unpack
turu bundle unpack SHALL merge using extend-without-duplicate rules (mirroring turu consolidate), skip entries whose ids already exist locally, and never overwrite existing file content.
Scenario: Merge into an existing workspace
- WHEN a bundle is unpacked into a workspace that already has some of the entries
- THEN existing entries are skipped and reported as
duplicates_skipped - AND existing file content is extended, not overwritten
Requirement: Transport-agnostic bundles
The bundle format SHALL make no assumption about transport; how bundles travel between workspaces is out of scope for the bundle capability.
Scenario: Bundle carries no transport state
- WHEN a bundle is produced
- THEN it contains only knowledge content and addressing (repo key, scope, entry ids)
- AND no transport-specific fields are required to unpack it
Scenario: Pack group scope with no active group
- WHEN
turu bundle pack groupruns while no group is active for this repo - THEN the command fails with the same error and suggestion as
turu resolve group
Requirement: Bundles never carry the private zone
turu bundle pack SHALL exclude everything under the checkout’s .whisper/private/ tree from the produced bundle, and SHALL refuse — with a clear error and no bundle produced — any scope whose resolved path lies under .whisper/private/ (a guard binding future routing changes; no shipped scope resolves there today). turu bundle unpack SHALL never create, extend, or modify files under .whisper/private/, and SHALL place entries at the paths resolved under current resolution rules — a bundle packed before this change unpacks into the repo-local root. The exclusion SHALL apply to the exact top-level path only; a private/ directory nested deeper under .whisper/ is ordinary content.
Scenario: Pack refuses private-resolved scopes
- WHEN
turu bundle packis requested for a scope whose resolved path lies under.whisper/private/ - THEN the command fails with an error explaining the private zone is never transported
- AND no bundle is produced
Scenario: Pack with private content
- WHEN
turu bundle pack branchruns in a checkout whose.whisper/private/branches/main/notes.mdhas entries - THEN the bundle contains no path under
.whisper/private/ - AND the envelope reports the excluded private entries count
Scenario: Unpack never writes into the private zone
- WHEN a bundle containing an entry that would target a path under
.whisper/private/is unpacked - THEN that entry is skipped and reported in the envelope
- AND no file under
.whisper/private/is created or modified
Scenario: Legacy bundle unpacks into current resolution
- WHEN a bundle packed before this change (entries addressed to workspace-root paths) is unpacked in a checkout
- THEN entries are placed at the paths resolved under current rules
- AND existing-content rules (extend, never overwrite) are unchanged
workspace-maintenance Specification
Purpose
Baseline spec for existing behavior: workspace lifecycle commands — init (never-overwriting layout creation), consolidate (legacy repo-key migration), and read-only diagnostics (status, check, doctor).
Requirements
Requirement: Init creates the layout without overwriting
turu init SHALL create the checkout’s workspace slot: rules.md, the repo slot (env.md), the branch slot (notes.md, context.md, plan.md), and the worktree slot (env.md). Existing files SHALL be reported as existing and left untouched.
Scenario: Fresh init
- WHEN
turu initruns against a new checkout - THEN all slot files are created and reported as created
Scenario: Idempotent re-init
- WHEN
turu initruns again after a previous init - THEN no file is modified or overwritten
- AND all files are reported as existing
Requirement: Consolidate migrates legacy repo-key dirs non-destructively
turu consolidate SHALL migrate legacy repo-key directory variants (bare repo name, host:name colon form matching the bare name) into the canonical key dir: rename the whole dir when the canonical dir is absent; otherwise move entries without conflict or merge text files by extending with non-duplicate lines. Colon dirs that do not match this repo’s bare name SHALL never be touched.
Scenario: Canonical dir absent
- WHEN a legacy variant exists and the canonical dir does not
- THEN the legacy dir is renamed to the canonical dir
- AND the envelope reports it as moved
Scenario: Merge extends without duplicating
- WHEN a legacy variant exists alongside the canonical dir and both contain overlapping lines in a text file
- THEN the merged file contains each line exactly once
- AND the legacy dir is removed afterwards
Scenario: Unrelated colon dirs are safe
- WHEN
repos/contains a colon-form dir belonging to a different repo - THEN
turu consolidateleaves it untouched
Requirement: Diagnostics are read-only
turu status, turu check, and turu doctor SHALL report workspace state (paths, existence flags, legacy variants, group definitions, missing files, managed-block state) without modifying any file.
Scenario: Status is side-effect free
- WHEN
turu statusruns - THEN the workspace layout is unchanged afterwards
Scenario: Check flags legacy variants
- WHEN legacy repo-key dirs exist
- THEN
turu checkreports them together with the canonical key to route new knowledge into
Requirement: Init and sync ensure the private-zone ignore rule
turu init and turu sync SHALL ensure the checkout’s .whisper/private/ is effectively ignored: appending the rule .whisper/private/ to the root .gitignore when git check-ignore does not already report the path as ignored (e.g. when covered by a nested rule), never duplicating an existing rule, and never modifying or removing any other entry.
Scenario: Fresh init adds the rule
- WHEN
turu initruns in a checkout whose.gitignorelacks.whisper/private/ - THEN the rule is appended exactly once
- AND the envelope reports it as created
Scenario: Idempotent ignore rule
- WHEN
turu initruns again after the rule was added - THEN no duplicate rule is appended
- AND existing
.gitignorecontent is unchanged
Requirement: Doctor reports privacy integrity
turu doctor SHALL run three privacy checks and report them read-only via the envelope: (a) .whisper/private/ is effectively ignored — verified with git check-ignore, not by text presence in a .gitignore file (warning with a fix hint when not ignored); (b) no tracked files exist under .whisper/private/ (git ls-files — a prominent failure, since gitignore is not a hard guarantee and committed history cannot be retracted); (c) an advisory leak-shape lint on public repo-local knowledge files flagging machine-specific paths, hostnames, and token-shaped strings — reported via envelope warnings[], never blocking and never modifying files.
Scenario: Private zone not effectively ignored
- WHEN
turu doctorruns in a checkout wheregit check-ignore .whisper/private/does not report the path (rule absent, or present only in a nested.gitignorewhose scope does not cover it) - THEN the check reports a warning with the fix hint (
turu initorturu sync)
Scenario: Tracked file under the private zone
- WHEN
.whisper/private/env.mdis tracked by git (committed before the ignore rule, or force-added) - THEN the doctor check fails prominently, stating that the file is published in history and prevention cannot be retroactive
Scenario: Leak-shaped content in public files
- WHEN a public repo-local knowledge file contains machine-specific paths such as
/home/<user>/or token-shaped strings - THEN doctor emits an advisory warning naming the file and match shape
- AND the command still exits successfully
workspace-routing Specification
Purpose
Baseline spec for existing behavior: canonical repo-key derivation, branch slugs, worktree slots, and scope resolution — the deterministic addressing that makes knowledge routing a pure function of the checkout, never a guess.
Requirements
Requirement: Canonical repo key is a pure function of the remote URL
The repo key SHALL be derived only from the origin remote URL: scheme and git@ prefixes stripped, first colon replaced with a slash, trailing .git and slashes removed (git@host:org/repo.git → host/org/repo). The derivation SHALL NOT depend on machine identity.
Scenario: SSH remote
- WHEN origin is
git@github.com:org/repo.git - THEN the repo key is
github.com/org/repo
Scenario: HTTPS remote
- WHEN origin is
https://host/org/repo.git - THEN the repo key is
host/org/repo
Scenario: No remote
- WHEN the checkout has no origin remote
- THEN the repo key is
local/<basename-of-repo-root> - AND the key is the same on every machine for the same directory name
Requirement: Branch slug normalization
The branch slug SHALL be git rev-parse --abbrev-ref HEAD with / replaced by --; outside a repo the slug SHALL be none.
Scenario: Feature branch
- WHEN HEAD is
feature/login-fix - THEN the branch slug is
feature--login-fix
Requirement: Worktree slot
The worktree slot SHALL be the basename of the git common dir, falling back to the basename of the working directory outside a repo. In practice the common dir basename is .git and is shared by every worktree of the same repo — the slot does not distinguish linked worktrees from the main checkout (the worktree scope’s value is machine/checkout-local setup, not per-worktree identity).
Scenario: Linked worktree shares the slot
- WHEN a linked worktree and the main checkout of the same repo both resolve the worktree scope
- THEN both get the same slot (basename of the shared common dir, typically
.git) - AND their worktree env resolves to the same path
Scenario: Bare repo
- WHEN the checkout is a bare repo whose common dir is the bare dir itself
- THEN the slot is the bare dir’s basename
Requirement: Scope resolution is exact and total
Each scope SHALL resolve to exactly one path. global → <workspace-root>/rules.md; worktree → <workspace-root>/repos/<repo-key>/worktrees/<slot>/env.md; group → <group-root>/repos/<repo-key>/env.md. For repo and branch, the knowledge root SHALL be the checkout’s repo-local .whisper/ directory: repo → .whisper/env.md; branch → .whisper/branches/<branch-slug>/notes.md. The private destination mirrors the repo-local layout under .whisper/private/ (private/env.md, private/branches/<branch-slug>/notes.md) and SHALL be reachable deterministically via the --private routing flag on append/resolve. The global-store equivalents (repos/<repo-key>/env.md, repos/<repo-key>/branches/<branch-slug>/notes.md) SHALL remain valid destinations via the explicit --global escape hatch. Resolution SHALL never guess or search.
Scenario: Branch scope resolves into the checkout
- WHEN
turu resolve branchruns in a repo on branchmain - THEN the target is
<repo-root>/.whisper/branches/main/notes.md
Scenario: Private routing flag
- WHEN
turu append branch --private --text ...runs on branchmain - THEN the entry is written to
<repo-root>/.whisper/private/branches/main/notes.md
Scenario: Global escape hatch
- WHEN
turu append branch --global --text ...runs in a repo with keygithub.com/org/repo - THEN the entry is written to
<workspace-root>/repos/github.com/org/repo/branches/main/notes.md
Scenario: Group scope without an active group
- WHEN
turu resolve groupruns and no group is configured for this repo - THEN the command fails with an error and a next-step suggestion
- AND no path is returned
Scenario: Unknown scope rejected
- WHEN
turu resolve nonsenseruns - THEN the command fails with an error listing the valid scopes
Requirement: Recall composes repo-local and global stores in store-major order
turu recall for repo and branch scopes SHALL serve entries from the checkout’s repo-local root (including the private zone) and from the workspace root for the repo’s key, in store-major order: all repo-local entries by recency first, then all workspace-root entries by recency, under the whole-entry byte budget. Entries SHALL be deduplicated by id across stores. Knowledge stored in the workspace root before this change SHALL remain servable.
Scenario: Pre-existing global-store knowledge stays servable
- WHEN an entry exists only in
<workspace-root>/repos/<repo-key>/env.mdandturu recall reporuns in the checkout - THEN the entry is served from the global store
Scenario: Store-major composition order
- WHEN repo-local and workspace-root stores both contain entries for the repo scope and a workspace-root entry is more recent than some repo-local entries
- THEN all repo-local entries are served before any workspace-root entry
- AND within each store, entries are ordered by recency
- AND no entry appears twice
Requirement: Private zone is read-included and publish-excluded
.whisper/private/ SHALL be the canonical private zone of a repo workspace: included in recall composition, and excluded — by the exact top-level path .whisper/private/ — from every publication and transport verb (bundle pack, bundle unpack, managed-block generation). No verb SHALL write into the private zone except explicit private-scope operations. turu consolidate operates on the workspace root only and structurally cannot reach the checkout; if it is ever extended to checkout-local knowledge, the private zone SHALL remain excluded from its moves and merges. The exclusion SHALL NOT apply to any other path, including a private/ directory nested deeper under .whisper/.
Scenario: Recall serves private knowledge locally
- WHEN an entry exists under
.whisper/private/branches/main/notes.mdandturu recall branchruns onmain - THEN the entry is served
Scenario: Consolidate cannot reach the private zone
- WHEN
turu consolidateruns against the workspace root - THEN it scans only legacy repo-key variants under the workspace root
- AND if it is ever extended to checkout-local knowledge, no file under
.whisper/private/is moved, merged, or removed
Requirement: Declarative per-repo routing mode
<repo>/.whisper/config.toml SHALL accept a routing key with value
repo-local or machine-local, defaulting to repo-local. The mode
SHALL be the persistent default destination for the repo and branch
scopes: repo-local resolves them into the checkout’s committed
.whisper/ zone (decision A); machine-local resolves them into the
machine-local store — the persistent equivalent of the --global
escape hatch. Per-invocation overrides compose on top: --private
keeps routing into the checkout’s private zone and group routing keeps
priority when a group is active. An unknown mode value SHALL fail the
invocation with an error naming the valid values.
Scenario: Machine-local routing is the persistent default
- WHEN
.whisper/config.tomlsetsrouting = "machine-local"andturu append branch --text ...runs - THEN the entry is written to the machine-local store without any flag
Scenario: Repo-local remains the default without a routing key
- WHEN the repo config has no
routingkey - THEN repo/branch scopes resolve into the checkout’s committed zone (decision A)
Scenario: Unknown routing mode is rejected with the valid values
- WHEN the repo config sets
routing = "cloud" - THEN the invocation fails with an error naming
repo-localandmachine-local
Scenario: The private zone override composes over the declared mode
- WHEN
turu append branch --private --text ...runs undermachine-localrouting - THEN the entry is written to
<repo-root>/.whisper/private/branches/main/notes.md
Requirement: Init provisions the declared routing layer
turu init SHALL provision exactly the layer the declared routing mode
resolves to for repo/branch scopes: both checkout and store layers under
repo-local (recall composes both), only the store layer under
machine-local — never both provisioning passes for the same scope.
Scenario: Init under machine-local routing provisions only the store
- WHEN
turu initruns withrouting = "machine-local" - THEN the store slots for repo/branch are created
- AND no checkout repo/branch slot files are created
Requirement: Doctor reports the declared routing mode
turu doctor SHALL report the declared routing mode and the effective
repo/branch route via a check, and SHALL warn when machine-local
routing is active while the checkout layer still carries content (the
mode switch is a config edit; leftovers must be surfaced, not silent).
Scenario: Doctor flags stale checkout-layer content under machine-local
- WHEN
turu doctorruns withrouting = "machine-local"and the checkout’s repo slot file has content - THEN the routing check warns naming the stale files with a migration hint
- AND a clean switch reports the store route and no warning