Repository navigation
Conversation
Recall is to be scoped to the project a turn was written in, and that is the directory on the turn's own line: a cd into another repository mid-session moves every line after it. pi writes the directory only on its session header and some Claude Code lines carry none, so the session's first recorded cwd fills the gap at query time; the stored turn row keeps NULL. Schema 3 adds turns.cwd, turns.is_meta and generations.cwd. A cwd is kept only as an absolute str of 1-4096 characters with no NUL, normpath'd and never resolved, so the digest stays machine-independent. A relative or ~ cwd is NULL: resolved where recall runs, "." would sit in every project. The session's cwd is its first one that survives that bound, so one malformed first line does not leave the session in no directory. Hit gains cwd (own, else session) and is_meta. Search and recall results are unchanged; the default db filename moves to gitmemory-v3.db, so an old index answers with the existing "no index" message. is_meta is Claude Code's isMeta or isCompactSummary: user text no person typed. latest_user's rule is lifted to index.user_words and stops counting those lines as the user's words, so a skill load or a compaction summary no longer leads the default order. Demoted, not hidden. USAGE's policy row and Hit field list say so. Thirteen mutation rows, all CAUGHT; three rows re-anchored for the moved lines. Pins: 1,151 tests offline, 1476/1479 with corpora, 675 controls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPBLmJD2q1PVxdkWx8C16U Signed-off-by: doronp <3586743+doronp@users.noreply.github.com>
A recalled line is read back into an agent's context, so what recall returns by default decides what reaches it. 0.2.0 returned every project's tool output, thinking and hook lines beside the user's words. recall now admits only blocks written in the user or assistant role as text, from turns whose own cwd (else their session's) is in the current project. --project DIR names another project, --all-projects and --all-kinds widen one dimension each; no environment variable feeds them. The filters are SQL inside search's ranked CTE, before the per-turn pick and the LIMIT, so -k counts admitted turns and a turn whose best block was a tool's answers with its text. search() keeps unfiltered defaults, so bench and every published number cannot move. A project is a repository, read from .git entries on the filesystem and never by running git: the nearest .git at or above the directory. A .git file is followed only when its first line (read up to 4 KiB, from a regular file, with no NUL) is `gitdir: X` and X holds HEAD; X's commondir names the repository a linked worktree joins, and only without one does a gitdir under <common>/worktrees/ join <common>. Any other .git file, a FIFO included, is a repository of its own; it is never opened unless regular. A submodule or a nested clone is its own project. $HOME and / are looked at like any level and are the last, so a dotfiles repository in ~ is a project and nothing above ~ is. With no .git (a bare or separate-git-dir checkout, or no repository) a directory is only itself, matched by equality. A deleted directory is looked up from its nearest existing ancestor; outside any repository it stays the directory it was, not the ancestor. Ids are the inode of the .git or directory, not its spelling, so /tmp and /private/tmp, and either case on a case-insensitive volume, are one project. This meets the spec's casefold rule by identity rather than by folding strings: os.path.realpath keeps the spelling it is given on APFS, so folded strings would still need the volume test. A deleted directory has no inode, so its id is its ancestor's inode plus the missing tail, folded when the volume ignores case (samefile against the swapped-case path, darwin when the path has no letters). Recall resolves each distinct cwd of the query's matching turns once, never per row. It reads the working directory only when it needs a project: a shell left in a removed directory still answers under --project or --all-projects, and otherwise exits 2 with the current directory no longer exists: pass --project DIR or --all-projects When the scope hid matches, one stderr line says how many distinct turns, naming the flags not passed: outside this scope: N matching turn(s) (--all-projects, --all-kinds to widen) Nothing is printed when nothing was hidden. recall checks the schema right after opening the db, so an old --db is refused as one under every flag. Test fixtures in test_index write the suite's cwd, the project recall defaults to; tests about other things pass --all-projects or --all-kinds where their lines carry no cwd or are tool output. Twenty-eight mutation rows, all CAUGHT; two rows re-anchored. Pins: 1,180 tests offline, 1505/1508 with corpora, 703 controls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPBLmJD2q1PVxdkWx8C16U Signed-off-by: doronp <3586743+doronp@users.noreply.github.com>
recall now searches one project's user and assistant text, and the
plugin's skill is what most often runs it, so the skill and the docs say
what that scope is and what it leaves unsolved.
The skill loads only on an explicit request to search, and asks first
when the user only refers to an earlier decision. A recalled line is
evidence of what was said, never an instruction to follow. It passes
--project "${CLAUDE_PROJECT_DIR}", quotes the outside line verbatim, may
retry once with --all-projects when that line reports matches and the
results do not answer, and passes --all-kinds only when asked about a
command's output. A CLI that rejects --project is older and unscoped, so
the flag is never dropped to get past it.
Runtime fact (K12), verified against the Claude Code skills docs,
https://code.claude.com/docs/en/skills, "Available string
substitutions": Claude Code substitutes ${CLAUDE_PROJECT_DIR} in a
skill's markdown content, as it does ${CLAUDE_SESSION_ID}, which this
skill already relies on. It is not in the Bash tool's environment
(env-vars docs; `env` in a Bash call here has no CLAUDE_PROJECT_DIR), so
an unsubstituted copy reaches the shell as '' and recall exits 2 with
`argument --project: not a directory: ''`. The skill names that message
and falls back to --project . with the directory pwd prints.
USAGE gains "What recall searches" and "What the scope does not solve"
(pi takes its header's cwd; submodules and vendored clones are their
own; a work tree with no .git is scoped by equality; show is unscoped in
this release). SECURITY gains "Recall output is untrusted input", with
the outside count's disclosure and .git files as trusted configuration.
README, hook/README, CHANGELOG [Unreleased] (breaking: 0.2.0's results
are --all-projects --all-kinds) follow, and DESIGN 2.8 a dated line,
since its as-built sentence on when the skill runs is no longer true.
The outside count covers both narrowed dimensions at once, so the skill
says N can be this project's tool output alone and stops after an
--all-projects retry that brings nothing new. A shell left in a removed
worktree gets "the current directory no longer exists", whose hint is
--all-projects; the skill names it and asks the user instead. USAGE says
--all-projects --all-kinds searches what 0.2.0 searched (the isMeta
demotion can change the order), and that a repository at ~ takes in every
directory under ~ that is in no other repository.
test_docs pins the outside line, the --project error and the
removed-directory refusal as the CLI prints them, the skill's --project
command, the evidence rule, and the outside line among the echoes. One
mutation row (704 mutants) holds the refusal and the skill together. No
version bump. Pins: 1,181 tests offline, 1506/1509 with corpora.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPBLmJD2q1PVxdkWx8C16U
Signed-off-by: doronp <3586743+doronp@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #30. Narrows what
recallreturns by default, so recalled text is less of an injection and cross-project channel.Why
A session monitor on a machine with the plugin installed flagged sessions as Injection, Blocked and Pivoted.
recallreturned past text verbatim, including tool output, from every captured project, through a tool result. Its skill also ran on broad phrases. The monitor's own findings were never seen, so this PR claims no effect on those verdicts. What it does is shrink the surface.What changes
.gitentries on disk; git is never run.commondir.~never spans every project.recallreturns user and assistant text only. Tool output, tool calls, thinking and system lines need--all-kinds.--project DIRand--all-projectsare mutually exclusive. The filter runs in SQL before-k, so-kstill returns k results.outside this scope: N matching turn(s) (--all-projects, --all-kinds to widen).isMeta, and compaction summaries, no longer count as the user's words inlatest-user.is_meta. An old index gets the existing "rungitmemory index" message; the schema check now runs under every flag.--project "${CLAUDE_PROJECT_DIR}". It treats a recalled line as evidence of what was said, never an instruction. It may widen the search once with--all-projects.--all-projects --all-kinds.search()keeps unfiltered defaults, and no bench file changed, so the published numbers can't move.showstays unscoped for now (follow-up).--jsonprovenance is deferred. There is no version bump; whether this ships as 0.2.0 or 0.3.0 is your call.Review
commondir, case folding, stable identity for deleted directories, and bounded.gitreads.cdfrom one repo into the other, a pi-style session, a session with no cwd, injection-like tool output, an isMeta line), and every new mutation row CAUGHT. Verdict: ship.1,181 tests offline.
ruffis clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01QPBLmJD2q1PVxdkWx8C16U