Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 35 additions & 13 deletions .claude/skills/recall/scripts/recall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
# forwards every flag verbatim — so `recall.sh "<query>" --mode hybrid --json`
# is exactly `eidetic recall "<query>" --mode hybrid --json`.
#
# The store is the files backend at ~/.eidetic/memory by default — a home-dir
# path OUTSIDE any git worktree, so Claude and the colleague backend (which runs
# in throwaway worktrees) read the SAME memories. Set EIDETIC_DATA_DIR to opt out
# of sharing; set EIDETIC_MONGO_URI / NEO4J_URI + --backend for a server store.
# The store is the files backend. Default location resolves per-operation:
# PUBLIC records inside a git repo → <repo-root>/.eidetic/memory (committed,
# team-shared); PRIVATE records, or any record outside a git repo →
# $HOME/.eidetic/memory (never committed). Recall reads both stores and merges.
# An explicit EIDETIC_DATA_DIR wins and short-circuits to that single dir.

set -euo pipefail

Expand All @@ -34,12 +35,10 @@ resolve_eidetic() {
fi
dir=$(dirname "$dir")
done
cat >&2 <<'EOF'
error: eidetic CLI not found.
hint: install it with `uv tool install eidetic-cli` (or `pipx install eidetic-cli`),
or run from inside the eidetic-cli checkout with `uv` available.
The console script is `eidetic` (dist name: eidetic-cli).
EOF
# In a vendored copy there is no eidetic-cli checkout to fall back to, so the
# only honest remedy is to install the CLI. One `error:` + one `hint:` line.
printf 'error: eidetic CLI not found.\n' >&2
printf 'hint: install it with: uv tool install eidetic-cli (or pipx install eidetic-cli); the console script is eidetic.\n' >&2
return 1
}

Expand All @@ -65,10 +64,17 @@ EOF
}

case "${1:-}" in
-h | --help | help | "")
-h | --help)
usage
exit 0
;;
"")
# A missing query is a usage error, not success. The bareword `help` is
# a legitimate search term, so it is intentionally NOT a usage alias.
printf 'error: no query given.\n' >&2
printf 'hint: recall.sh "<query>" [--mode ...] [--json]; run recall.sh --help for usage.\n' >&2
exit 1
;;
esac

resolve_eidetic || exit 2
Expand Down Expand Up @@ -100,9 +106,12 @@ resolve_scope() {
# inline `# comment` or trailing space can't bleed into the scope),
# then strip surrounding quotes only — matching the canonical parser
# in .claude/skills/cicd/scripts/_resolve-nick.sh.
# `|| true`: under `set -o pipefail`, `head -n1` closing the pipe
# early can SIGPIPE `sed`, making the substitution non-zero and
# aborting the script. An empty parse must yield "" here, not exit.
suffix=$(sed -n \
's/^[[:space:]]*-\{0,1\}[[:space:]]*suffix:[[:space:]]*\([^[:space:]]*\).*/\1/p' \
"$dir/culture.yaml" | head -n1 | tr -d "\"'")
"$dir/culture.yaml" | head -n1 | tr -d "\"'" || true)
break
fi
dir=$(dirname "$dir")
Expand All @@ -127,7 +136,20 @@ if ! has_flag --scope "$@"; then
EIDETIC_SCOPE=$(resolve_scope)
if [ -n "$EIDETIC_SCOPE" ]; then
SCOPE_ARGS+=(--scope "$EIDETIC_SCOPE")
has_flag --visibility "$@" || SCOPE_ARGS+=(--visibility private)
# rollout-cli eidetic-memory recipe POLICY OVERRIDE (not eidetic's
# upstream private default): default to PUBLIC, so a plain recall queries
# the in-repo public pool (<repo>/.eidetic/memory) this repo writes to.
# Pass --visibility private to also surface this agent's private ($HOME)
# notes. The two-store read model reads both dirs regardless.
has_flag --visibility "$@" || SCOPE_ARGS+=(--visibility public)
elif ! has_flag --visibility "$@"; then
# No suffix AND no explicit --visibility: the query runs against
# eidetic's own default (scope=default, visibility=public), not this
# agent's private personal scope — so an empty result isn't silently
# misread. Warn on stderr (stdout stays clean for --json). Warn ONLY
# here: an explicit --scope (outer guard) or --visibility (this guard) is
# a deliberate choice, honored verbatim, so either flag silences this.
printf 'warning: no culture.yaml suffix resolved; querying the public default scope rather than a private personal scope. Pass --scope or --visibility to target deliberately.\n' >&2
fi
fi

