fix(memory): persist verified host toolchain across restarts - #323
fix(memory): persist verified host toolchain across restarts#323parkjs101 wants to merge 4 commits into
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughChangesDurable host toolchain
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant MemoryRuntime
participant HostToolchain
participant ProfileFile
participant MemoryIndex
participant DiskPromptBuilder
MemoryRuntime->>HostToolchain: resolve and refresh profile
HostToolchain->>ProfileFile: merge managed JSON block
MemoryRuntime->>MemoryIndex: reindex profile.md
DiskPromptBuilder->>ProfileFile: read and parse profile
DiskPromptBuilder->>HostToolchain: render bounded prompt block
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1⚔️ Resolve merge conflicts 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…oolchain # Conflicts: # src/memory/host-toolchain.ts # src/prompt/builder.ts # structure/str_func.md
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/memory/host-toolchain.ts`:
- Line 456: Update the truncation logic in the host-toolchain rendering flow to
ensure the returned string never exceeds maxChars, including when maxChars is
smaller than the truncation suffix length. Preserve the existing full-rendered
result when it fits, and shorten or omit the suffix as needed for small budgets.
- Around line 219-226: Limit PATH-derived candidates per tool before
resolveHostToolchainProfile verifies them, using a small fixed cap applied to
the results from listCliBinaryCandidates. Preserve scanError handling and
candidate deduplication, and ensure excess candidates are excluded from
verification.
In `@src/prompt/builder.ts`:
- Around line 731-732: Update the AGENTS.md generation flow around
loadDiskHostToolchain() to refresh or regenerate the host-toolchain profile
before loading it, ensuring profile.md contains current verified absolute paths.
Preserve the existing conditional prompt append behavior after the refreshed
profile is loaded.
In `@structure/str_func.md`:
- Around line 210-222: Synchronize the documented src/memory inventory: in
structure/str_func.md lines 210-222, change the module count from 15 to 16 and
add shared.ts, synonyms.ts, worklog.ts, and heartbeat-report.ts; in
structure/memory_architecture.md lines 11-12, add src/memory/heartbeat-report.ts
to the source inventory.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 2a640be7-d9b7-4693-8555-7393eafd97cf
📒 Files selected for processing (8)
src/memory/bootstrap.tssrc/memory/host-toolchain.tssrc/memory/runtime.tssrc/prompt/builder.tsstructure/infra.mdstructure/memory_architecture.mdstructure/str_func.mdtests/unit/host-toolchain-profile.test.ts
| for (const envName of ENV_PATHS[tool]) addCandidate(candidates, env[envName], `env:${envName}`); | ||
|
|
||
| let scanError = false; | ||
| for (const name of pathNames(tool, platform)) { | ||
| const scan = listCliBinaryCandidates(name, env['PATH'] || env['Path'] || env['path'] || ''); | ||
| scanError ||= !!scan.scanError; | ||
| for (const candidate of scan.candidates) addCandidate(candidates, candidate.path, 'PATH'); | ||
| } |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
fd -a -t f 'cli-detect\.(ts|mts|cts|js)$' src/core | while IFS= read -r file; do
echo "== $file =="
ast-grep outline "$file" --items all
rg -n -C 10 'listCliBinaryCandidates|scanError|candidates|slice\(|limit' "$file"
doneRepository: lidge-jun/cli-jaw
Length of output: 12663
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo '== host-toolchain symbols =='
ast-grep outline src/memory/host-toolchain.ts --items all
rg -n -C 12 'listCliBinaryCandidates|spawnSync|1[.]8|timeout|resolveHostToolchainProfile|scanError' src/memory/host-toolchain.tsRepository: lidge-jun/cli-jaw
Length of output: 9718
Enforce a per-tool PATH candidate limit.
listCliBinaryCandidates returns every unique which -a result. resolveHostToolchainProfile verifies each candidate sequentially, with an 1,800 ms timeout per --version probe. Cap the candidates before verification, or add an equivalent limit in listCliBinaryCandidates.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/memory/host-toolchain.ts` around lines 219 - 226, Limit PATH-derived
candidates per tool before resolveHostToolchainProfile verifies them, using a
small fixed cap applied to the results from listCliBinaryCandidates. Preserve
scanError handling and candidate deduplication, and ensure excess candidates are
excluded from verification.
| if (record?.lastAttemptAt) lines.push('', `verified_at: ${record.lastAttemptAt}`); | ||
| return lines.join('\n'); | ||
| const rendered = lines.join('\n'); | ||
| return rendered.length <= maxChars ? rendered : `${rendered.slice(0, Math.max(0, maxChars - 16))}\n...(truncated)`; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Honor maxChars for small budgets.
Line 456 returns the full truncation suffix when maxChars is less than 16. The result can exceed the caller budget.
Proposed fix
- const rendered = lines.join('\n');
- return rendered.length <= maxChars ? rendered : `${rendered.slice(0, Math.max(0, maxChars - 16))}\n...(truncated)`;
+ const rendered = lines.join('\n');
+ const budget = Math.max(0, maxChars);
+ const suffix = '\n...(truncated)';
+ if (rendered.length <= budget) return rendered;
+ if (budget <= suffix.length) return suffix.slice(0, budget);
+ return `${rendered.slice(0, budget - suffix.length)}${suffix}`;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return rendered.length <= maxChars ? rendered : `${rendered.slice(0, Math.max(0, maxChars - 16))}\n...(truncated)`; | |
| const rendered = lines.join('\n'); | |
| const budget = Math.max(0, maxChars); | |
| const suffix = '\n...(truncated)'; | |
| if (rendered.length <= budget) return rendered; | |
| if (budget <= suffix.length) return suffix.slice(0, budget); | |
| return `${rendered.slice(0, budget - suffix.length)}${suffix}`; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/memory/host-toolchain.ts` at line 456, Update the truncation logic in the
host-toolchain rendering flow to ensure the returned string never exceeds
maxChars, including when maxChars is smaller than the truncation suffix length.
Preserve the existing full-rendered result when it fits, and shorten or omit the
suffix as needed for small budgets.
| const hostToolchain = loadDiskHostToolchain(); | ||
| if (hostToolchain) prompt += `\n\n---\n${hostToolchain}\n`; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
rg -n -C 12 -i \
'getSystemPrompt|host.?toolchain|profile\.md|bootstrap|refresh' \
src/memory src/prompt testsRepository: lidge-jun/cli-jaw
Length of output: 50374
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '== relevant symbols =='
rg -n \
'function getSystemPrompt|const getSystemPrompt|export .*getSystemPrompt|loadDiskHostToolchain|resolveHostToolchainProfile|scanSystemProfile|profile\.md|bootstrap' \
src/prompt/builder.ts src/memory src/agent src/core tests/unit/host-toolchain-profile.test.ts
printf '%s\n' '== builder structure =='
ast-grep outline src/prompt/builder.ts --match 'getSystemPrompt' --view expanded
printf '%s\n' '== host-toolchain module structure =='
ast-grep outline src/memory/host-toolchain.ts --view expandedRepository: lidge-jun/cli-jaw
Length of output: 13015
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '== prompt loader and renderer =='
sed -n '640,745p' src/prompt/builder.ts
printf '%s\n' '== host-toolchain parsing, refresh, and rendering =='
sed -n '250,455p' src/memory/host-toolchain.ts
printf '%s\n' '== memory initialization and bootstrap ordering =='
sed -n '275,355p' src/memory/runtime.ts
sed -n '380,435p' src/memory/bootstrap.ts
sed -n '545,600p' src/memory/bootstrap.ts
printf '%s\n' '== all getSystemPrompt call sites =='
rg -n -C 8 'getSystemPrompt\s*\(' --glob '!src/prompt/builder.ts' --glob '!tests/**' .Repository: lidge-jun/cli-jaw
Length of output: 50375
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '== disk prompt generation =='
rg -n -C 20 'function regenerateB|export .*regenerateB|getSystemPrompt\(\{ forDisk: true \}\)|getSystemPrompt\(\{[^}]*forDisk' src tests --glob '*.ts'
printf '%s\n' '== memory readiness callers =='
rg -n -C 12 'ensureIntegratedMemoryReady\(|refreshHostToolchain\(|refreshHostToolchainProfileFile\(' src tests --glob '*.ts'
printf '%s\n' '== startup and command orchestration =='
rg -n -C 16 'regenerateB\(|ensureIntegratedMemoryReady\(|initPromptFiles\(' src/cli src/core src/agent src/index.ts src/main.ts bin tests --glob '*.ts' 2>/dev/null || trueRepository: lidge-jun/cli-jaw
Length of output: 50373
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '== ensureIntegratedMemoryReady references =='
rg -n 'ensureIntegratedMemoryReady' . --glob '*.ts' --glob '!tests/**'
printf '%s\n' '== regenerateB references =='
rg -n 'regenerateB' src bin --glob '*.ts'
printf '%s\n' '== runtime settings refresh region =='
sed -n '330,410p' src/core/runtime-settings.ts
printf '%s\n' '== entrypoint candidates and memory startup calls =='
git ls-files | rg '(^|/)(main|index|startup|bootstrap|runtime|app)[^/]*\.(ts|js)$' | head -100
rg -n -C 10 'ensureIntegratedMemoryReady|regenerateB|initPromptFiles' src bin --glob '*.ts' --glob '!src/prompt/builder.ts'Repository: lidge-jun/cli-jaw
Length of output: 42558
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
builder = Path("src/prompt/builder.ts").read_text()
runtime = Path("src/memory/runtime.ts").read_text()
spawn = Path("src/agent/spawn.ts").read_text()
runtime_settings = Path("src/core/runtime-settings.ts").read_text()
get_prompt = re.search(
r"export function getSystemPrompt\b[\s\S]*?(?=\nexport function |\nfunction |\nlet _lastPromptHash)",
builder,
)
regenerate = re.search(
r"export function regenerateB\b[\s\S]*?(?=\nexport function |\nfunction |\nlet )",
builder,
)
assert get_prompt and regenerate
prompt_body = get_prompt.group(0)
regenerate_body = regenerate.group(0)
print("getSystemPrompt reads profile.md:", "readFileSync(profilePath" in prompt_body)
print("getSystemPrompt refreshes profile:", "refreshHostToolchainProfileFile" in prompt_body or "ensureIntegratedMemoryReady" in prompt_body)
print("regenerateB calls disk prompt:", "getSystemPrompt({ forDisk: true })" in regenerate_body)
print("spawn refreshes via regenerateB:", "regenerateB();" in spawn)
print("runtime-settings refreshes before regenerateB:",
bool(re.search(r"refreshHostToolchainProfileFile[\s\S]{0,500}regenerateB\(\)", runtime_settings)))
print("ensureIntegratedMemoryReady direct references:",
len(re.findall(r"\bensureIntegratedMemoryReady\b", runtime)))
PYRepository: lidge-jun/cli-jaw
Length of output: 244
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
builder = Path("src/prompt/builder.ts").read_text()
runtime = Path("src/memory/runtime.ts").read_text()
spawn = Path("src/agent/spawn.ts").read_text()
runtime_settings = Path("src/core/runtime-settings.ts").read_text()
def section(text, start_marker, end_marker=None):
start = text.index(start_marker)
end = text.index(end_marker, start) if end_marker else len(text)
return text[start:end]
prompt_body = section(builder, "export function getSystemPrompt", "export function getEmployeePrompt")
regenerate_body = section(builder, "export function regenerateB")
checks = {
"getSystemPrompt reads profile.md": "readFileSync(profilePath" in prompt_body,
"getSystemPrompt refreshes profile": (
"refreshHostToolchainProfileFile" in prompt_body
or "ensureIntegratedMemoryReady" in prompt_body
),
"regenerateB calls disk prompt": "getSystemPrompt({ forDisk: true })" in regenerate_body,
"spawn refreshes via regenerateB": "regenerateB();" in spawn,
"runtime-settings calls regenerateB": "regenerateB();" in runtime_settings,
"runtime-settings calls host-toolchain refresh": (
"refreshHostToolchainProfileFile" in runtime_settings
or "ensureIntegratedMemoryReady" in runtime_settings
),
}
for name, value in checks.items():
print(f"{name}: {value}")
print(
"ensureIntegratedMemoryReady references:",
runtime.count("ensureIntegratedMemoryReady"),
)
assert checks["getSystemPrompt reads profile.md"]
assert not checks["getSystemPrompt refreshes profile"]
assert checks["regenerateB calls disk prompt"]
assert checks["spawn refreshes via regenerateB"]
assert checks["runtime-settings calls regenerateB"]
assert not checks["runtime-settings calls host-toolchain refresh"]
PYRepository: lidge-jun/cli-jaw
Length of output: 535
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
builder = Path("src/prompt/builder.ts").read_text()
runtime = Path("src/memory/runtime.ts").read_text()
spawn = Path("src/agent/spawn.ts").read_text()
runtime_settings = Path("src/core/runtime-settings.ts").read_text()
disk_loader = builder[builder.index("function loadDiskHostToolchain"):
builder.index("function getCurrentSessionIdentityLine")]
regenerate = builder[builder.index("export function regenerateB"):]
checks = {
"loadDiskHostToolchain reads profile.md": "readFileSync(profilePath" in disk_loader,
"loadDiskHostToolchain refreshes profile": (
"refreshHostToolchainProfileFile" in disk_loader
or "ensureIntegratedMemoryReady" in disk_loader
),
"regenerateB calls disk prompt": "getSystemPrompt({ forDisk: true })" in regenerate,
"spawn calls regenerateB": "regenerateB();" in spawn,
"runtime-settings calls regenerateB": "regenerateB();" in runtime_settings,
"runtime-settings calls host-toolchain refresh": (
"refreshHostToolchainProfileFile" in runtime_settings
or "ensureIntegratedMemoryReady" in runtime_settings
),
}
for name, value in checks.items():
print(f"{name}: {value}")
print("ensureIntegratedMemoryReady references:", runtime.count("ensureIntegratedMemoryReady"))
print("refreshHostToolchainProfileFile references:", runtime.count("refreshHostToolchainProfileFile"))
assert checks["loadDiskHostToolchain reads profile.md"]
assert not checks["loadDiskHostToolchain refreshes profile"]
assert checks["regenerateB calls disk prompt"]
assert checks["spawn calls regenerateB"]
assert checks["runtime-settings calls regenerateB"]
assert not checks["runtime-settings calls host-toolchain refresh"]
assert runtime.count("ensureIntegratedMemoryReady") == 2
PYRepository: lidge-jun/cli-jaw
Length of output: 495
Refresh the host-toolchain profile before regenerating AGENTS.md. loadDiskHostToolchain() only reads profile.md, so stale verified absolute paths can be written to AGENTS.md.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/prompt/builder.ts` around lines 731 - 732, Update the AGENTS.md
generation flow around loadDiskHostToolchain() to refresh or regenerate the
host-toolchain profile before loading it, ensuring profile.md contains current
verified absolute paths. Preserve the existing conditional prompt append
behavior after the refreshed profile is loaded.
| │ ├── memory/ ← 데이터 영속화 + advanced memory runtime (15 files) | ||
| │ │ ├── advanced.ts ← Advanced Memory re-export stub (1L) | ||
| │ │ ├── bootstrap.ts ← legacy memory/bootstrap import + structured root 초기화 (584L) | ||
| │ │ ├── bootstrap.ts ← legacy memory/bootstrap import + structured root 초기화 (597L) | ||
| │ │ ├── heartbeat.ts ← Heartbeat 잡 스케줄 + cron/every timer orchestration + minute-slot dedupe + fs.watch (311L) | ||
| │ │ ├── heartbeat-schedule.ts ← Heartbeat schedule normalize + cron validate/match + timezone validate + immediate cron loop helper (410L) | ||
| │ │ ├── host-toolchain.ts ← durable verified host paths + profile managed block + bounded AGENTS summary (457L) | ||
| │ │ ├── identity.ts ← `shared/soul.md` 관리 + soul runtime helper (87L) | ||
| │ │ ├── indexing.ts ← FTS5/BM25 reindex + indexed file/chunk 상태 집계 (721L) | ||
| │ │ ├── injection.ts ← memory injection policy + advanced/basic search routing (69L) | ||
| │ │ ├── keyword-expand.ts ← search keyword expansion + provider config normalize (98L) | ||
| │ │ ├── memory.ts ← Persistent Memory grep 기반 (165L) | ||
| │ │ ├── reflect.ts ← episode → shared/procedures reflection + promoted fact 정리 (380L) | ||
| │ │ ├── runtime.ts ← Advanced Memory 런타임: bootstrap/import/FTS5 인덱스/BM25 검색/task snapshot/delta reindex (380L) | ||
| │ │ ├── runtime.ts ← Advanced Memory 런타임: bootstrap/import/FTS5 인덱스/BM25 검색/task snapshot/delta reindex (396L) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Synchronize the src/memory inventory.
The documentation has inconsistent module counts and file lists.
structure/str_func.md#L210-L222: Change the count to 16. Addshared.ts,synonyms.ts,worklog.ts, andheartbeat-report.ts.structure/memory_architecture.md#L11-L12: Addsrc/memory/heartbeat-report.tsto the source inventory.
📍 Affects 2 files
structure/str_func.md#L210-L222(this comment)structure/memory_architecture.md#L11-L12
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@structure/str_func.md` around lines 210 - 222, Synchronize the documented
src/memory inventory: in structure/str_func.md lines 210-222, change the module
count from 15 to 16 and add shared.ts, synonyms.ts, worklog.ts, and
heartbeat-report.ts; in structure/memory_architecture.md lines 11-12, add
src/memory/heartbeat-report.ts to the source inventory.
|
Closing without merge. Issue #299 is already completed on dev by ca2426f with accepted CI-backed behavior. This PR replaces that accepted sidecar implementation with a larger profile.md redesign, so it is no longer required for the resolved issue. Keeping the branch available for reference; no branch deletion. |
Summary
officecli,soffice, Python, and ripgrep## Host toolchainblock in generated diskAGENTS.mdRoot cause
The system scan recorded general runtime facts but no durable document/search tool paths. Generated disk prompts therefore gave rebooted agents no authoritative host-toolchain state, forcing discovery from zero on every session and allowing one failed probe to be misreported as an absent installation.
Design
The durable record lives inside
memory/structured/profile.mdbetween cli-jaw-owned markers. It stores only absolute paths, safe version tokens, source labels,verified_at, and a verification result. Startup verifies a cached path directly before any discovery. The raw managed JSON is removed from normal profile summaries; disk prompt generation renders a separate block capped at 1800 characters.Current
devgained a smaller sidecar-based implementation while this branch was in progress. This branch is merged with currentdevand replaces that sidecar/duplicate startup probe with the profile-backed contract above.This intentionally does not add a generic shell runner or broaden Windows shell-resolution policy (#302/#310).
Addresses #299 without closing it.
Validation
npx tsx --experimental-test-module-mocks --test tests/unit/host-toolchain-profile.test.ts tests/unit/memory-core-profile-sync.test.ts tests/unit/agents-md-soul.test.ts— 12 passednpm run typecheck— passed after merging currentdevnpm run build— passed after merging currentdev; atomicdist/swap and asset verification succeededdist/contains the host-toolchain resolver/refresh symbolsbash structure/verify-counts.shwith a clean WSL systemPATH— 418 checks passednpm run test:all— attempted, but this host has unrelated existing browser/clone failures and the run blocks indefinitely on the interactive WindowsFolderBrowserDialogworkspace-picker test because the runner uses--test-timeout=0; the exact spawned test process tree was stopped. No full-suite pass is claimed.