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
129 lines • 4.1 kB
JavaScript
/**
* JSON Graph Backend
*
* Default zero-dependency implementation of GraphBackend wrapping the
* existing DependencyGraph adjacency list format. Suitable for projects
* with <5k nodes.
*
* @implements #727
* @source @src/artifacts/graph-backend.ts
* @tests @test/unit/artifacts/graph-backend.test.ts
*/
import { normalizeEdges } from '../types.js';
/**
* JSON-backed graph using plain objects and JS Set operations.
*
* This is the default backend — always available, no external dependencies.
*/
export class JsonGraphBackend {
graph = new Map();
// --- Mutation ---
addNode(id, attrs) {
if (!this.graph.has(id)) {
this.graph.set(id, { upstream: [], downstream: [], attrs: attrs ?? {} });
}
else if (attrs) {
const node = this.graph.get(id);
Object.assign(node.attrs, attrs);
}
}
addEdge(source, target, type = 'depends-on', attrs) {
// Ensure both nodes exist
this.addNode(source);
this.addNode(target);
const sourceNode = this.graph.get(source);
const targetNode = this.graph.get(target);
// Add downstream edge on source
sourceNode.downstream.push({ path: target, type, ...attrs });
// Add upstream edge on target
targetNode.upstream.push({ path: source, type, ...attrs });
}
// --- Query ---
hasNode(id) {
return this.graph.has(id);
}
hasEdge(source, target, edgeType) {
const node = this.graph.get(source);
if (!node)
return false;
return node.downstream.some(e => e.path === target && (!edgeType || e.type === edgeType));
}
getNodeAttrs(id) {
return this.graph.get(id)?.attrs;
}
nodes() {
return [...this.graph.keys()];
}
// --- Traversal ---
neighbors(nodeId, direction, edgeType) {
const node = this.graph.get(nodeId);
if (!node)
return [];
const results = new Set();
if (direction === 'in' || direction === 'both') {
for (const edge of node.upstream) {
if (!edgeType || edge.type === edgeType) {
results.add(edge.path);
}
}
}
if (direction === 'out' || direction === 'both') {
for (const edge of node.downstream) {
if (!edgeType || edge.type === edgeType) {
results.add(edge.path);
}
}
}
return [...results];
}
// --- Set operations ---
intersection(setA, setB) {
const b = new Set(setB);
return setA.filter(x => b.has(x));
}
difference(setA, setB) {
const b = new Set(setB);
return setA.filter(x => !b.has(x));
}
union(setA, setB) {
return [...new Set([...setA, ...setB])];
}
// --- Persistence ---
serialize() {
const result = {};
for (const [id, node] of this.graph) {
result[id] = {
upstream: [...node.upstream],
downstream: [...node.downstream],
};
}
return result;
}
deserialize(data) {
this.graph.clear();
for (const [id, node] of Object.entries(data)) {
const upstream = normalizeEdges(node.upstream);
const downstream = normalizeEdges(node.downstream);
this.graph.set(id, { upstream, downstream, attrs: {} });
}
// Ensure all referenced nodes exist
for (const [, node] of this.graph) {
for (const edge of [...node.upstream, ...node.downstream]) {
if (!this.graph.has(edge.path)) {
this.graph.set(edge.path, { upstream: [], downstream: [], attrs: {} });
}
}
}
}
nodeCount() {
return this.graph.size;
}
edgeCount() {
let count = 0;
for (const [, node] of this.graph) {
count += node.downstream.length;
}
return count;
}
}
//# sourceMappingURL=json-backend.js.map