UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

91 lines 3 kB
/** * Checksum Manifest — Fast Change Detection * * Stores `{ path, checksum, mtime, size }` per indexed file to enable * two-phase change detection: * Phase 1: stat() comparison (no file read) — filter out unchanged files * Phase 2: content read + checksum — only for candidates where stat differs * * This avoids reading every file on `aiwg index build` when most haven't * changed, reducing rebuild time from O(N reads) to O(N stats + M reads) * where M << N on typical projects. * * @implements #794 * @source @src/artifacts/index-builder.ts */ import fs from 'fs'; import path from 'path'; const MANIFEST_FILENAME = 'checksum-manifest.json'; /** * Build a manifest entry from a file's current stat + checksum */ export function makeEntry(checksum, stat) { return { checksum, mtime: stat.mtime.toISOString(), size: stat.size, }; } /** * Load the checksum manifest from an index directory. * Returns an empty manifest if the file is missing or malformed. */ export function loadManifest(indexDir) { const manifestPath = path.join(indexDir, MANIFEST_FILENAME); if (!fs.existsSync(manifestPath)) { return { version: 1, generated: '', entries: {} }; } try { const raw = fs.readFileSync(manifestPath, 'utf-8'); const parsed = JSON.parse(raw); if (parsed.version !== 1 || !parsed.entries) { return { version: 1, generated: '', entries: {} }; } return parsed; } catch { return { version: 1, generated: '', entries: {} }; } } /** * Write the checksum manifest atomically (write to temp, rename). */ export function writeManifest(indexDir, manifest) { fs.mkdirSync(indexDir, { recursive: true }); const manifestPath = path.join(indexDir, MANIFEST_FILENAME); const tmpPath = `${manifestPath}.tmp`; const payload = { version: 1, generated: new Date().toISOString(), entries: manifest.entries, }; fs.writeFileSync(tmpPath, JSON.stringify(payload, null, 2), 'utf-8'); fs.renameSync(tmpPath, manifestPath); } /** * Phase 1 check: can we skip this file based on stat alone? * * Returns true if the file's current mtime and size match the manifest entry — * in which case we presume the content is unchanged and skip the read. */ export function statMatches(stat, entry) { if (!entry) return false; return stat.mtime.toISOString() === entry.mtime && stat.size === entry.size; } /** * Remove stale entries (files that no longer exist on disk) from the manifest. * Returns the count removed. */ export function pruneManifest(manifest, cwd) { let removed = 0; for (const relativePath of Object.keys(manifest.entries)) { const fullPath = path.join(cwd, relativePath); if (!fs.existsSync(fullPath)) { delete manifest.entries[relativePath]; removed++; } } return removed; } //# sourceMappingURL=checksum-manifest.js.map