Expand Down
52 changes: 39 additions & 13 deletions .claude/skills/remember/scripts/remember.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
# Upsert is idempotent by id (and dedups by content hash): re-remembering the
# same record updates it in place, never duplicates.
#
# The store is the files backend at ~/.eidetic/memory by default — a home-dir
# path OUTSIDE any git worktree, so a record Claude remembers is recallable by
# the colleague backend (which runs in throwaway worktrees), and vice versa.
# Set EIDETIC_DATA_DIR to opt out of sharing; use --backend mongo|neo4j (with
# The store is the files backend. Default location resolves per-operation:
# PUBLIC records inside a git repo → <repo-root>/.eidetic/memory (committed,
# team-shared); PRIVATE records, or any record outside a git repo →
# $HOME/.eidetic/memory (never committed). An explicit EIDETIC_DATA_DIR still
# wins and short-circuits to that single dir. Use --backend mongo|neo4j (with
# EIDETIC_MONGO_URI / NEO4J_URI) for a server-backed shared store.

set -euo pipefail
Expand All @@ -40,12 +41,10 @@ resolve_eidetic() {
fi
dir=$(dirname "$dir")
done
cat >&2 <<'EOF'
error: eidetic CLI not found.
hint: install it with `uv tool install eidetic-cli` (or `pipx install eidetic-cli`),
or run from inside the eidetic-cli checkout with `uv` available.
The console script is `eidetic` (dist name: eidetic-cli).
EOF
# In a vendored copy there is no eidetic-cli checkout to fall back to, so the
# only honest remedy is to install the CLI. One `error:` + one `hint:` line.
printf 'error: eidetic CLI not found.\n' >&2
printf 'hint: install it with: uv tool install eidetic-cli (or pipx install eidetic-cli); the console script is eidetic.\n' >&2
return 1
}

Expand All @@ -60,7 +59,9 @@ Usage:

A record needs `id`, `text`, and `type`; `hash` and `metadata` are recommended
(hash is derived from text when omitted). Upsert is idempotent by id.
Public data only. Every flag is forwarded verbatim to `eidetic remember`.
Records default to this agent's PRIVATE personal scope (--scope from the
culture.yaml suffix); pass --visibility public to contribute to the shared
public pool. Every flag is forwarded verbatim to `eidetic remember`.
Comment on lines +62 to +64

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Help contradicts public default 🐞 Bug ⛨ Security

In remember.sh, the usage text says records default to the agent’s PRIVATE personal scope and
suggests --visibility public to share, but when a culture.yaml suffix is resolved the wrapper
actually injects --visibility public by default. This mismatch can cause users to store sensitive
notes believing they are private, while they are written as public (and, with eidetic>=0.10 in a
repo, routed to the committed in-repo store).
Agent Prompt
### Issue description
`remember.sh` now defaults to `--visibility public` when it can resolve a `culture.yaml` suffix, but the wrapper’s help text (and the existing SKILL.md docs for both remember/recall) still describe a private-by-default flow and the old `~/.eidetic/memory` default store. This is especially risky because users can rely on help/docs to decide whether to store sensitive information.

### Issue Context
- Runtime behavior: when suffix resolves, wrapper injects `--visibility public` unless caller explicitly passes `--visibility`.
- Repo documentation (`CLAUDE.md`) states the intended policy is public/in-repo by default.
- SKILL.md files still describe the pre-0.10 behavior and private defaults.

### Fix Focus Areas
- .claude/skills/remember/scripts/remember.sh[51-66]
- .claude/skills/remember/scripts/remember.sh[88-104]
- .claude/skills/remember/scripts/remember.sh[139-158]
- .claude/skills/recall/scripts/recall.sh[82-99]
- .claude/skills/recall/scripts/recall.sh[134-154]
- .claude/skills/remember/SKILL.md[1-30]
- .claude/skills/remember/SKILL.md[61-100]
- .claude/skills/recall/SKILL.md[1-40]
- .claude/skills/recall/SKILL.md[114-133]

