MCPcopy
hub / github.com/garrytan/gstack / compact

Function compact

lib/gstack-decision.ts:274–325  ·  view source on GitHub ↗
(paths: DecisionPaths)

Source from the content-addressed store, hash-verified

272 * caller re-runs. Atomic rewrite (tmp + rename); refreshes the snapshot.
273 */
274export function compact(paths: DecisionPaths): CompactResult {
275 const lockPath = `${paths.log}.compact.lock`;
276 let lockFd: number;
277 try {
278 lockFd = openSync(lockPath, "wx"); // O_EXCL|O_CREAT — throws EEXIST if a compact holds it
279 } catch (err) {
280 if ((err as NodeJS.ErrnoException).code === "EEXIST") {
281 return { activeCount: computeActive(readEvents(paths)).length, archivedCount: 0, expungedCount: 0, skipped: true };
282 }
283 throw err;
284 }
285 try {
286 const sizeBefore = existsSync(paths.log) ? statSync(paths.log).size : 0;
287 const events = readEvents(paths);
288 const active = computeActive(events);
289 const activeIds = new Set(active.map((d) => d.id));
290 const redactedIds = new Set(
291 events.filter((e) => e.kind === "redact" && e.supersedes).map((e) => e.supersedes as string),
292 );
293 // Superseded = a decide that's neither active nor redacted. Archive these for history.
294 const superseded = events.filter(
295 (e): e is DecisionEvent => e.kind === "decide" && !activeIds.has(e.id) && !redactedIds.has(e.id),
296 );
297
298 // Append-race guard: if the log grew/changed since we read it, an append landed —
299 // rewriting now would drop it. Abort untouched; the caller re-runs.
300 const sizeNow = existsSync(paths.log) ? statSync(paths.log).size : 0;
301 if (sizeNow !== sizeBefore) {
302 return { activeCount: active.length, archivedCount: 0, expungedCount: 0, skipped: true };
303 }
304
305 // One batched append (not one open/write/close per event) — matches the atomic
306 // batched rewrite of the active log below and shrinks the mid-compact crash window.
307 if (superseded.length) {
308 appendFileSync(paths.archive, superseded.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf-8");
309 }
310
311 const tmp = `${paths.log}.tmp.${process.pid}`;
312 writeFileSync(tmp, active.map((d) => JSON.stringify(d)).join("\n") + (active.length ? "\n" : ""), "utf-8");
313 renameSync(tmp, paths.log);
314 writeSnapshot(paths, active);
315
316 return { activeCount: active.length, archivedCount: superseded.length, expungedCount: redactedIds.size };
317 } finally {
318 closeSync(lockFd);
319 try {
320 unlinkSync(lockPath);
321 } catch {
322 // best-effort lock cleanup; a leftover lock only blocks the NEXT compact, which re-runs
323 }
324 }
325}

Callers 1

Calls 3

computeActiveFunction · 0.85
writeSnapshotFunction · 0.85
readEventsFunction · 0.70

Tested by

no test coverage detected