feat: confluence benchmark, pattern extractor, agent KB, UX spec

- extract-patterns.js: mines layered arch, ArgoCD appsets, cloud regions,
  CIDR allocations, naming conventions, sync waves, tech stack from code
- agent-kb.js: token-efficient JSON rendering of same doc tree
- eval-confluence-ref-questions.json: 32 reference-only benchmark questions
- wiggum-v2.sh: Ralph Wiggum loop targeting confluence baseline (77.8%)
- docs/human-ux-spec.md: BMad UX designer spec for human doc structure
- Eval results: V2 at 28.7% vs confluence 77.8% baseline
- Hub/spoke ownership now correctly extracted (95% on that question)
- Naming conventions, regions, CIDRs surfaced in system-architecture.md
This commit is contained in:
Jarvis Prime
2026-03-10 14:20:35 +00:00
parent 049609a358
commit 0265ec7a60
844 changed files with 2129910 additions and 30 deletions

150
impact.js Normal file
View File

@@ -0,0 +1,150 @@
const GraphStore = require('./graph.js');
/**
* Phase 9: Change Impact Query
* Given a node ID, traverse graph edges to find all affected downstream entities.
* Uses reverse BFS on DEPENDS_ON, IMPORTS, CALLS, and CONTAINS edges.
*/
/**
* Query the blast radius of changing a given node.
* @param {GraphStore} graph - The populated knowledge graph.
* @param {string} targetNodeId - The node being modified.
* @param {number} maxDepth - Max traversal depth (default 10).
* @returns {Object} { target, impacted: [...], tree: {...}, depth, circular: [...] }
*/
function queryImpact(graph, targetNodeId, maxDepth = 10) {
const impacted = [];
const visited = new Set();
const circular = [];
const tree = { id: targetNodeId, depth: 0, children: [] };
const treeMap = new Map(); // nodeId → tree node for parent linking
visited.add(targetNodeId);
treeMap.set(targetNodeId, tree);
// Build reverse adjacency: target → [sources] for relevant edge types
const reverseAdj = new Map();
const IMPACT_EDGE_TYPES = new Set(['DEPENDS_ON', 'IMPORTS', 'CALLS', 'CONTAINS', 'USES']);
for (const edge of graph.edges) {
if (!IMPACT_EDGE_TYPES.has(edge.type)) continue;
if (!reverseAdj.has(edge.target)) reverseAdj.set(edge.target, []);
reverseAdj.get(edge.target).push({ source: edge.source, type: edge.type });
}
// BFS
const queue = [{ id: targetNodeId, depth: 0 }];
let head = 0;
while (head < queue.length) {
const { id: currentId, depth } = queue[head++];
if (depth >= maxDepth) continue;
const dependents = reverseAdj.get(currentId) || [];
for (const { source, type } of dependents) {
if (visited.has(source)) {
circular.push({ from: source, to: currentId, type });
continue;
}
visited.add(source);
const node = graph.nodes.get(source);
const entry = {
id: source,
name: node?.name || source,
kind: node?.kind || node?.type || 'unknown',
depth: depth + 1,
via: type,
from: currentId
};
impacted.push(entry);
// Build tree
const treeNode = { id: source, depth: depth + 1, via: type, children: [] };
treeMap.set(source, treeNode);
const parentTree = treeMap.get(currentId);
if (parentTree) parentTree.children.push(treeNode);
queue.push({ id: source, depth: depth + 1 });
}
}
return {
target: targetNodeId,
targetNode: graph.nodes.get(targetNodeId) || null,
impacted,
impactedCount: impacted.length,
maxDepthReached: impacted.some(i => i.depth >= maxDepth),
circular,
tree
};
}
/**
* Format impact result as markdown.
*/
function formatImpactMarkdown(result) {
const lines = [];
lines.push(`## Change Impact: \`${result.target}\``);
lines.push('');
if (result.targetNode) {
lines.push(`**Kind:** ${result.targetNode.kind || result.targetNode.type}`);
lines.push('');
}
lines.push(`**Total impacted:** ${result.impactedCount} entities`);
lines.push('');
if (result.impacted.length === 0) {
lines.push('No downstream dependents found.');
return lines.join('\n');
}
// Group by depth
const byDepth = new Map();
for (const item of result.impacted) {
if (!byDepth.has(item.depth)) byDepth.set(item.depth, []);
byDepth.get(item.depth).push(item);
}
for (const [depth, items] of [...byDepth.entries()].sort((a, b) => a[0] - b[0])) {
lines.push(`### Depth ${depth}`);
for (const item of items) {
lines.push(`- \`${item.id}\` (${item.kind}) via ${item.via}`);
}
lines.push('');
}
if (result.circular.length > 0) {
lines.push('### Circular References');
for (const c of result.circular) {
lines.push(`- ${c.from}${c.to} (${c.type})`);
}
}
return lines.join('\n');
}
// CLI
if (require.main === module) {
const snapshotPath = process.argv[2];
const targetId = process.argv[3];
const maxDepth = parseInt(process.argv[4]) || 10;
const format = process.argv[5] || 'json';
if (!snapshotPath || !targetId) {
console.error('Usage: node impact.js <snapshot.json> <targetNodeId> [maxDepth] [json|md]');
process.exit(1);
}
const graph = GraphStore.loadSnapshot(snapshotPath);
const result = queryImpact(graph, targetId, maxDepth);
if (format === 'md') {
console.log(formatImpactMarkdown(result));
} else {
console.log(JSON.stringify(result, null, 2));
}
}
module.exports = { queryImpact, formatImpactMarkdown };