deepdebug-local-agent 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/.github/workflows/deploy-gcp.yml +58 -26
  2. package/CHANGELOG.md +6 -0
  3. package/docs/la-protocol-v2.md +102 -0
  4. package/package.json +2 -1
  5. package/scripts/sync-la-protocol-v2-contract.mjs +58 -0
  6. package/src/la-protocol-v2/chunks.js +45 -0
  7. package/src/la-protocol-v2/config.js +27 -0
  8. package/src/la-protocol-v2/content-handlers/audit.js +36 -0
  9. package/src/la-protocol-v2/content-handlers/config.js +48 -0
  10. package/src/la-protocol-v2/content-handlers/egress.js +65 -0
  11. package/src/la-protocol-v2/content-handlers/git-diff.js +92 -0
  12. package/src/la-protocol-v2/content-handlers/handler.js +170 -0
  13. package/src/la-protocol-v2/content-handlers/index.js +6 -0
  14. package/src/la-protocol-v2/content-handlers/install.js +50 -0
  15. package/src/la-protocol-v2/content-handlers/path-guard.js +142 -0
  16. package/src/la-protocol-v2/content-handlers/ref-registry.js +87 -0
  17. package/src/la-protocol-v2/content.js +63 -0
  18. package/src/la-protocol-v2/contract/CONTRACT_SOURCE.json +81 -0
  19. package/src/la-protocol-v2/contract/README.md +103 -0
  20. package/src/la-protocol-v2/contract/ack.schema.json +51 -0
  21. package/src/la-protocol-v2/contract/alert.schema.json +62 -0
  22. package/src/la-protocol-v2/contract/approval.decision.schema.json +57 -0
  23. package/src/la-protocol-v2/contract/audit.event.schema.json +86 -0
  24. package/src/la-protocol-v2/contract/config.update.schema.json +73 -0
  25. package/src/la-protocol-v2/contract/content.request.schema.json +110 -0
  26. package/src/la-protocol-v2/contract/content.response.schema.json +90 -0
  27. package/src/la-protocol-v2/contract/defs.schema.json +191 -0
  28. package/src/la-protocol-v2/contract/envelope.schema.json +89 -0
  29. package/src/la-protocol-v2/contract/error.schema.json +59 -0
  30. package/src/la-protocol-v2/contract/heartbeat.schema.json +100 -0
  31. package/src/la-protocol-v2/contract/hello.schema.json +111 -0
  32. package/src/la-protocol-v2/contract/incident.candidate.schema.json +102 -0
  33. package/src/la-protocol-v2/contract/index.json +166 -0
  34. package/src/la-protocol-v2/contract/job.assign.schema.json +212 -0
  35. package/src/la-protocol-v2/contract/job.cancel.schema.json +52 -0
  36. package/src/la-protocol-v2/contract/job.event.schema.json +115 -0
  37. package/src/la-protocol-v2/contract/job.result.schema.json +100 -0
  38. package/src/la-protocol-v2/contract/log.stats.schema.json +79 -0
  39. package/src/la-protocol-v2/contract/ready.schema.json +106 -0
  40. package/src/la-protocol-v2/contract/register.schema.json +92 -0
  41. package/src/la-protocol-v2/contract/scan.upload.schema.json +59 -0
  42. package/src/la-protocol-v2/contract/scan.uploaded.schema.json +51 -0
  43. package/src/la-protocol-v2/contract/webhook.forward.schema.json +69 -0
  44. package/src/la-protocol-v2/contract.js +90 -0
  45. package/src/la-protocol-v2/errors.js +34 -0
  46. package/src/la-protocol-v2/index.js +8 -0
  47. package/src/la-protocol-v2/link.js +93 -0
  48. package/src/la-protocol-v2/runtime.js +81 -0
  49. package/src/la-protocol-v2/session.js +650 -0
  50. package/src/la-protocol-v2/strict-json.js +47 -0
  51. package/src/server.js +25 -4
  52. package/test/contract/la-protocol-v2/vectors/ack.vectors.json +149 -0
  53. package/test/contract/la-protocol-v2/vectors/alert.vectors.json +101 -0
  54. package/test/contract/la-protocol-v2/vectors/approval.decision.vectors.json +165 -0
  55. package/test/contract/la-protocol-v2/vectors/audit.event.vectors.json +156 -0
  56. package/test/contract/la-protocol-v2/vectors/config.update.vectors.json +189 -0
  57. package/test/contract/la-protocol-v2/vectors/content.request.vectors.json +325 -0
  58. package/test/contract/la-protocol-v2/vectors/content.response.vectors.json +413 -0
  59. package/test/contract/la-protocol-v2/vectors/cross-type.vectors.json +260 -0
  60. package/test/contract/la-protocol-v2/vectors/error.vectors.json +179 -0
  61. package/test/contract/la-protocol-v2/vectors/heartbeat.vectors.json +109 -0
  62. package/test/contract/la-protocol-v2/vectors/hello.vectors.json +224 -0
  63. package/test/contract/la-protocol-v2/vectors/incident.candidate.vectors.json +186 -0
  64. package/test/contract/la-protocol-v2/vectors/job.assign.vectors.json +631 -0
  65. package/test/contract/la-protocol-v2/vectors/job.cancel.vectors.json +133 -0
  66. package/test/contract/la-protocol-v2/vectors/job.event.vectors.json +233 -0
  67. package/test/contract/la-protocol-v2/vectors/job.result.vectors.json +137 -0
  68. package/test/contract/la-protocol-v2/vectors/log.stats.vectors.json +137 -0
  69. package/test/contract/la-protocol-v2/vectors/ready.vectors.json +177 -0
  70. package/test/contract/la-protocol-v2/vectors/register.vectors.json +399 -0
  71. package/test/contract/la-protocol-v2/vectors/scan.upload.vectors.json +137 -0
  72. package/test/contract/la-protocol-v2/vectors/scan.uploaded.vectors.json +101 -0
  73. package/test/contract/la-protocol-v2/vectors/traces/invalid-agent-mismatch.json +140 -0
  74. package/test/contract/la-protocol-v2/vectors/traces/invalid-chunk-after-last.json +200 -0
  75. package/test/contract/la-protocol-v2/vectors/traces/invalid-chunk-byte-length-mismatch.json +174 -0
  76. package/test/contract/la-protocol-v2/vectors/traces/invalid-chunk-last-flag.json +200 -0
  77. package/test/contract/la-protocol-v2/vectors/traces/invalid-chunk-out-of-order.json +200 -0
  78. package/test/contract/la-protocol-v2/vectors/traces/invalid-chunk-total-changes.json +200 -0
  79. package/test/contract/la-protocol-v2/vectors/traces/invalid-clock-skew.json +46 -0
  80. package/test/contract/la-protocol-v2/vectors/traces/invalid-event-before-ready.json +72 -0
  81. package/test/contract/la-protocol-v2/vectors/traces/invalid-first-frame-not-register.json +48 -0
  82. package/test/contract/la-protocol-v2/vectors/traces/invalid-frame-over-48kib.json +141 -0
  83. package/test/contract/la-protocol-v2/vectors/traces/invalid-la-sends-job-assign.json +173 -0
  84. package/test/contract/la-protocol-v2/vectors/traces/invalid-params-raw-differs-from-payload.json +172 -0
  85. package/test/contract/la-protocol-v2/vectors/traces/invalid-payload-above-limit.json +174 -0
  86. package/test/contract/la-protocol-v2/vectors/traces/invalid-resume-restarts-seq.json +144 -0
  87. package/test/contract/la-protocol-v2/vectors/traces/invalid-seq-gap.json +166 -0
  88. package/test/contract/la-protocol-v2/vectors/traces/invalid-seq-repeated.json +166 -0
  89. package/test/contract/la-protocol-v2/vectors/traces/invalid-sig-tenant-mismatch.json +172 -0
  90. package/test/contract/la-protocol-v2/vectors/traces/invalid-signature-warn-with-enforce-required.json +77 -0
  91. package/test/contract/la-protocol-v2/vectors/traces/invalid-tenant-mismatch.json +140 -0
  92. package/test/contract/la-protocol-v2/vectors/traces/invalid-type-outside-allowlist.json +140 -0
  93. package/test/contract/la-protocol-v2/vectors/traces/valid-content-chunks-in-order.json +224 -0
  94. package/test/contract/la-protocol-v2/vectors/traces/valid-handshake-and-job.json +266 -0
  95. package/test/contract/la-protocol-v2/vectors/traces/valid-resume-continues-seq.json +168 -0
  96. package/test/contract/la-protocol-v2/vectors/traces/valid-retransmit-is-idempotent.json +193 -0
  97. package/test/contract/la-protocol-v2/vectors/webhook.forward.vectors.json +170 -0
  98. package/test/la-protocol-v2/content-handlers.test.js +633 -0
  99. package/test/la-protocol-v2/content-path-guard.test.js +214 -0
  100. package/test/la-protocol-v2/content-wiring.test.js +225 -0
  101. package/test/la-protocol-v2/contract.test.js +131 -0
  102. package/test/la-protocol-v2/helpers/content-rig.js +94 -0
  103. package/test/la-protocol-v2/helpers/la-rig.js +83 -0
  104. package/test/la-protocol-v2/helpers/reference-cp.js +349 -0
  105. package/test/la-protocol-v2/helpers/spawn-agent.js +36 -0
  106. package/test/la-protocol-v2/link.test.js +141 -0
  107. package/test/la-protocol-v2/session-inbound.test.js +279 -0
  108. package/test/la-protocol-v2/session-outbound.test.js +382 -0
  109. package/test/la-protocol-v2/traces.test.js +270 -0
  110. package/test/la-protocol-v2/tunnel-e2e.test.js +124 -0
  111. package/tools/graphify-validation/extras/README.md +19 -0
  112. package/tools/graphify-validation/extras/bymethod.py +35 -0
  113. package/tools/graphify-validation/extras/docker/Dockerfile +16 -0
  114. package/tools/graphify-validation/extras/docker/inner.sh +7 -0
  115. package/tools/graphify-validation/extras/g10.py +59 -0
  116. package/tools/graphify-validation/extras/g3aug.py +49 -0
  117. package/tools/graphify-validation/extras/hybrid.py +34 -0
  118. package/tools/graphify-validation/extras/truth2.py +44 -0
  119. package/tools/interop/LaV2NodeContentInteropTest.java +248 -0
  120. package/tools/interop/LaV2NodeInteropTest.java +266 -0
  121. package/tools/interop/README.md +38 -0
  122. package/tools/interop/la-interop-client.mjs +97 -0
  123. package/tools/interop/la-interop-content-client.mjs +91 -0
