Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 42 additions & 2 deletions bin/knowledge-mcp.js
Original file line number Diff line number Diff line change
Expand Up @@ -25814,6 +25814,42 @@ function summariesMatch(left, right) {
function migrationTimestamp(now) {
return now.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
}
function sleepSync(milliseconds) {
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
}
function isRetriableFsLock(error51) {
return error51 instanceof Error && /\b(EBUSY|EPERM)\b/.test(error51.message);
}
function removeWorkspaceWithRetries(path) {
let lastError;
for (let attempt = 0;attempt < 8; attempt += 1) {
try {
rmSync(path, { recursive: true, force: false });
return;
} catch (error51) {
lastError = error51;
if (!isRetriableFsLock(error51))
throw error51;
sleepSync(50 * (attempt + 1));
}
}
throw lastError;
}
function isRetainedTombstoneFile(file2) {
return file2 === "TOMBSTONE.md" || file2 === "migration.json" || file2 === "knowledge.db" || file2 === "knowledge.db-shm" || file2 === "knowledge.db-wal" || file2 === "knowledge.db-journal";
}
function prepareLegacyTombstoneDirectory(home) {
for (const file2 of readdirSync2(home)) {
if (file2 === "TOMBSTONE.md" || file2 === "migration.json")
continue;
try {
rmSync(join5(home, file2), { recursive: true, force: false });
} catch (error51) {
if (!isRetriableFsLock(error51) || !file2.startsWith("knowledge.db"))
throw error51;
}
}
}
function moveWorkspace(sourceHome, targetHome) {
try {
renameSync2(sourceHome, targetHome);
Expand All @@ -25826,8 +25862,12 @@ function moveWorkspace(sourceHome, targetHome) {
preserveTimestamps: true
});
try {
rmSync(sourceHome, { recursive: true, force: false });
removeWorkspaceWithRetries(sourceHome);
} catch (removeError) {
if (isRetriableFsLock(removeError)) {
prepareLegacyTombstoneDirectory(sourceHome);
return;
}
rmSync(targetHome, { recursive: true, force: true });
throw removeError;
}
Expand All @@ -25840,7 +25880,7 @@ function isMigrationTombstone(workspace, summary, currentHome) {
return false;
if (!summary.files.includes("TOMBSTONE.md") || !summary.files.includes("migration.json"))
return false;
if (summary.files.some((file2) => file2 !== "TOMBSTONE.md" && file2 !== "migration.json"))
if (summary.files.some((file2) => !isRetainedTombstoneFile(file2)))
return false;
try {
const metadata = JSON.parse(readFileSync11(join5(workspace.home, "migration.json"), "utf8"));
Expand Down
284 changes: 142 additions & 142 deletions bin/knowledge.js

Large diffs are not rendered by default.

44 changes: 42 additions & 2 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26807,6 +26807,42 @@ function summariesMatch(left, right) {
function migrationTimestamp(now) {
return now.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
}
function sleepSync(milliseconds) {
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
}
function isRetriableFsLock(error51) {
return error51 instanceof Error && /\b(EBUSY|EPERM)\b/.test(error51.message);
}
function removeWorkspaceWithRetries(path) {
let lastError;
for (let attempt = 0;attempt < 8; attempt += 1) {
try {
rmSync(path, { recursive: true, force: false });
return;
} catch (error51) {
lastError = error51;
if (!isRetriableFsLock(error51))
throw error51;
sleepSync(50 * (attempt + 1));
}
}
throw lastError;
}
function isRetainedTombstoneFile(file2) {
return file2 === "TOMBSTONE.md" || file2 === "migration.json" || file2 === "knowledge.db" || file2 === "knowledge.db-shm" || file2 === "knowledge.db-wal" || file2 === "knowledge.db-journal";
}
function prepareLegacyTombstoneDirectory(home) {
for (const file2 of readdirSync2(home)) {
if (file2 === "TOMBSTONE.md" || file2 === "migration.json")
continue;
try {
rmSync(join5(home, file2), { recursive: true, force: false });
} catch (error51) {
if (!isRetriableFsLock(error51) || !file2.startsWith("knowledge.db"))
throw error51;
}
}
}
function moveWorkspace(sourceHome, targetHome) {
try {
renameSync2(sourceHome, targetHome);
Expand All @@ -26819,8 +26855,12 @@ function moveWorkspace(sourceHome, targetHome) {
preserveTimestamps: true
});
try {
rmSync(sourceHome, { recursive: true, force: false });
removeWorkspaceWithRetries(sourceHome);
} catch (removeError) {
if (isRetriableFsLock(removeError)) {
prepareLegacyTombstoneDirectory(sourceHome);
return;
}
rmSync(targetHome, { recursive: true, force: true });
throw removeError;
}
Expand All @@ -26833,7 +26873,7 @@ function isMigrationTombstone(workspace, summary, currentHome) {
return false;
if (!summary.files.includes("TOMBSTONE.md") || !summary.files.includes("migration.json"))
return false;
if (summary.files.some((file2) => file2 !== "TOMBSTONE.md" && file2 !== "migration.json"))
if (summary.files.some((file2) => !isRetainedTombstoneFile(file2)))
return false;
try {
const metadata = JSON.parse(readFileSync12(join5(workspace.home, "migration.json"), "utf8"));
Expand Down
51 changes: 49 additions & 2 deletions src/workspace-migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,49 @@ function migrationTimestamp(now: Date): string {
return now.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z');
}

function sleepSync(milliseconds: number): void {
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
}

function isRetriableFsLock(error: unknown): boolean {
return error instanceof Error && /\b(EBUSY|EPERM)\b/.test(error.message);
}

function removeWorkspaceWithRetries(path: string): void {
let lastError: unknown;
for (let attempt = 0; attempt < 8; attempt += 1) {
try {
rmSync(path, { recursive: true, force: false });
return;
} catch (error) {
lastError = error;
if (!isRetriableFsLock(error)) throw error;
sleepSync(50 * (attempt + 1));
}
}
throw lastError;
}

function isRetainedTombstoneFile(file: string): boolean {
return file === 'TOMBSTONE.md'
|| file === 'migration.json'
|| file === 'knowledge.db'
|| file === 'knowledge.db-shm'
|| file === 'knowledge.db-wal'
|| file === 'knowledge.db-journal';
}

function prepareLegacyTombstoneDirectory(home: string): void {
for (const file of readdirSync(home)) {
if (file === 'TOMBSTONE.md' || file === 'migration.json') continue;
try {
rmSync(join(home, file), { recursive: true, force: false });
} catch (error) {
if (!isRetriableFsLock(error) || !file.startsWith('knowledge.db')) throw error;
}
}
}

function moveWorkspace(sourceHome: string, targetHome: string): void {
try {
renameSync(sourceHome, targetHome);
Expand All @@ -183,8 +226,12 @@ function moveWorkspace(sourceHome: string, targetHome: string): void {
preserveTimestamps: true,
});
try {
rmSync(sourceHome, { recursive: true, force: false });
removeWorkspaceWithRetries(sourceHome);
} catch (removeError) {
if (isRetriableFsLock(removeError)) {
prepareLegacyTombstoneDirectory(sourceHome);
return;
}
rmSync(targetHome, { recursive: true, force: true });
throw removeError;
}
Expand All @@ -199,7 +246,7 @@ function isMigrationTombstone(
): boolean {
if (!summary.exists) return false;
if (!summary.files.includes('TOMBSTONE.md') || !summary.files.includes('migration.json')) return false;
if (summary.files.some((file) => file !== 'TOMBSTONE.md' && file !== 'migration.json')) return false;
if (summary.files.some((file) => !isRetainedTombstoneFile(file))) return false;
try {
const metadata = JSON.parse(readFileSync(join(workspace.home, 'migration.json'), 'utf8')) as {
new_path?: unknown;
Expand Down
Loading