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
90 lines • 3.2 kB
JavaScript
/**
* Semantic enrichment sidecar store.
*
* Storage:
* .aiwg/index/semantic/<sanitized-id>.json
*
* Sanitization: replace `/` with `__` and any non-[a-zA-Z0-9._-] with `_`.
* Preserves `.md`, `.ts`, etc. extensions readably while keeping filesystem-safe.
*
* @implements #1204
*/
import { createHash, } from 'node:crypto';
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync, } from 'node:fs';
import { join } from 'node:path';
const SEMANTIC_ROOT_DEFAULT = '.aiwg/index/semantic';
export function resolveSemanticRoot(cwd = process.cwd()) {
return join(cwd, SEMANTIC_ROOT_DEFAULT);
}
export function sanitizeId(id) {
return id.replace(/\//g, '__').replace(/[^a-zA-Z0-9._\-]/g, '_');
}
function entryPath(root, id) {
return join(root, `${sanitizeId(id)}.json`);
}
export function has(root, id) {
return existsSync(entryPath(root, id));
}
export function get(root, id) {
const file = entryPath(root, id);
if (!existsSync(file))
throw new Error(`No enrichment for: ${id}`);
return JSON.parse(readFileSync(file, 'utf-8'));
}
export function put(root, id, fields) {
mkdirSync(root, { recursive: true });
const file = entryPath(root, id);
writeFileSync(file, JSON.stringify(fields, null, 2), 'utf-8');
return file;
}
export function remove(root, id) {
const file = entryPath(root, id);
if (!existsSync(file))
return false;
rmSync(file);
return true;
}
/** Drop ALL enrichment data — implements `aiwg index enrich --reset`. */
export function reset(root) {
if (!existsSync(root))
return { removed: 0 };
const before = readdirSync(root).filter((f) => f.endsWith('.json'));
rmSync(root, { recursive: true, force: true });
return { removed: before.length };
}
/** Compute the content hash that would be stored as `enrichedHash`. */
export function computeContentHash(content) {
return createHash('sha256').update(content).digest('hex');
}
/** List all enrichment entries with staleness flag (caller supplies current hashes). */
export function list(root, currentHashes, now = new Date()) {
if (!existsSync(root))
return [];
const out = [];
for (const file of readdirSync(root)) {
if (!file.endsWith('.json'))
continue;
let fields;
try {
fields = JSON.parse(readFileSync(join(root, file), 'utf-8'));
}
catch {
continue;
}
const id = file.replace(/\.json$/, '').replace(/__/g, '/');
const ageDays = Math.max(0, Math.floor((now.getTime() - new Date(fields.enrichedAt).getTime()) / 86_400_000));
const currentHash = currentHashes[id];
const isStale = currentHash !== undefined && currentHash !== fields.enrichedHash;
out.push({
artifactId: id,
enrichedAt: fields.enrichedAt,
enrichedHash: fields.enrichedHash,
symbolCount: fields.declaredSymbols.length,
citationCount: fields.citations.length,
ageDays,
isStale,
});
}
return out.sort((a, b) => a.artifactId.localeCompare(b.artifactId));
}
//# sourceMappingURL=store.js.map