@@ -0,0 +1,124 @@
1
+ // F3-05b end to end: the real agent process (src/server.js) against the reference CP.
2
+ // - flag unset / false: the v1 client, nothing of v2 on the wire (characterization; the frames are the v1 ones)
3
+ // - flag true: v2 handshake with a signed ready, v1 commands still answered on the same connection
4
+ // - flag true against a gateway without v2: falls back to the v1 register on the same connection
5
+ // - flag true without an agent id: stays v1
6
+ import { test } from "node:test";
7
+ import assert from "node:assert/strict";
8
+ import { ReferenceCpServer, TENANT, AGENT, waitFor } from "./helpers/reference-cp.js";
9
+ import { runAgent } from "./helpers/spawn-agent.js";
10
+
11
+ const baseEnv = (srv) => ({
12
+ TENANT_ID: TENANT,
13
+ LOCAL_AGENT_SIGNATURE_MODE: "enforce",
14
+ LOCAL_AGENT_SIGNING_PUBLIC_KEYS: `${srv.keyId}=${srv.publicKeyRaw}`,
15
+ });
16
+
17
+ for (const flag of [undefined, "false", "off", "0", "bogus"]) {
18
+ test(`flag ${flag === undefined ? "unset" : JSON.stringify(flag)}: the v1 client, exactly: legacy register, no v2 frame, v1 command answered`, async () => {
19
+ const srv = await new ReferenceCpServer({}).start();
20
+ try {
21
+ await runAgent({
22
+ // everything v2 needs IS configured (UUID tenant, agent id, trusted key): only the flag keeps it off
23
+ srv, cfg: { authorizationId: AGENT, tenantId: TENANT }, env: { ...baseEnv(srv), LOCAL_AGENT_PROTOCOL_V2: flag },
24
+ until: async () => {
25
+ if (srv.ws && srv.legacy.some((f) => f.type === "register") && !srv.sent) {
26
+ srv.sent = true;
27
+ srv.ws.send(srv.cp.v1Command("health", {}));
28
+ }
29
+ return srv.legacy.some((f) => f.command === "health");
30
+ },
31
+ });
32
+ const reg = srv.legacy.find((f) => f.type === "register");
33
+ assert.deepEqual(Object.keys(reg).sort(), ["port", "tenantId", "type", "workspacePath"]);
34
+ assert.equal(reg.tenantId, TENANT);
35
+ const health = srv.legacy.find((f) => f.command === "health");
36
+ assert.equal(health.status, "ok");
37
+ assert.equal(srv.cp.rawIn.length, 0, "no v2 frame was ever sent");
38
+ assert.equal(srv.cp.registers.length, 0);
39
+ assert.equal(srv.textsFromLa.some((t) => t.includes("schemaVersion")), false);
40
+ } finally { await srv.stop(); }
41
+ });
42
+ }
43
+
44
+ test("flag true: v2 handshake (register, hello, signed ready), no v1 register, and a v1 command is still answered on the same connection", async () => {
45
+ const srv = await new ReferenceCpServer({ requireEnforce: true }).start();
46
+ try {
47
+ await runAgent({
48
+ srv, cfg: { authorizationId: AGENT, tenantId: TENANT },
49
+ env: { ...baseEnv(srv), LOCAL_AGENT_PROTOCOL_V2: "true" },
50
+ until: async () => {
51
+ if (srv.cp.state === "ready" && !srv.sent) { srv.sent = true; srv.ws.send(srv.cp.v1Command("health", {})); }
52
+ return srv.legacy.some((f) => f.command === "health");
53
+ },
54
+ });
55
+ assert.equal(srv.connections, 1);
56
+ const reg = srv.cp.registers[0];
57
+ assert.equal(reg.protocol, 2);
58
+ assert.equal(reg.signatureMode, "enforce");
59
+ assert.match(reg.laVersion, /^\d+\.\d+\.\d+$/);
60
+ assert.deepEqual(reg.capabilities, ["chunk.base64", "protocol.v2", "v1.commands"]);
61
+ assert.ok(reg.workspaces.every((w) => /^[0-9a-f]{64}$/.test(w.pathSha256)), "only hashes of paths");
62
+ assert.equal(JSON.stringify(srv.cp.rawIn).includes("/tmp") || JSON.stringify(srv.cp.rawIn).includes("v2e2e-ws"), false, "no workspace path on the wire");
63
+ assert.equal(srv.cp.hellos[0].signatureMode, "enforce");
64
+ assert.equal(srv.legacy.some((f) => f.type === "register"), false, "no v1 register: v2 owns the registration");
65
+ assert.deepEqual(srv.cp.errorsSent, []);
66
+ assert.equal(srv.legacy.find((f) => f.command === "health").status, "ok", "v1 command on a v2 connection");
67
+ } finally { await srv.stop(); }
68
+ });
69
+
70
+ test("flag true, the CP sends a signed v2 job.cancel: acked `rejected protocol_unsupported` (no handler yet), connection stays up", async () => {
71
+ const srv = await new ReferenceCpServer({ requireEnforce: true }).start();
72
+ try {
73
+ await runAgent({
74
+ srv, cfg: { authorizationId: AGENT, tenantId: TENANT },
75
+ env: { ...baseEnv(srv), LOCAL_AGENT_PROTOCOL_V2: "true" },
76
+ until: async () => {
77
+ if (srv.cp.state === "ready" && !srv.sent) { srv.sent = true; srv.cp.sendData("job.cancel", { jobId: "c3d4e5f6-a7b8-4c9d-8e0f-2a3b4c5d6e7f", reason: "user" }); }
78
+ return srv.cp.acksFromLa.length > 0;
79
+ },
80
+ });
81
+ assert.deepEqual(srv.cp.acksFromLa, [{ ackSeq: 1, status: "rejected", errorCode: "protocol_unsupported" }]);
82
+ assert.deepEqual(srv.cp.errorsSent, []);
83
+ } finally { await srv.stop(); }
84
+ });
85
+
86
+ test("flag true against a gateway that does not speak v2: after the handshake timeout the v1 register goes out on the same connection", async () => {
87
+ const srv = await new ReferenceCpServer({ v2Disabled: true }).start();
88
+ try {
89
+ await runAgent({
90
+ srv, cfg: { authorizationId: AGENT, tenantId: TENANT },
91
+ env: { ...baseEnv(srv), LOCAL_AGENT_PROTOCOL_V2: "true", LOCAL_AGENT_V2_HANDSHAKE_MS: "300" },
92
+ until: async () => srv.legacy.some((f) => f.type === "register"),
93
+ });
94
+ const reg = srv.legacy.find((f) => f.type === "register");
95
+ assert.deepEqual(Object.keys(reg).sort(), ["port", "tenantId", "type", "workspacePath"]);
96
+ assert.equal(srv.connections, 1, "same connection");
97
+ } finally { await srv.stop(); }
98
+ });
99
+
100
+ test("flag true without an agent id (no authorizationId): v1 only, nothing of v2 on the wire", async () => {
101
+ const srv = await new ReferenceCpServer({}).start();
102
+ try {
103
+ await runAgent({
104
+ srv, env: { ...baseEnv(srv), LOCAL_AGENT_PROTOCOL_V2: "true" },
105
+ until: async () => srv.legacy.some((f) => f.type === "register"),
106
+ });
107
+ assert.equal(srv.cp.rawIn.length, 0);
108
+ assert.ok(srv.legacy.some((f) => f.type === "register"));
109
+ } finally { await srv.stop(); }
110
+ });
111
+
112
+ test("flag true with a tenant id that is not a UUID: v1 only", async () => {
113
+ const srv = await new ReferenceCpServer({}).start();
114
+ try {
115
+ await runAgent({
116
+ srv, cfg: { authorizationId: AGENT },
117
+ env: { ...baseEnv(srv), TENANT_ID: "tenant-test", LOCAL_AGENT_SIGNATURE_MODE: "off", LOCAL_AGENT_PROTOCOL_V2: "true" },
118
+ until: async () => srv.legacy.some((f) => f.type === "register"),
119
+ });
120
+ assert.equal(srv.cp.rawIn.length, 0);
121
+ } finally { await srv.stop(); }
122
+ });
123
+
124
+ test("sanity: waitFor is exported", () => assert.equal(typeof waitFor, "function"));
@@ -0,0 +1,19 @@
1
+ # Graphify validation extras (T4-06, round 2, 2026-10-28)
2
+
3
+ Evidence scripts used for `10 Library/Plans/Fecho Dados 2026-10-28 T4.md`. They sit beside the harness and read its
4
+ output; nothing here is part of the agent runtime. Layout expected under `$GV_T4_ROOT`:
5
+ `harness/` (a `git archive` of this repo), `out/run1/` (a `run.sh --out` directory: `venv/`, `work/`, `results/`),
6
+ `bin/rg` (ripgrep wrapper), `g10/home/` (empty HOME), `pub/` (shallow clones of public Go/Terraform repos).
7
+
8
+ | Script | What it measures |
9
+ |---|---|
10
+ | `g10.py` | cold run, network denied, determinism and per-language file coverage for extra repos (Python, TS, Go, Terraform, SQL) |
11
+ | `truth2.py` | `affected` recall against a code-only ground truth (comment, import and test lines removed), class level and class+methods |
12
+ | `bymethod.py` | `affected` by class versus by method, against `rg -w` as the harness does |
13
+ | `hybrid.py` | recall@8 of graph, lexical, reciprocal-rank fusion and lexical-first interleave |
14
+ | `g3aug.py` | G3 Spring fixture with a wrapper-level "call via interface reaches implementations" post-process |
15
+ | `docker/` | G9: image from `requirements-py311.lock` (build context `tools/graphify-validation`), `inner.sh` measures wall time and cgroup peak |
16
+
17
+ Docker run used for the numbers: `docker run --rm --read-only --network none --user 10001:10001 --cpus=2 --memory=4g
18
+ --tmpfs /tmp -e HOME=/data/home -v DATA:/data -v inner.sh:/inner.sh:ro --entrypoint /bin/sh IMAGE /inner.sh REPO RUN`.
19
+ Public repos are only parsed (tree-sitter), never executed. No customer repository is involved.
@@ -0,0 +1,35 @@
1
+ import os
2
+ import sys, json, pathlib, subprocess, os
3
+ SP=pathlib.Path(os.environ['GV_T4_ROOT'])
4
+ sys.path.insert(0,str(SP/'harness/tools/graphify-validation/lib'))
5
+ os.environ['RG']=str(SP/'bin/rg')
6
+ import affected_vs_rg as A, gv_common as gc, graph_metrics as gm
7
+ S=json.load(open(SP/'harness/tools/graphify-validation/config/symbols.json'))
8
+ env=gc.clean_env(SP/'g10/home',str(SP/'out/run1/venv/bin')); G=str(SP/'out/run1/venv/bin/graphify')
9
+ out={}
10
+ for repo in ['vh-api-gateway','visionhammer-agents','deepdebug-local-agent']:
11
+ wd=SP/'out/run1/work'/repo; g=gm.load_graph(wd)
12
+ syms=S[repo] if isinstance(S,dict) else S
13
+ rows=[]
14
+ for sym in syms:
15
+ cls=[n for n in g['nodes'] if n.get('label')==sym and n.get('source_file') and not n['label']==n['source_file'].rsplit('/',1)[-1]]
16
+ if not cls: rows.append({'symbol':sym,'status':'nodef'}); continue
17
+ defs={n['source_file'] for n in cls}
18
+ ids=[n['id'] for n in cls]
19
+ for n in g['nodes']:
20
+ if n.get('source_file') in defs and n.get('label','').startswith('.') and n['label'].endswith('()'):
21
+ ids.append(n['id'])
22
+ files=set()
23
+ for i in ids:
24
+ r=subprocess.run([G,'affected',i,'--depth','1'],cwd=str(wd),env=env,capture_output=True,text=True,timeout=300)
25
+ files|=A.parse_affected(r.stdout)
26
+ files-=defs
27
+ rgf=gc.files_with_word(wd,sym,include_tests=True)-defs
28
+ m=lambda s:{f for f in s if not gc.is_test_path(f)}
29
+ gf,rf=m(files),m(rgf)
30
+ rows.append({'symbol':sym,'methods':len(ids)-len(cls),'graph':len(gf),'rg':len(rf),'recall':round(100*len(gf&rf)/len(rf),1) if rf else None,'precision':round(100*len(gf&rf)/len(gf),1) if gf else None})
31
+ rr=[r for r in rows if r.get('recall') is not None]
32
+ tot_rg=sum(r['rg'] for r in rr); tot_both=sum(round(r['recall']*r['rg']/100) for r in rr)
33
+ out[repo]={'rows':rows,'agg_recall_pct':round(100*tot_both/tot_rg,1) if tot_rg else None}
34
+ print(repo,out[repo]['agg_recall_pct'],[(r['symbol'],r.get('methods'),r.get('recall')) for r in rows])
35
+ json.dump(out,open(SP/'g10/bymethod.json','w'),indent=1)
@@ -0,0 +1,16 @@
1
+ # T4-06 G9 test image: mirrors the python slice of the LA Dockerfile.full (node:20-slim = Debian 12, python3 3.11) + packaging plan 2.2
2
+ FROM node:20-slim AS base
3
+ RUN apt-get update && apt-get install -y --no-install-recommends git python3 python3-pip \
4
+ && rm -rf /var/lib/apt/lists/*
5
+
6
+ FROM base AS graphify
7
+ RUN apt-get update && apt-get install -y --no-install-recommends python3-venv ripgrep \
8
+ && rm -rf /var/lib/apt/lists/*
9
+ COPY requirements-py311.lock /tmp/graphify.lock
10
+ RUN python3 -m venv /opt/vh-graphify \
11
+ && /opt/vh-graphify/bin/pip install --no-cache-dir --disable-pip-version-check \
12
+ --require-hashes --no-deps --only-binary=:all: -r /tmp/graphify.lock \
13
+ && /opt/vh-graphify/bin/python -m compileall -q /opt/vh-graphify/lib \
14
+ && rm -f /opt/vh-graphify/bin/graphify-mcp /tmp/graphify.lock
15
+ RUN useradd -u 10001 -m -d /nonexistent-home vhga 2>/dev/null || true
16
+ ENV PATH=/opt/vh-graphify/bin:$PATH PYTHONDONTWRITEBYTECODE=1 PYTHONHASHSEED=0
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+ # runs inside the container: $1 = repo dir under /data/ws
3
+ S=$(date +%s.%N)
4
+ graphify update /data/ws/$1 > /data/log-$1-$2.txt 2>&1; RC=$?
5
+ E=$(date +%s.%N)
6
+ PEAK=$(cat /sys/fs/cgroup/memory.peak 2>/dev/null)
7
+ echo "repo=$1 run=$2 rc=$RC wall=$(echo "$E $S" | awk '{printf "%.1f",$1-$2}')s cgroup_peak_mb=$(echo $PEAK | awk '{printf "%.0f",$1/1048576}')"
@@ -0,0 +1,59 @@
1
+ import os
2
+ import sys, os, json, subprocess, shutil, pathlib, collections
3
+ LIB=os.path.join(os.environ['GV_T4_ROOT'],'harness/tools/graphify-validation/lib')
4
+ sys.path.insert(0, LIB)
5
+ import run_graphify as rgf, graph_metrics as gm, gv_common as gc
6
+ SP=pathlib.Path(os.environ['GV_T4_ROOT'])
7
+ VENV=SP/'out/run1/venv'; BIN=str(VENV/'bin'); G=str(VENV/'bin/graphify')
8
+ W=SP/'g10/work'; W.mkdir(exist_ok=True)
9
+ HOME=SP/'g10/home'; HOME.mkdir(exist_ok=True)
10
+ BASE='/Users/eduardooliveira/Documents/visionhammer projects/'
11
+ targets=[
12
+ ('vh-litellm', BASE+'vh-litellm','origin/main'),
13
+ ('visionhammer-pentest', BASE+'../x','') ,
14
+ ('vision-heart-glow','%svision-heart-glow-3478cd8d'%BASE,'origin/main'),
15
+ ('go-chi-chi', str(SP/'pub/chi'),'HEAD'),
16
+ ('spf13-cobra', str(SP/'pub/cobra'),'HEAD'),
17
+ ('tf-aws-vpc', str(SP/'pub/terraform-aws-vpc'),'HEAD'),
18
+ ('tf-provider-random', str(SP/'pub/terraform-provider-random'),'HEAD'),
19
+ ]
20
+ targets=[t for t in targets if t[2]]
21
+ targets.append(('visionhammer-pentest', str(pathlib.Path(os.environ.get('GV_PENTEST_CLONE','/nonexistent'))),'HEAD'))
22
+ wrap,mech=rgf.network_wrapper()
23
+ ok,why=rgf.verify_block(wrap,sys.executable)
24
+ print('network block',mech,ok,why)
25
+ EXTS={'.java':'java','.ts':'ts','.tsx':'ts','.js':'js','.jsx':'js','.mjs':'js','.py':'python','.go':'go','.tf':'terraform','.sql':'sql'}
26
+ res={}
27
+ for name,repo,ref in targets:
28
+ d=W/name
29
+ if d.exists(): shutil.rmtree(d)
30
+ d.mkdir(parents=True)
31
+ a=subprocess.Popen(['git','-C',repo,'archive',ref],stdout=subprocess.PIPE)
32
+ subprocess.run(['tar','-x','-C',str(d)],stdin=a.stdout,check=True); a.wait()
33
+ shutil.rmtree(d/'graphify-out',ignore_errors=True)
34
+ env=gc.clean_env(HOME,BIN)
35
+ audit=SP/('g10/audit-%s.tsv'%name)
36
+ r1=rgf.measure([G,'update','.'],d,env,wrapper=wrap,audit_file=audit)
37
+ g=gm.load_graph(d)
38
+ sig1=gm.signature(g)
39
+ # second cold
40
+ shutil.rmtree(d/'graphify-out')
41
+ r2=rgf.measure([G,'update','.'],d,env)
42
+ sig2=gm.signature(gm.load_graph(d))
43
+ # per-language coverage (own: includes sql), files with >=1 symbol
44
+ with_sym,_=gm.file_node_labels(g)
45
+ cnt=collections.defaultdict(lambda:[0,0])
46
+ for p in d.rglob('*'):
47
+ if p.is_file() and p.suffix in EXTS and 'node_modules' not in p.parts and 'graphify-out' not in p.parts and '.git' not in p.parts:
48
+ rel=str(p.relative_to(d)); lang=EXTS[p.suffix]
49
+ if gc.is_test_path(rel) and lang!='go': pass
50
+ cnt[lang][0]+=1
51
+ if rel in with_sym: cnt[lang][1]+=1
52
+ cov={k:{'files':v[0],'with_symbols':v[1],'pct':round(100*v[1]/v[0],1) if v[0] else None} for k,v in cnt.items()}
53
+ g2=gm.g2_edges(g)
54
+ rel=collections.Counter(e.get('relation') for e in gm.edges_of(g))
55
+ res[name]={'ref':ref,'cold':{k:r1[k] for k in ('exit_code','wall_s','peak_tree_rss_mb','max_child_rss_mb')},
56
+ 'network_events':r1.get('audit'),'deterministic':sig1==sig2,'signature':sig1,'coverage_by_lang':cov,
57
+ 'g1_harness':gm.g1_extraction(g,d),'inferred_pct':g2['inferred_pct'],'inferred_by_relation':g2['inferred_by_relation'],'relations_top':dict(rel.most_common(8))}
58
+ print(name, json.dumps({k:res[name][k] for k in ('cold','deterministic','coverage_by_lang','inferred_pct')}))
59
+ json.dump(res,open(SP/'g10/g10.json','w'),indent=1)
@@ -0,0 +1,49 @@
1
+ import os
2
+ import sys, json, pathlib, os, copy, collections
3
+ SP=pathlib.Path(os.environ['GV_T4_ROOT'])
4
+ sys.path.insert(0,str(SP/'harness/tools/graphify-validation/lib'))
5
+ import spring_fixture as sf, graph_metrics as gm
6
+ wd=SP/'out/run1/work/spring-fixture'
7
+ g=gm.load_graph(wd)
8
+ exp=json.load(open(SP/'harness/tools/graphify-validation/fixtures/spring/expected.json'))
9
+ base=sf.check(g,exp)
10
+ print('baseline',{k:(v['found'],v['expected']) for k,v in base['groups'].items()})
11
+ def augment(g):
12
+ g=copy.deepcopy(g); es=gm.edges_of(g)
13
+ byid={n['id']:n for n in g['nodes']}
14
+ # class of a method node = node in same file with non-method label (first)
15
+ file_cls={}
16
+ for n in g['nodes']:
17
+ sfile=n.get('source_file'); lab=n.get('label','')
18
+ if sfile and not lab.startswith('.') and lab!=os.path.basename(sfile): file_cls.setdefault(sfile,n['id'])
19
+ impls=collections.defaultdict(list) # interface class id -> impl class ids
20
+ for e in es:
21
+ if e.get('relation') in ('implements','inherits','extends'): impls[e['target']].append(e['source'])
22
+ methods=collections.defaultdict(dict) # class id -> {label: id}
23
+ for n in g['nodes']:
24
+ lab=n.get('label','')
25
+ if lab.startswith('.') and n.get('source_file') in file_cls: methods[file_cls[n['source_file']]][lab]=n['id']
26
+ new=[]
27
+ for e in es:
28
+ if e.get('relation')!='calls': continue
29
+ t=byid.get(e['target']);
30
+ if not t or t.get('source_file') not in file_cls: continue
31
+ iface=file_cls[t['source_file']]
32
+ for c in impls.get(iface,[]):
33
+ m=methods[c].get(t['label'])
34
+ if m: new.append({**e,'target':m,'relation':'calls','confidence':'INFERRED','note':'via-interface (wrapper)'})
35
+ # field-type edges (references) + implements: class A references interface I => A reaches impl classes (depth)
36
+ for e in es:
37
+ if e.get('relation')=='references' and e['target'] in impls:
38
+ for c in impls[e['target']]: new.append({**e,'target':c,'relation':'references','confidence':'INFERRED'})
39
+ g['links']=es+new if 'links' in g else g.get('edges',[])+new
40
+ if 'edges' in g and 'links' not in g: g['edges']=es+new
41
+ return g,len(new)
42
+ g2,n=augment(g)
43
+ aug=sf.check(g2,exp)
44
+ print('augmented (+%d synthetic edges)'%n,{k:(v['found'],v['expected']) for k,v in aug['groups'].items()})
45
+ print('interface_impl pct',base['groups']['interface_impl']['pct'],'->',aug['groups']['interface_impl']['pct'])
46
+ for k,v in aug['groups'].items():
47
+ for it in v['items']:
48
+ if not it['found']: print(' still failing',k,it['id'],it['detail'])
49
+ json.dump({'baseline':{k:v['pct'] for k,v in base['groups'].items()},'augmented':{k:v['pct'] for k,v in aug['groups'].items()},'synthetic_edges':n},open(SP/'g10/g3aug.json','w'))
@@ -0,0 +1,34 @@
1
+ import os
2
+ import sys, json, pathlib
3
+ SP=pathlib.Path(os.environ['GV_T4_ROOT'])
4
+ sys.path.insert(0,str(SP/'harness/tools/graphify-validation/lib'))
5
+ import recall as R, gv_common as gc
6
+ import os
7
+ os.environ['RG']=str(SP/'bin/rg')
8
+ Q=json.load(open(SP/'harness/tools/graphify-validation/config/questions.json'))
9
+ env=gc.clean_env(SP/'g10/home',str(SP/'out/run1/venv/bin'))
10
+ G=str(SP/'out/run1/venv/bin/graphify')
11
+ def rrf(*ranks,k=60):
12
+ s={}
13
+ for r in ranks:
14
+ for i,f in enumerate(R.clean(r)): s[f]=s.get(f,0)+1/(k+i+1)
15
+ return [f for f,_ in sorted(s.items(),key=lambda kv:-kv[1])]
16
+ def interleave(a,b):
17
+ out=[]
18
+ for x,y in zip(R.clean(a),R.clean(b)):
19
+ for z in (y,x):
20
+ if z not in out: out.append(z)
21
+ return out + [f for f in R.clean(a)+R.clean(b) if f not in out]
22
+ res={}
23
+ for repo,wd in [('vh-api-gateway',SP/'out/run1/work/vh-api-gateway'),('visionhammer-agents',SP/'out/run1/work/visionhammer-agents'),('deepdebug-local-agent',SP/'out/run1/work/deepdebug-local-agent')]:
24
+ rows=[]
25
+ for q in Q[repo]:
26
+ g,_=R.rank_graph(G,wd,q['question'],env)
27
+ l=R.rank_lexical(wd,R.auto_keywords(q['question']),whole_word=False,ignore_case=True)
28
+ rows.append({'kind':q['kind'],'graph':R.recall_at(g,q['truth'])[0],'lex':R.recall_at(l,q['truth'])[0],
29
+ 'rrf':R.recall_at(rrf(g,l),q['truth'])[0],'inter':R.recall_at(interleave(l,g),q['truth'])[0]})
30
+ def avg(sel,k): return round(100*sum(r[k] for r in sel)/len(sel),1) if sel else None
31
+ res[repo]={kind:{k:avg([r for r in rows if kind=='all' or r['kind']==kind],k) for k in ('graph','lex','rrf','inter')}|{'n':len([r for r in rows if kind=='all' or r['kind']==kind])} for kind in ('all','symbolic','conceptual')}
32
+ print(repo,json.dumps(res[repo]))
33
+ tot=[];
34
+ json.dump(res,open(SP/'g10/hybrid.json','w'),indent=1)
@@ -0,0 +1,44 @@
1
+ import os
2
+ import sys, json, pathlib, subprocess, os, re
3
+ SP=pathlib.Path(os.environ['GV_T4_ROOT'])
4
+ sys.path.insert(0,str(SP/'harness/tools/graphify-validation/lib'))
5
+ os.environ['RG']=str(SP/'bin/rg')
6
+ import affected_vs_rg as A, gv_common as gc, graph_metrics as gm
7
+ S=json.load(open(SP/'harness/tools/graphify-validation/config/symbols.json'))
8
+ env=gc.clean_env(SP/'g10/home',str(SP/'out/run1/venv/bin')); G=str(SP/'out/run1/venv/bin/graphify')
9
+ SKIP=re.compile(r'^\s*(\*|//|/\*|#|import\s|from\s|package\s|\*/)')
10
+ def code_files(wd,word):
11
+ r=subprocess.run([str(SP/'bin/rg'),'-w','-n','--no-heading','--glob','*.java','--glob','*.js','--glob','*.mjs','--glob','*.ts','--glob','*.tsx','--glob','*.py','--glob','!**/test/**','--glob','!**/tests/**','--glob','!**/node_modules/**','--glob','!graphify-out/**',word,'.'],cwd=str(wd),capture_output=True,text=True)
12
+ fs=set()
13
+ for ln in r.stdout.splitlines():
14
+ m=re.match(r'\./([^:]+):\d+:(.*)',ln)
15
+ if not m: continue
16
+ f,t=m.groups()
17
+ if SKIP.match(t) or gc.is_test_path(f): continue
18
+ fs.add(f)
19
+ return fs
20
+ out={}
21
+ tot={'class':[0,0,0],'union':[0,0,0]}
22
+ for repo in ['vh-api-gateway','visionhammer-agents','deepdebug-local-agent']:
23
+ wd=SP/'out/run1/work'/repo; g=gm.load_graph(wd); rows=[]
24
+ for sym in S[repo]:
25
+ cls=[n for n in g['nodes'] if n.get('label')==sym and n.get('source_file') and n['label']!=n['source_file'].rsplit('/',1)[-1]]
26
+ if not cls: continue
27
+ defs={n['source_file'] for n in cls}
28
+ ids=[n['id'] for n in cls]
29
+ meth=[n['id'] for n in g['nodes'] if n.get('source_file') in defs and n.get('label','').startswith('.') and n['label'].endswith('()')]
30
+ def aff(idl):
31
+ fs=set()
32
+ for i in idl:
33
+ r=subprocess.run([G,'affected',i,'--depth','1'],cwd=str(wd),env=env,capture_output=True,text=True,timeout=300); fs|=A.parse_affected(r.stdout)
34
+ return {f for f in fs-defs if not gc.is_test_path(f)}
35
+ truth=code_files(wd,sym)-defs
36
+ a_c=aff(ids); a_u=aff(ids+meth)
37
+ row={'symbol':sym,'truth_code_files':len(truth),'class':[len(a_c),len(a_c&truth)],'union':[len(a_u),len(a_u&truth)]}
38
+ rows.append(row)
39
+ for k,idx in (('class','class'),('union','union')):
40
+ tt=sum(r['truth_code_files'] for r in rows); bb=sum(r[idx][1] for r in rows); gg=sum(r[idx][0] for r in rows)
41
+ out.setdefault(repo,{})[k]={'recall_pct':round(100*bb/tt,1) if tt else None,'precision_pct':round(100*bb/gg,1) if gg else None,'truth':tt}
42
+ out[repo]['rows']=rows
43
+ print(repo,json.dumps({k:out[repo][k] for k in ('class','union')}),[(r['symbol'],r['truth_code_files'],r['class'][1],r['union'][1]) for r in rows])
44
+ json.dump(out,open(SP/'g10/truth2.json','w'),indent=1)