fix(code-intelligence): consent that means what it says — polarity, receipts, read-only veto

Four review findings on the wave's own Phase 1 port, all red-first:
'consent <repo> no' recorded consent GRANTED (the CLI ignored the
argument and always wrote true) — yes|no is now required and garbage
records nothing; Sourcebot egress receipts claimed consented=true on
paths that never checked consent — the actual consent state is threaded
into every receipt, search is fail-closed on non-loopback, and the
liveness probe's receipt says truthfully that it sends no repo content;
repoPolicyVeto only honored the deny tier while gbrain refresh writes
pages — write-class ops now veto on read-only too, matching the sync
chokepoint, via one shared lib/gbrain-repo-policy-client.ts (win32
bash invocation, spawn-vs-unreadable error distinction) used by both
call sites. Also: source ids get a host+path hash (same-name repos no
longer collide), refresh timeout raised to 120s, availability probes
run concurrently at 3s, graphify status stops JSON.parsing 100MB graphs
for a count, and every ported file carries the fork MIT notice.
+15 tests across the two suites.
This commit is contained in:
Garry Tan
2026-08-14 17:13:22 -07:00
parent 531d9a6e1f
commit 9488173b0a
13 changed files with 640 additions and 100 deletions
+22 -5
View File
@@ -1,6 +1,8 @@
/**
* Graphify adapter — real CLI integration (github.com/Graphify-Labs/graphify).
*
* Portions copyright (c) 2026 Sina Matian, time-attack/gstack (GStack 2), MIT.
*
* Graphify is a LOCAL tree-sitter knowledge graph. For CODE, `graphify <dir>`
* and `graphify update <dir>` produce the SAME AST graph with NO LLM and NO
* network (verified against graphify 0.9.23 — both emit `AST extraction on N
@@ -26,7 +28,7 @@
*/
import { spawnSync } from "child_process";
import { existsSync, readFileSync } from "fs";
import { existsSync, readFileSync, statSync } from "fs";
import { join } from "path";
import {
assertCapability,
@@ -47,6 +49,13 @@ const OUT_DIR = "graphify-out";
const GRAPH_JSON = "graph.json";
const DEFAULT_TIMEOUT_MS = 120_000; // indexing a repo can take a while
const NEEDS_SHELL_ON_WINDOWS = process.platform === "win32"; // graphify is a shim on Windows
/**
* status() only parses graph.json for a node count when the file is at most
* this big. On the 1000+-file repos this feature targets, graph.json can run
* to hundreds of MB — JSON.parsing that for a cosmetic count is a heap spike.
* Above the threshold the display reports the file size instead.
*/
const STATUS_PARSE_MAX_BYTES = 5 * 1024 * 1024;
export interface GraphifyOptions {
/** Directory whose `graphify-out/` search/status/export read. Defaults to cwd. */
@@ -127,13 +136,21 @@ export class GraphifyProvider implements CodeProvider {
const graphPath = join(dir, OUT_DIR, GRAPH_JSON);
if (!existsSync(graphPath)) return { id: dir, state: "absent" };
let itemCount: number | undefined;
let detail = graphPath;
try {
const graph = JSON.parse(readFileSync(graphPath, "utf-8")) as { nodes?: unknown[] };
if (Array.isArray(graph.nodes)) itemCount = graph.nodes.length;
// stat first: parse the whole graph only when it's small (the node count
// is display-only, never worth a hundreds-of-MB JSON.parse heap spike).
const size = statSync(graphPath).size;
if (size <= STATUS_PARSE_MAX_BYTES) {
const graph = JSON.parse(readFileSync(graphPath, "utf-8")) as { nodes?: unknown[] };
if (Array.isArray(graph.nodes)) itemCount = graph.nodes.length;
} else {
detail = `${graphPath} (${(size / (1024 * 1024)).toFixed(1)} MB graph; node count skipped)`;
}
} catch {
// graph.json present but unparseable — still ready, just no count.
// graph.json present but unstatable/unparseable — still ready, just no count.
}
return { id: dir, state: "ready", itemCount, detail: graphPath };
return { id: dir, state: "ready", itemCount, detail };
}
async export(source: SourceRef, _opts: OpOptions = {}): Promise<string> {