Prune removed bundled skills during bootstrap sync

This commit is contained in:
Advait Paliwal
2026-03-25 01:37:08 -07:00
parent 75b0467761
commit 151956ea24
2 changed files with 91 additions and 9 deletions

View File

@@ -1,5 +1,5 @@
import { createHash } from "node:crypto";
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { dirname, relative, resolve } from "node:path";
import { getBootstrapStatePath } from "../config/paths.js";
@@ -64,27 +64,76 @@ function listFiles(root: string): string[] {
return files.sort();
}
function removeEmptyParentDirectories(path: string, stopAt: string): void {
let current = dirname(path);
while (current.startsWith(stopAt) && current !== stopAt) {
if (!existsSync(current)) {
current = dirname(current);
continue;
}
if (readdirSync(current).length > 0) {
return;
}
rmSync(current, { recursive: true, force: true });
current = dirname(current);
}
}
function syncManagedFiles(
sourceRoot: string,
targetRoot: string,
scope: string,
state: BootstrapState,
result: BootstrapSyncResult,
): void {
const sourcePaths = new Set(listFiles(sourceRoot).map((sourcePath) => relative(sourceRoot, sourcePath)));
for (const targetPath of listFiles(targetRoot)) {
const key = relative(targetRoot, targetPath);
if (sourcePaths.has(key)) continue;
const scopedKey = `${scope}:${key}`;
const previous = state.files[scopedKey] ?? state.files[key];
if (!previous) {
continue;
}
if (!existsSync(targetPath)) {
delete state.files[scopedKey];
delete state.files[key];
continue;
}
const currentTargetText = readFileSync(targetPath, "utf8");
const currentTargetHash = sha256(currentTargetText);
if (currentTargetHash !== previous.lastAppliedTargetHash) {
result.skipped.push(key);
continue;
}
rmSync(targetPath, { force: true });
removeEmptyParentDirectories(targetPath, targetRoot);
delete state.files[scopedKey];
delete state.files[key];
}
for (const sourcePath of listFiles(sourceRoot)) {
const key = relative(sourceRoot, sourcePath);
const targetPath = resolve(targetRoot, key);
const sourceText = readFileSync(sourcePath, "utf8");
const sourceHash = sha256(sourceText);
const previous = state.files[key];
const scopedKey = `${scope}:${key}`;
const previous = state.files[scopedKey] ?? state.files[key];
mkdirSync(dirname(targetPath), { recursive: true });
if (!existsSync(targetPath)) {
writeFileSync(targetPath, sourceText, "utf8");
state.files[key] = {
state.files[scopedKey] = {
lastAppliedSourceHash: sourceHash,
lastAppliedTargetHash: sourceHash,
};
delete state.files[key];
result.copied.push(key);
continue;
}
@@ -93,10 +142,11 @@ function syncManagedFiles(
const currentTargetHash = sha256(currentTargetText);
if (currentTargetHash === sourceHash) {
state.files[key] = {
state.files[scopedKey] = {
lastAppliedSourceHash: sourceHash,
lastAppliedTargetHash: currentTargetHash,
};
delete state.files[key];
continue;
}
@@ -111,10 +161,11 @@ function syncManagedFiles(
}
writeFileSync(targetPath, sourceText, "utf8");
state.files[key] = {
state.files[scopedKey] = {
lastAppliedSourceHash: sourceHash,
lastAppliedTargetHash: sourceHash,
};
delete state.files[key];
result.updated.push(key);
}
}
@@ -128,9 +179,9 @@ export function syncBundledAssets(appRoot: string, agentDir: string): BootstrapS
skipped: [],
};
syncManagedFiles(resolve(appRoot, ".feynman", "themes"), resolve(agentDir, "themes"), state, result);
syncManagedFiles(resolve(appRoot, ".feynman", "agents"), resolve(agentDir, "agents"), state, result);
syncManagedFiles(resolve(appRoot, "skills"), resolve(agentDir, "skills"), state, result);
syncManagedFiles(resolve(appRoot, ".feynman", "themes"), resolve(agentDir, "themes"), "themes", state, result);
syncManagedFiles(resolve(appRoot, ".feynman", "agents"), resolve(agentDir, "agents"), "agents", state, result);
syncManagedFiles(resolve(appRoot, "skills"), resolve(agentDir, "skills"), "skills", state, result);
writeBootstrapState(statePath, state);
return result;