### What to change
- Update `remember.sh` usage text to state the correct default: when a suffix is resolved, the wrapper defaults to `--visibility public` (and mention `--visibility private` to keep data in `$HOME` store).
- Update the large comment blocks in both wrappers that still claim “PERSONAL, PRIVATE scope … private default”.
- Update `.claude/skills/remember/SKILL.md` and `.claude/skills/recall/SKILL.md` to reflect:
  - store routing is visibility-based (public in-repo vs private in `$HOME`) and
  - the wrapper policy override is public-by-default (conditional on suffix resolution),
  - and remove/replace the outdated `~/.eidetic/memory` claims.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

See `eidetic explain remember`.
EOF
}
Expand All @@ -72,6 +73,16 @@ case "${1:-}" in
;;
esac

# No record argument AND stdin is an interactive terminal → `eidetic remember`
# would block forever waiting for NDJSON. Show usage instead of hanging. A piped
# or redirected stdin (`cat records.ndjson | remember.sh`) is not a TTY and
# proceeds to the batch path normally.
if [ "$#" -eq 0 ] && [ -t 0 ]; then
usage >&2
printf 'hint: pass a JSON record as an argument, or pipe NDJSON on stdin.\n' >&2
exit 1
fi

resolve_eidetic || exit 2

# ── default to this agent's PERSONAL, PRIVATE scope (culture.yaml `suffix`) ──
Expand Down Expand Up @@ -100,9 +111,12 @@ resolve_scope() {
# inline `# comment` or trailing space can't bleed into the scope),
# then strip surrounding quotes only — matching the canonical parser
# in .claude/skills/cicd/scripts/_resolve-nick.sh.
# `|| true`: under `set -o pipefail`, `head -n1` closing the pipe
# early can SIGPIPE `sed`, making the substitution non-zero and
# aborting the script. An empty parse must yield "" here, not exit.
suffix=$(sed -n \
's/^[[:space:]]*-\{0,1\}[[:space:]]*suffix:[[:space:]]*\([^[:space:]]*\).*/\1/p' \
"$dir/culture.yaml" | head -n1 | tr -d "\"'")
"$dir/culture.yaml" | head -n1 | tr -d "\"'" || true)
break
fi
dir=$(dirname "$dir")
Expand All @@ -127,7 +141,19 @@ if ! has_flag --scope "$@"; then
EIDETIC_SCOPE=$(resolve_scope)
if [ -n "$EIDETIC_SCOPE" ]; then
SCOPE_ARGS+=(--scope "$EIDETIC_SCOPE")
has_flag --visibility "$@" || SCOPE_ARGS+=(--visibility private)
# rollout-cli eidetic-memory recipe POLICY OVERRIDE (not eidetic's
# upstream private default): default to PUBLIC so a plain remember lands
# in <repo>/.eidetic/memory — committed, team- and mesh-shared. Pass
# --visibility private to keep a record in $HOME (uncommitted).
has_flag --visibility "$@" || SCOPE_ARGS+=(--visibility public)
elif ! has_flag --visibility "$@"; then
# No suffix AND no explicit --visibility: the record falls back to
# eidetic's own default (scope=default, visibility=public). Don't let an
# expected-private record go public silently — warn on stderr (stdout
# stays clean for --json). Warn ONLY here: an explicit --scope (outer
# guard) or --visibility (this guard) means the caller chose deliberately
# and is honored verbatim, so either flag silences this.
printf 'warning: no culture.yaml suffix resolved; this record falls back to the public default scope. Pass --scope or --visibility to place it deliberately.\n' >&2
fi
fi

