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.
Version: v7.1.1 (verified against
03a969dd96dc7ff701d92dbc8a8b8d6db4728834)What happens
ContextSearchroots its ISA search atMEMORY/WORKalone. 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: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.mdlistscold start,resumeandpick up where we left offas 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)
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
LifeOS/LifeOS/install/skills/ContextSearch/Tools/ContextSearch.ts
Line 11 in 03a969d
Both ISA paths are built from that one constant, and there is no second root anywhere:
LifeOS/LifeOS/install/skills/ContextSearch/Tools/ContextSearch.ts
Line 284 in 03a969d
LifeOS/LifeOS/install/skills/ContextSearch/Tools/ContextSearch.ts
Lines 312 to 315 in 03a969d
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 thefleet.jsonconvention already used inCMUX/Tools/cmux.ts:Then a
project-isasource alongside the existing five. Two things have to differ fromsearchIsaBodies: recency comes from the ISA file's mtime (project dirs aren't namedYYYYMMDD-slug, so there's no date to parse), and the frontmatter is read so a hit reports the project's ownphase/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.tsgates onfilePath.includes('MEMORY/WORK/'), so project ISAs never reachwork.jsoneither — andwork.jsonevicts 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-isasource, zero-config parity verified byte-identical against the current behavior, plus the edge cases (a root pointing at a file bypasses ripgrep's--globentirely; non-canonical root spellings double-count; a project ISA whose frontmatterslugcollides 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.