@phren/cli
Version:
Knowledge layer for AI agents - CLI, MCP server, and data layer
65 lines (64 loc) • 2.39 kB
JavaScript
import * as fs from "fs";
import * as path from "path";
import * as crypto from "crypto";
import { getProjectDirs } from "../shared.js";
import { TASK_FILE_ALIASES } from "../data/tasks.js";
import { FINDINGS_FILENAME } from "../data/access.js";
import { errorMessage } from "../utils.js";
import { logger } from "../logger.js";
function fileChecksum(filePath) {
const content = fs.readFileSync(filePath);
return crypto.createHash("sha256").update(content).digest("hex");
}
function checksumStorePath(phrenPath) {
return path.join(phrenPath, ".config", "file-checksums.json");
}
function loadChecksums(phrenPath) {
const file = checksumStorePath(phrenPath);
if (!fs.existsSync(file))
return {};
try {
return JSON.parse(fs.readFileSync(file, "utf8"));
}
catch (err) {
logger.debug("checksums", `loadChecksums: ${errorMessage(err)}`);
return {};
}
}
function saveChecksums(phrenPath, store) {
const file = checksumStorePath(phrenPath);
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, JSON.stringify(store, null, 2) + "\n");
}
export function updateFileChecksums(phrenPath, profileName) {
const store = loadChecksums(phrenPath);
const now = new Date().toISOString();
const tracked = [];
const dirs = getProjectDirs(phrenPath, profileName);
for (const dir of dirs) {
for (const name of [FINDINGS_FILENAME, ...TASK_FILE_ALIASES, "truths.md"]) {
const full = path.join(dir, name);
if (!fs.existsSync(full))
continue;
const rel = path.relative(phrenPath, full).replace(/\\/g, "/");
store[rel] = { sha256: fileChecksum(full), updatedAt: now };
tracked.push(rel);
}
}
saveChecksums(phrenPath, store);
return { updated: tracked.length, files: tracked };
}
export function verifyFileChecksums(phrenPath) {
const store = loadChecksums(phrenPath);
const results = [];
for (const [rel, entry] of Object.entries(store)) {
const full = path.join(phrenPath, rel);
if (!fs.existsSync(full)) {
results.push({ file: rel, status: "missing" });
continue;
}
const current = fileChecksum(full);
results.push({ file: rel, status: current === entry.sha256 ? "ok" : "mismatch" });
}
return results;
}