From 2d86d339c048e2849606800fb1fe6e07172e0ae1 Mon Sep 17 00:00:00 2001 From: Lum1104 Date: Fri, 27 Mar 2026 14:34:42 +0800 Subject: [PATCH] perf(understand): gate LLM graph-reviewer behind --review flag, add inline deterministic validation Co-Authored-By: Claude Opus 4.6 --- .../skills/understand/SKILL.md | 107 ++++++++++++++++-- 1 file changed, 98 insertions(+), 9 deletions(-) diff --git a/understand-anything-plugin/skills/understand/SKILL.md b/understand-anything-plugin/skills/understand/SKILL.md index 71f188f..13cabcd 100644 --- a/understand-anything-plugin/skills/understand/SKILL.md +++ b/understand-anything-plugin/skills/understand/SKILL.md @@ -12,6 +12,7 @@ Analyze the current codebase and produce a `knowledge-graph.json` file in `.unde - `$ARGUMENTS` may contain: - `--full` — Force a full rebuild, ignoring any existing graph + - `--review` — Run full LLM graph-reviewer instead of inline deterministic validation - A directory path — Scope analysis to a specific subdirectory --- @@ -327,7 +328,93 @@ Assemble the full KnowledgeGraph JSON object: 2. Write the assembled graph to `$PROJECT_ROOT/.understand-anything/intermediate/assembled-graph.json`. -3. Dispatch a subagent using the prompt template at `./graph-reviewer-prompt.md`. Read the template file and pass the full content as the subagent's prompt, appending the following additional context: +3. **Check `$ARGUMENTS` for `--review` flag.** Then run the appropriate validation path: + +--- + +#### Default path (no `--review`): inline deterministic validation + +Write the following Node.js script to `$PROJECT_ROOT/.understand-anything/tmp/ua-inline-validate.cjs`: + +```javascript +#!/usr/bin/env node +const fs = require('fs'); +const graphPath = process.argv[2]; +const outputPath = process.argv[3]; +try { + const graph = JSON.parse(fs.readFileSync(graphPath, 'utf8')); + const issues = [], warnings = []; + if (!Array.isArray(graph.nodes)) { issues.push('graph.nodes is missing or not an array'); graph.nodes = []; } + if (!Array.isArray(graph.edges)) { issues.push('graph.edges is missing or not an array'); graph.edges = []; } + const nodeIds = new Set(); + const seen = new Map(); + graph.nodes.forEach((n, i) => { + if (!n.id) { issues.push(`Node[${i}] missing id`); return; } + if (!n.type) issues.push(`Node[${i}] '${n.id}' missing type`); + if (!n.name) issues.push(`Node[${i}] '${n.id}' missing name`); + if (!n.summary) issues.push(`Node[${i}] '${n.id}' missing summary`); + if (!n.tags || !n.tags.length) issues.push(`Node[${i}] '${n.id}' missing tags`); + if (seen.has(n.id)) issues.push(`Duplicate node ID '${n.id}' at indices ${seen.get(n.id)} and ${i}`); + else seen.set(n.id, i); + nodeIds.add(n.id); + }); + graph.edges.forEach((e, i) => { + if (!nodeIds.has(e.source)) issues.push(`Edge[${i}] source '${e.source}' not found`); + if (!nodeIds.has(e.target)) issues.push(`Edge[${i}] target '${e.target}' not found`); + }); + const fileNodes = graph.nodes.filter(n => n.type === 'file').map(n => n.id); + const assigned = new Map(); + (graph.layers || []).forEach(layer => { + (layer.nodeIds || []).forEach(id => { + if (!nodeIds.has(id)) issues.push(`Layer '${layer.id}' refs missing node '${id}'`); + if (assigned.has(id)) issues.push(`Node '${id}' appears in multiple layers`); + assigned.set(id, layer.id); + }); + }); + fileNodes.forEach(id => { + if (!assigned.has(id)) issues.push(`File node '${id}' not in any layer`); + }); + (graph.tour || []).forEach((step, i) => { + (step.nodeIds || []).forEach(id => { + if (!nodeIds.has(id)) issues.push(`Tour step[${i}] refs missing node '${id}'`); + }); + }); + const withEdges = new Set([ + ...graph.edges.map(e => e.source), + ...graph.edges.map(e => e.target) + ]); + graph.nodes.forEach(n => { + if (!withEdges.has(n.id)) warnings.push(`Node '${n.id}' has no edges (orphan)`); + }); + const stats = { + totalNodes: graph.nodes.length, + totalEdges: graph.edges.length, + totalLayers: (graph.layers || []).length, + tourSteps: (graph.tour || []).length, + nodeTypes: graph.nodes.reduce((a, n) => { a[n.type] = (a[n.type]||0)+1; return a; }, {}), + edgeTypes: graph.edges.reduce((a, e) => { a[e.type] = (a[e.type]||0)+1; return a; }, {}) + }; + fs.writeFileSync(outputPath, JSON.stringify({ issues, warnings, stats }, null, 2)); + process.exit(0); +} catch (err) { process.stderr.write(err.message + '\n'); process.exit(1); } +``` + +Execute it: +```bash +node $PROJECT_ROOT/.understand-anything/tmp/ua-inline-validate.cjs \ + "$PROJECT_ROOT/.understand-anything/intermediate/assembled-graph.json" \ + "$PROJECT_ROOT/.understand-anything/intermediate/review.json" +``` + +If the script exits non-zero, read stderr, fix the script, and retry once. + +--- + +#### `--review` path: full LLM reviewer + +If `--review` IS in `$ARGUMENTS`, dispatch the LLM graph-reviewer subagent as follows: + +Dispatch a subagent using the prompt template at `./graph-reviewer-prompt.md`. Read the template file and pass the full content as the subagent's prompt, appending the following additional context: > **Additional context from main session:** > @@ -343,14 +430,16 @@ Assemble the full KnowledgeGraph JSON object: Pass these parameters in the dispatch prompt: - > Validate the knowledge graph at `$PROJECT_ROOT/.understand-anything/intermediate/assembled-graph.json`. - > Project root: `$PROJECT_ROOT` - > Read the file and validate it for completeness and correctness. - > Write output to: `$PROJECT_ROOT/.understand-anything/intermediate/review.json` +> Validate the knowledge graph at `$PROJECT_ROOT/.understand-anything/intermediate/assembled-graph.json`. +> Project root: `$PROJECT_ROOT` +> Read the file and validate it for completeness and correctness. +> Write output to: `$PROJECT_ROOT/.understand-anything/intermediate/review.json` -4. After the subagent completes, read `$PROJECT_ROOT/.understand-anything/intermediate/review.json`. +--- -5. **If `approved: false`:** +4. Read `$PROJECT_ROOT/.understand-anything/intermediate/review.json`. + +5. **If `issues` array is non-empty:** - Review the `issues` list - Apply automated fixes where possible: - Remove edges with dangling references @@ -359,7 +448,7 @@ Pass these parameters in the dispatch prompt: - Re-run the final graph validation after automated fixes - If critical issues remain after one fix attempt, save the graph anyway but include the warnings in the final report and mark dashboard auto-launch as skipped -6. **If `approved: true`:** Proceed to Phase 7. +6. **If `issues` array is empty:** Proceed to Phase 7. --- @@ -401,7 +490,7 @@ Pass these parameters in the dispatch prompt: ## Error Handling - If any subagent dispatch fails, retry **once** with the same prompt plus additional context about the failure. -- Track all warnings and errors from each phase in a `$PHASE_WARNINGS` list. Pass this list to the graph-reviewer in Phase 6 for comprehensive validation. +- Track all warnings and errors from each phase in a `$PHASE_WARNINGS` list. When using `--review`, pass this list to the graph-reviewer in Phase 6. On the default path, include accumulated warnings in the Phase 7 final report. - If it fails a second time, skip that phase and continue with partial results. - ALWAYS save partial results — a partial graph is better than no graph. - Report any skipped phases or errors in the final summary so the user knows what happened.