claude-flow
Version:
Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration
122 lines (110 loc) • 4.56 kB
JavaScript
// adr-verify — read the persisted adr-patterns + adr-edges namespaces, surface
// dangling refs, supersede cycles, and status mismatches.
//
// Companion to scripts/import.mjs. Run after import to validate graph integrity.
// Useful in CI: exits with code 1 if any issues found (gate on graph health).
//
// Usage:
// node scripts/verify.mjs # markdown report
// VERIFY_FORMAT=json node scripts/verify.mjs # JSON for chaining
// VERIFY_STRICT=1 node scripts/verify.mjs # exit 1 on ANY issue (default: only on cycles)
import { spawnSync } from 'node:child_process';
// ADR-100 / #1748 Issue 3 — CLI_CORE=1 routes to lite cli-core (~2s cold-cache).
// verify only does list+retrieve across adr-patterns and adr-edges namespaces;
// no semantic search needed. JSON backend is sufficient.
const CLI_PKG = process.env.CLI_CORE === '1'
? '@claude-flow/cli-core@alpha'
: '@claude-flow/cli@latest';
function memoryListJson(namespace) {
const r = spawnSync('npx', [
CLI_PKG, 'memory', 'list',
'--namespace', namespace, '--format', 'json',
], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8' });
if (r.status !== 0) return [];
const m = /\[[\s\S]*\]/.exec(r.stdout || '');
if (!m) return [];
try { return JSON.parse(m[0]); } catch { return []; }
}
function memoryRetrieve(namespace, key) {
const r = spawnSync('npx', [
CLI_PKG, 'memory', 'retrieve',
'--namespace', namespace, '--key', key,
], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8' });
if (r.status !== 0) return null;
// Strip ANSI / box-drawing
const txt = (r.stdout || '').replace(/\x1b\[[0-9;]*m/g, '');
return txt;
}
const patternEntries = memoryListJson('adr-patterns');
const edgeEntries = memoryListJson('adr-edges');
const adrIds = new Set(
patternEntries.map((e) => (e.key || '').split('::')[0]).filter(Boolean)
);
// Parse edge values to recover {from, to, relation}
const edges = [];
for (const e of edgeEntries) {
const k = e.key || '';
// key format: relation:FROM->TO:timestamp-rand
const m = /^(\w[\w-]*?):(\S+?)->(\S+?):/.exec(k);
if (m) edges.push({ relation: m[1], from: m[2], to: m[3], key: k });
}
const danglingRefs = edges.filter((e) => !adrIds.has(e.to));
const danglingFroms = edges.filter((e) => !adrIds.has(e.from));
// Cycle detection on supersedes (cycle = data corruption — ADR can't supersede itself transitively)
const supersedesGraph = new Map();
for (const e of edges.filter((e) => e.relation === 'supersedes')) {
if (!supersedesGraph.has(e.from)) supersedesGraph.set(e.from, []);
supersedesGraph.get(e.from).push(e.to);
}
const cycles = [];
function findCycle(node, visited, stack) {
if (stack.has(node)) {
cycles.push([...stack, node].join(' → '));
return;
}
if (visited.has(node)) return;
visited.add(node);
stack.add(node);
for (const next of supersedesGraph.get(node) || []) {
findCycle(next, visited, stack);
}
stack.delete(node);
}
for (const n of supersedesGraph.keys()) findCycle(n, new Set(), new Set());
const result = {
adrCount: adrIds.size,
edgeCount: edges.length,
byRelation: edges.reduce((acc, e) => { acc[e.relation] = (acc[e.relation] || 0) + 1; return acc; }, {}),
danglingRefs,
danglingFroms,
cycles: [...new Set(cycles)],
};
if (process.env.VERIFY_FORMAT === 'json') {
console.log(JSON.stringify(result, null, 2));
} else {
console.log('## ADR Graph Verification');
console.log('');
console.log(`| Metric | Value |`);
console.log(`|---|---:|`);
console.log(`| ADRs in adr-patterns | ${result.adrCount} |`);
console.log(`| Edges in adr-edges | ${result.edgeCount} |`);
for (const [k, n] of Object.entries(result.byRelation).sort((a, b) => b[1] - a[1])) {
console.log(`| edges (${k}) | ${n} |`);
}
console.log(`| Dangling 'to' refs | ${result.danglingRefs.length} |`);
console.log(`| Dangling 'from' refs | ${result.danglingFroms.length} |`);
console.log(`| Supersede cycles | ${result.cycles.length} |`);
if (result.danglingRefs.length) {
console.log('\n### Sample dangling refs');
for (const d of result.danglingRefs.slice(0, 8)) console.log(`- ${d.relation} ${d.from} → ${d.to} (missing)`);
}
if (result.cycles.length) {
console.log('\n### Cycles (DATA CORRUPTION — fix immediately)');
for (const c of result.cycles) console.log(`- ${c}`);
}
}
const strict = process.env.VERIFY_STRICT === '1';
if (result.cycles.length || (strict && (result.danglingRefs.length || result.danglingFroms.length))) {
process.exit(1);
}