Skip to content
Merged
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
62 changes: 35 additions & 27 deletions .claude/skills/recall/scripts/recall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +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 a repo-local ./.eidetic by default — rooted
# at the MAIN worktree (via git's common dir), so Claude and the colleague
# backend (which runs in throwaway linked worktrees of the same repo) read the
# SAME memories, while keeping memory inside the repo rather than the home
# directory. Outside a git checkout it falls back to the eidetic CLI default.
# Set EIDETIC_DATA_DIR to override; 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 @@ -37,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 @@ -68,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 @@ -103,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 @@ -130,24 +136,26 @@ 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

# Default the embedding endpoint to the local model-gear embed gear. eidetic
# falls back to a deterministic offline embedding if it's unreachable, so this
# is safe even when the gear is down. Override by exporting these yourself.
# ── default the store to a repo-local .eidetic at the main worktree root ────
# Keep memory inside the repo (./.eidetic) instead of the home-dir
# ~/.eidetic/memory. Root it at the common git dir's parent so linked worktrees
# (the colleague backend) resolve the SAME store, preserving shared recall.
# Outside a git checkout, leave it unset so eidetic uses its own default.
if [ -z "${EIDETIC_DATA_DIR:-}" ]; then
_ed_common=$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse \
--path-format=absolute --git-common-dir 2>/dev/null) || _ed_common=""
[ -n "$_ed_common" ] && export EIDETIC_DATA_DIR="$(dirname "$_ed_common")/.eidetic"
fi

: "${EIDETIC_EMBED_URL:=http://localhost:8002/v1}"
: "${EIDETIC_EMBED_MODEL:=Qwen/Qwen3-Embedding-0.6B}"
export EIDETIC_EMBED_URL EIDETIC_EMBED_MODEL
Expand Down
65 changes: 39 additions & 26 deletions .claude/skills/remember/scripts/remember.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +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 a repo-local ./.eidetic by default — rooted
# at the MAIN worktree (via git's common dir), so a record Claude remembers is
# recallable by the colleague backend (which runs in throwaway linked worktrees
# of the same repo), and vice versa, while keeping memory inside the repo rather
# than the home directory. Outside a git checkout it falls back to the eidetic
# CLI default. Set EIDETIC_DATA_DIR to override; 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 @@ -42,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 @@ -62,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`.
See `eidetic explain remember`.
EOF
}
Expand All @@ -74,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 @@ -102,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 @@ -129,21 +141,22 @@ 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

# ── default the store to a repo-local .eidetic at the main worktree root ────
# Keep memory inside the repo (./.eidetic) instead of the home-dir
# ~/.eidetic/memory. Root it at the common git dir's parent so linked worktrees
# (the colleague backend) resolve the SAME store, preserving shared recall.
# Outside a git checkout, leave it unset so eidetic uses its own default.
if [ -z "${EIDETIC_DATA_DIR:-}" ]; then
_ed_common=$(git -C "$(dirname "${BASH_SOURCE[0]}")" rev-parse \
--path-format=absolute --git-common-dir 2>/dev/null) || _ed_common=""
[ -n "$_ed_common" ] && export EIDETIC_DATA_DIR="$(dirname "$_ed_common")/.eidetic"
fi

: "${EIDETIC_EMBED_URL:=http://localhost:8002/v1}"
: "${EIDETIC_EMBED_MODEL:=Qwen/Qwen3-Embedding-0.6B}"
export EIDETIC_EMBED_URL EIDETIC_EMBED_MODEL
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 @@ -151,3 +151,31 @@ Per-machine paths live in **`.claude/skills.local.yaml`** (git-ignored). A commi
- `steward` — alignment authority; the original audit at `docs/steward/suggestions.md` drove v0.0.1. Sibling-relative path: `../steward`.

If `ghafi` grows verbs that overlap with `gh` (the GitHub CLI), favour the same flag/output shapes when reasonable, but don't take a runtime dep on `gh` — agents need `ghafi` to be self-contained.

## 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
`ghafi` 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 = "ghafi"
version = "0.3.0"
version = "0.4.0"
description = "ghafi — GitHub Agent First Interface; an AgentCulture manager."
readme = "README.md"
license = "MIT"
Expand Down
Loading