Expand Down
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,45 @@ All notable changes to this project will be documented in this file.
Format follows [Keep a Changelog](https://keepachangelog.com/). This project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.4.0] - 2026-06-24

### Added

- **Memory-discipline "Conventions and workflow" section in `CLAUDE.md`** — a
per-task *recall-before / remember-after* convention (scope localized to this
repo's nick) so the vendored `remember` / `recall` skills are actually used,
not just present: `/recall` before non-trivial work to build on prior
decisions instead of re-deriving them, and `/remember` when a non-obvious
decision, constraint, fix-and-why, or hard-won gotcha surfaces. The section
documents this repo's memory as **in-repo and public** — records resolve to
`<repo-root>/.eidetic/memory` (committed, team- and mesh-shared). Inserted
idempotently (skipped if already present), slotted under an existing
"Conventions and workflow" heading when one exists, else appended.

### Changed

- **Refreshed the `remember` + `recall` wrappers from eidetic-cli 0.10.0**
(cite-don't-import) — picks up eidetic's **project-local store default**: the
files backend now resolves per record by visibility — PUBLIC records inside a
git repo go to `<repo-root>/.eidetic/memory` (committed, team-shared), PRIVATE
records (or any record outside a repo) go to `$HOME/.eidetic/memory` (never
committed), an explicit `EIDETIC_DATA_DIR` still wins, and recall reads both
stores and merges. Also carries the 0.9.3 hardening (interactive-stdin guard,
`help` as a search term, SIGPIPE-safe suffix parsing). **Recipe policy
override (the wrappers here are NOT byte-verbatim):** the injected default
visibility is flipped from eidetic's `private` to **`public`**, so a plain
`/remember` lands the note in `./.eidetic/memory` in this repo, kept as part
of the repo — pass `--visibility private` to route a record to `$HOME`
instead. `remember` drives `eidetic remember` (idempotent upsert of one JSON
record or an NDJSON batch on stdin); `recall` drives `eidetic recall` with
four search modes (exact / approximate / keyword / hybrid). Each `SKILL.md` is
localized only in the illustrative `--scope <nick>` examples (Provenance keeps
"First-party to eidetic-cli"). Runtime dep: the `eidetic` CLI on PATH (else a
local eidetic-cli checkout with `uv`) — **`eidetic >= 0.10.0`** for the
in-repo routing; on an older CLI the public records still work but are stored
in `$HOME/.eidetic/memory` instead of in-repo. Propagated by rollout-cli's
`eidetic-memory` recipe.

## [0.3.0] - 2026-06-23

### Added
Expand Down
28 changes: 28 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,31 @@ Then rename: the `tensor/` package dir, `pyproject.toml` (`name`,
`sonar-project.properties` (`projectKey`), all docstrings/prose, and
`culture.yaml` (`suffix`, `backend`). Re-run `/init` afterward and re-vendor only
the skills you need.

## Conventions and workflow

**Memory discipline — recall before, remember after.** This repo keeps its
eidetic memory **in-repo and public**: records resolve to
`<repo-root>/.eidetic/memory` — committed, and shared with the team and mesh
peers (the `claude` and `colleague` backends both read the same
`tensor-cli` scope), so memory travels with the repo, not a private
home-dir store. Make it a per-task habit:

- **`/recall` before you start.** Search the store for the area you're about
to touch — prior decisions, gotchas, "have we done this before?" — so you
build on what's already known instead of re-deriving it. Do this before
non-trivial tasks, not just when asked.
- **`/remember` when something worth keeping surfaces.** A non-obvious
decision and its rationale, a constraint, a fix and *why* it was needed, a
gotcha that cost time, a fact the next session would otherwise re-learn.
Capture it as it happens, not at the end when it's faded.

A plain `/remember` lands the note in `./.eidetic/memory` in this repo — no
flag needed (the wrappers here default to `--visibility public`; in-repo
routing needs `eidetic >= 0.10.0`, older CLIs keep records in `$HOME`). Keep
something out of the committed store only by passing `--visibility private`
(routes to `$HOME/.eidetic/memory`, never committed); `/recall` reads both
stores and merges. Don't store what the repo already records (code structure,
git history, what's already in this file or `CHANGELOG.md`) — store what you'd
have to re-derive. These are the `recall`/`remember` skills (`.claude/skills/`),
backed by the `eidetic` store.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "tensor-cli"
version = "0.3.0"
version = "0.4.0"
description = "Agent/CLI for tensor operations and ML tensor manipulation"
readme = "README.md"
license = "MIT"
Expand Down
Loading