refactor(v2): per-group filesystem init, persistent across spawns
Each group's on-disk state (CLAUDE.md, .claude-shared/, agent-runner-src/) is now initialized exactly once at group creation and owned by the group forever after. Spawn does only mounts — no copies, no settings.json overwrites, no skill clobbers, no source resyncs. Global memory composition switches from "host reads /workspace/global/CLAUDE.md at bootstrap and stuffs it into systemPrompt.append" to "group CLAUDE.md imports it via @/workspace/global/CLAUDE.md at the top." Edits to global propagate instantly through the existing read-only mount; no copy, no restart. - src/group-init.ts: new initGroupFilesystem(group, opts?) — idempotent, populates groups/<folder>/, .claude-shared/, agent-runner-src/ only when paths don't already exist. - src/container-runner.ts: buildMounts() calls init defensively at the top (catches existing groups on first spawn after this change), drops the inline settings.json write, skills cpSync loop, and agent-runner-src rm-then-copy. Just mounts now. - src/delivery.ts: create_agent flow uses initGroupFilesystem with optional instructions, replacing the inline mkdirSync + writeFileSync. - container/agent-runner/src/index.ts: drops GLOBAL_CLAUDE_MD reading. systemContext.instructions is now only the runtime-generated destinations addendum. - scripts/migrate-group-claude-md.ts: one-shot migration that prepends the @-import to existing groups' CLAUDE.md. Skips if global doesn't exist or if the @-import is already present (regex match on the @ form to avoid false positives from prose mentions of the path). - groups/main/CLAUDE.md: prepended by the migration. Existing groups need a one-time wipe of their agent-runner-src/ dir so init re-populates from current host source — done locally before this commit. Future host-side updates to container/skills/ or container/agent-runner/src/ won't auto-propagate; that's the trade-off for unconditional persistence and will be covered by host-mediated refresh tools in a follow-up. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
65
scripts/migrate-group-claude-md.ts
Normal file
65
scripts/migrate-group-claude-md.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* One-shot migration: prepend `@/workspace/global/CLAUDE.md` to each
|
||||
* existing group's CLAUDE.md so it imports the global memory under the
|
||||
* new model where the host no longer reads global CLAUDE.md at bootstrap.
|
||||
*
|
||||
* - Skips entirely if `groups/global/CLAUDE.md` doesn't exist (nothing
|
||||
* to import; running the script would just add a broken @-import).
|
||||
* - Skips any group whose CLAUDE.md already references
|
||||
* `/workspace/global/CLAUDE.md` (idempotent).
|
||||
* - Skips groups with no CLAUDE.md (nothing to prepend to).
|
||||
*
|
||||
* Usage: npx tsx scripts/migrate-group-claude-md.ts
|
||||
*/
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { GROUPS_DIR } from '../src/config.js';
|
||||
|
||||
const GLOBAL_CLAUDE_MD = path.join(GROUPS_DIR, 'global', 'CLAUDE.md');
|
||||
const IMPORT_LINE = '@/workspace/global/CLAUDE.md';
|
||||
// Must match the @-import syntax exactly — a bare path reference inside
|
||||
// instructional prose ("you can write to /workspace/global/CLAUDE.md")
|
||||
// shouldn't count as "already wired."
|
||||
const IMPORT_REGEX = /@\/workspace\/global\/CLAUDE\.md/;
|
||||
|
||||
if (!fs.existsSync(GLOBAL_CLAUDE_MD)) {
|
||||
console.error(`No global CLAUDE.md at ${GLOBAL_CLAUDE_MD} — nothing to migrate.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(GROUPS_DIR)) {
|
||||
console.error(`No groups dir at ${GROUPS_DIR} — nothing to migrate.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const entries = fs.readdirSync(GROUPS_DIR, { withFileTypes: true });
|
||||
let updated = 0;
|
||||
let alreadyWired = 0;
|
||||
let missingClaudeMd = 0;
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
if (entry.name === 'global') continue; // not a group
|
||||
|
||||
const claudeMd = path.join(GROUPS_DIR, entry.name, 'CLAUDE.md');
|
||||
if (!fs.existsSync(claudeMd)) {
|
||||
console.log(`[skip] ${entry.name}: no CLAUDE.md`);
|
||||
missingClaudeMd++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const body = fs.readFileSync(claudeMd, 'utf-8');
|
||||
if (IMPORT_REGEX.test(body)) {
|
||||
console.log(`[wired] ${entry.name}: already imports ${IMPORT_LINE}`);
|
||||
alreadyWired++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const newBody = `${IMPORT_LINE}\n\n${body}`;
|
||||
fs.writeFileSync(claudeMd, newBody);
|
||||
console.log(`[ok] ${entry.name}: prepended import`);
|
||||
updated++;
|
||||
}
|
||||
|
||||
console.log(`\nDone. updated=${updated} alreadyWired=${alreadyWired} missingClaudeMd=${missingClaudeMd}`);
|
||||
Reference in New Issue
Block a user