cherry-pick(state): atomic-writes for guided-flow-queue and reports
Cherry-pick of gsd-build/gsd-2 53babec29 (Jeremy <jeremy@fluxlabs.net>) — atomic-write half only. Eliminates torn-write risk on PROJECT.md queue sync and reports.json/HTML index regeneration by switching writeFileSync → atomicWriteSync (tmp+rename). The companion lock-wrapped-append changes (journal.ts, uok/audit.ts, workflow-logger.ts) are deferred — they need proper-lockfile + withFileLockSync helper introduced first. Co-Authored-By: Jeremy <jeremy@fluxlabs.net> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8e827147c9
commit
3df56cb94f
2 changed files with 9 additions and 7 deletions
|
|
@ -17,7 +17,8 @@ import {
|
||||||
sfRoot, resolveMilestoneFile, resolveSliceFile,
|
sfRoot, resolveMilestoneFile, resolveSliceFile,
|
||||||
resolveSfRootFile, relSfRootFile, relSliceFile,
|
resolveSfRootFile, relSfRootFile, relSliceFile,
|
||||||
} from "./paths.js";
|
} from "./paths.js";
|
||||||
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
import { readFileSync, existsSync } from "node:fs";
|
||||||
|
import { atomicWriteSync } from "./atomic-write.js";
|
||||||
import { nativeAddPaths, nativeCommit } from "./native-git-bridge.js";
|
import { nativeAddPaths, nativeCommit } from "./native-git-bridge.js";
|
||||||
import { loadEffectiveSFPreferences } from "./preferences.js";
|
import { loadEffectiveSFPreferences } from "./preferences.js";
|
||||||
import { loadQueueOrder, sortByQueueOrder, saveQueueOrder } from "./queue-order.js";
|
import { loadQueueOrder, sortByQueueOrder, saveQueueOrder } from "./queue-order.js";
|
||||||
|
|
@ -387,7 +388,7 @@ function removeDependsOnFromContextFiles(
|
||||||
const newContent = newFm.trim()
|
const newContent = newFm.trim()
|
||||||
? `---\n${newFm}\n---${body}`
|
? `---\n${newFm}\n---${body}`
|
||||||
: body.replace(/^\n+/, "");
|
: body.replace(/^\n+/, "");
|
||||||
writeFileSync(contextFile, newContent, "utf-8");
|
atomicWriteSync(contextFile, newContent, "utf-8");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -435,5 +436,5 @@ function syncProjectMdSequence(
|
||||||
const separatorLine = lines[tableStart + 1];
|
const separatorLine = lines[tableStart + 1];
|
||||||
const newTable = [headerLine, separatorLine, ...newRows];
|
const newTable = [headerLine, separatorLine, ...newRows];
|
||||||
lines.splice(tableStart, tableEnd - tableStart, ...newTable);
|
lines.splice(tableStart, tableEnd - tableStart, ...newTable);
|
||||||
writeFileSync(projectPath, lines.join("\n"), "utf-8");
|
atomicWriteSync(projectPath, lines.join("\n"), "utf-8");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,12 @@
|
||||||
* Manual: /sf export --html
|
* Manual: /sf export --html
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { writeFileSync, readFileSync, mkdirSync, existsSync } from 'node:fs';
|
import { readFileSync, mkdirSync, existsSync } from 'node:fs';
|
||||||
import { join, basename } from 'node:path';
|
import { join, basename } from 'node:path';
|
||||||
import { sfRoot } from './paths.js';
|
import { sfRoot } from './paths.js';
|
||||||
import { formatCost, formatTokenCount } from './metrics.js';
|
import { formatCost, formatTokenCount } from './metrics.js';
|
||||||
import { formatDateShort, formatDuration } from '../shared/format-utils.js';
|
import { formatDateShort, formatDuration } from '../shared/format-utils.js';
|
||||||
|
import { atomicWriteSync } from './atomic-write.js';
|
||||||
|
|
||||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -83,7 +84,7 @@ export function loadReportsIndex(basePath: string): ReportsIndex | null {
|
||||||
function saveReportsIndex(basePath: string, index: ReportsIndex): void {
|
function saveReportsIndex(basePath: string, index: ReportsIndex): void {
|
||||||
const dir = reportsDir(basePath);
|
const dir = reportsDir(basePath);
|
||||||
mkdirSync(dir, { recursive: true });
|
mkdirSync(dir, { recursive: true });
|
||||||
writeFileSync(reportsIndexPath(basePath), JSON.stringify(index, null, 2) + '\n', 'utf-8');
|
atomicWriteSync(reportsIndexPath(basePath), JSON.stringify(index, null, 2) + '\n', 'utf-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Write a report snapshot ──────────────────────────────────────────────────
|
// ─── Write a report snapshot ──────────────────────────────────────────────────
|
||||||
|
|
@ -121,7 +122,7 @@ export function writeReportSnapshot(args: WriteReportSnapshotArgs): string {
|
||||||
const filename = `${prefix}-${timestamp}.html`;
|
const filename = `${prefix}-${timestamp}.html`;
|
||||||
const filePath = join(dir, filename);
|
const filePath = join(dir, filename);
|
||||||
|
|
||||||
writeFileSync(filePath, args.html, 'utf-8');
|
atomicWriteSync(filePath, args.html, 'utf-8');
|
||||||
|
|
||||||
// Load or init registry
|
// Load or init registry
|
||||||
const existing = loadReportsIndex(args.basePath);
|
const existing = loadReportsIndex(args.basePath);
|
||||||
|
|
@ -170,7 +171,7 @@ export function writeReportSnapshot(args: WriteReportSnapshotArgs): string {
|
||||||
|
|
||||||
export function regenerateHtmlIndex(basePath: string, index: ReportsIndex): void {
|
export function regenerateHtmlIndex(basePath: string, index: ReportsIndex): void {
|
||||||
const html = buildIndexHtml(index);
|
const html = buildIndexHtml(index);
|
||||||
writeFileSync(reportsHtmlIndexPath(basePath), html, 'utf-8');
|
atomicWriteSync(reportsHtmlIndexPath(basePath), html, 'utf-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildIndexHtml(index: ReportsIndex): string {
|
function buildIndexHtml(index: ReportsIndex): string {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue