mirror of
https://github.com/sdwolf4103/opencode-working-memory.git
synced 2026-07-17 12:56:42 +02:00
1bba0511bb
## Task 1: Fix exitCode undefined false positive - Add `typeof exitCode !== "number"` check in plugin.ts - Only extract errors when exitCode is explicitly non-zero - Prevent git-log/cat with "errors" text from creating false positives ## Task 2: Fix workspace memory XML truncation - Budget-aware line-by-line rendering - Always include closing </workspace_memory> tag - Return empty string when budget too small - Bonus: canonical exact deduplication with source priority ## Task 3: Remove "always" as trigger - Replace "always" with "going forward" in patterns - Add word boundary via `g` flag and matchAll loop - "from now on" still works as expected ## Task 4: Verification - 22 tests passing - typecheck passing Tests cover: - git log/cat with loose "errors" ignored - TS2345/TypeError strong signals captured - undefined exitCode: no create, no clear - exitCode 0: clears errors - exitCode non-zero: creates error - XML never truncated mid-tag - "always" not a trigger
50 lines
1.5 KiB
TypeScript
50 lines
1.5 KiB
TypeScript
import { existsSync } from "fs";
|
|
import { randomUUID } from "crypto";
|
|
import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
import { dirname } from "path";
|
|
|
|
const fileLocks = new Map<string, Promise<unknown>>();
|
|
|
|
export async function readJSON<T>(path: string, fallback: () => T): Promise<T> {
|
|
if (!existsSync(path)) return fallback();
|
|
try {
|
|
return JSON.parse(await readFile(path, "utf8")) as T;
|
|
} catch {
|
|
return fallback();
|
|
}
|
|
}
|
|
|
|
export async function atomicWriteJSON(path: string, data: unknown): Promise<void> {
|
|
await mkdir(dirname(path), { recursive: true });
|
|
const tmp = `${path}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
|
|
await writeFile(tmp, JSON.stringify(data, null, 2), { encoding: "utf8", mode: 0o600 });
|
|
await rename(tmp, path);
|
|
}
|
|
|
|
export async function updateJSON<T>(
|
|
path: string,
|
|
fallback: () => T,
|
|
updater: (current: T) => T | Promise<T>,
|
|
): Promise<T> {
|
|
const previous = fileLocks.get(path) ?? Promise.resolve();
|
|
let release: () => void = () => {};
|
|
const currentLock = new Promise<void>(resolve => {
|
|
release = resolve;
|
|
});
|
|
const queued = previous.then(() => currentLock, () => currentLock);
|
|
fileLocks.set(path, queued);
|
|
|
|
try {
|
|
await previous.catch(() => undefined);
|
|
const current = await readJSON(path, fallback);
|
|
const updated = await updater(current);
|
|
await atomicWriteJSON(path, updated);
|
|
return updated;
|
|
} finally {
|
|
release();
|
|
if (fileLocks.get(path) === queued) {
|
|
fileLocks.delete(path);
|
|
}
|
|
}
|
|
}
|