Skip to content

ContextSearch never searches <project>/ISA.md — the ISAs of everything with persistent identity are unrecallable #1498

Description

@simeonzickert

Version: v7.1.1 (verified against 03a969dd96dc7ff701d92dbc8a8b8d6db4728834)

What happens

ContextSearch roots its ISA search at MEMORY/WORK alone. That's the home of task ISAs. The Algorithm puts the ISA of anything with persistent identity somewhere else — ALGORITHM/v8.4.0.md, "A run is complete when", point 2:

an ISA at the correct home (<project>/ISA.md for persistent things, MEMORY/WORK/{slug}/ISA.md for tasks)

So the ISAs that accumulate state worth recalling — the apps, the libraries, the long-lived projects — sit exactly where the tool never looks. ContextSearch/SKILL.md lists cold start, resume and pick up where we left off as USE WHEN triggers, and advertises "ISA bodies" as one of its five sources. In practice it answers "where was that?" but not "what's the state?", because the artifact holding the state is out of scope by construction.

The failure is silent, which is the part that costs: a project ISA that can't be found is indistinguishable from work that was never done. The absence message reports "No prior work found" with full confidence.

Repro (30 seconds)

mkdir -p /tmp/demo-project
cat > /tmp/demo-project/ISA.md <<'EOF'
---
task: "demo project"
phase: build
progress: 2/7
---
zzuniquetokenzz
EOF

bun run ~/.claude/skills/ContextSearch/Tools/ContextSearch.ts "zzuniquetokenzz"
# → No prior work found on "zzuniquetokenzz"

The file was never a candidate — this isn't a ranking miss.

On a real install here with eight project ISAs living outside MEMORY/WORK, searching a token unique to each one finds 0 of 8.

Cause

const WORK_DIR = join(LIFEOS_DIR, "LIFEOS", "MEMORY", "WORK");

const WORK_DIR = join(LIFEOS_DIR, "LIFEOS", "MEMORY", "WORK");

Both ISA paths are built from that one constant, and there is no second root anywhere:

const isaPath = join(WORK_DIR, dir, "ISA.md");

async function searchIsaBodies(tokens: string[], since: Date | null, until: Date | null = null): Promise<Result[]> {
if (tokens.length === 0 || !existsSync(WORK_DIR)) return [];
const pattern = tokens.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
const out = await ripgrep(pattern, WORK_DIR, ["--glob", "*ISA.md", "-c"]);

Fix

Project trees live wherever their owner keeps them, so there's no default worth guessing — the tool has to be told once. Opt-in roots, resolved --isa-root PATH → config file → nothing, matching the fleet.json convention already used in CMUX/Tools/cmux.ts:

// USER/CUSTOMIZATIONS/SKILLS/ContextSearch/project-roots.json
{ "roots": ["~/path/to/projects"] }

Then a project-isa source alongside the existing five. Two things have to differ from searchIsaBodies: recency comes from the ISA file's mtime (project dirs aren't named YYYYMMDD-slug, so there's no date to parse), and the frontmatter is read so a hit reports the project's own phase/progress — which is the difference between "where was that?" and "what's the state?".

With no roots declared the behavior is unchanged, including the Searched: line and the JSON shape.

Related, but not this

ISASync.hook.ts gates on filePath.includes('MEMORY/WORK/'), so project ISAs never reach work.json either — and work.json evicts after 4h/24h/7d with a 50-row cap, so it couldn't durably hold one anyway. Same blind spot one layer down, different blast radius. Happy to file separately if useful.


I have this working locally — opt-in roots, the project-isa source, zero-config parity verified byte-identical against the current behavior, plus the edge cases (a root pointing at a file bypasses ripgrep's --glob entirely; non-canonical root spellings double-count; a project ISA whose frontmatter slug collides with a work slug merges into one wrong result). Happy to open a PR if you want it.

Note it would touch ripgrep(), which #1486 is also changing — I'd rebase on whichever of us lands first.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions