@tekyzinc/gsd-t 4.9.14 → 4.10.11
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.
- package/CHANGELOG.md +30 -0
- package/README.md +1 -1
- package/bin/gsd-t-competition-judge.cjs +7 -1
- package/bin/gsd-t-context-brief-kinds/impact.cjs +91 -4
- package/bin/gsd-t-context-brief-kinds/partition.cjs +95 -0
- package/bin/gsd-t-context-brief-kinds/plan.cjs +110 -4
- package/bin/gsd-t-file-disjointness.cjs +319 -7
- package/bin/gsd-t-graph-anti-grep-lint.cjs +515 -0
- package/bin/gsd-t-graph-edge-extract.cjs +622 -0
- package/bin/gsd-t-graph-freshness.cjs +506 -0
- package/bin/gsd-t-graph-index.cjs +540 -0
- package/bin/gsd-t-graph-k1-sqlite-stream.cjs +251 -0
- package/bin/gsd-t-graph-query-cli.cjs +1182 -0
- package/bin/gsd-t-graph-scip-upgrade.cjs +440 -0
- package/bin/gsd-t-graph-store-bakeoff.cjs +889 -0
- package/bin/gsd-t-graph-synthetic-gen.cjs +304 -0
- package/bin/gsd-t-graph-ts-throughput.cjs +587 -0
- package/bin/gsd-t-require-store.cjs +89 -0
- package/bin/gsd-t-scip-reader.cjs +167 -0
- package/bin/gsd-t.js +170 -48
- package/commands/gsd-t-debug.md +10 -0
- package/commands/gsd-t-design-build.md +10 -0
- package/commands/gsd-t-execute.md +15 -0
- package/commands/gsd-t-feature.md +15 -7
- package/commands/gsd-t-gap-analysis.md +17 -7
- package/commands/gsd-t-impact.md +25 -0
- package/commands/gsd-t-integrate.md +25 -0
- package/commands/gsd-t-partition.md +18 -0
- package/commands/gsd-t-plan.md +16 -0
- package/commands/gsd-t-populate.md +16 -5
- package/commands/gsd-t-prd.md +18 -0
- package/commands/gsd-t-project.md +19 -0
- package/commands/gsd-t-promote-debt.md +16 -5
- package/commands/gsd-t-qa.md +20 -8
- package/commands/gsd-t-quick.md +10 -0
- package/commands/gsd-t-scan.md +21 -3
- package/commands/gsd-t-test-sync.md +10 -0
- package/commands/gsd-t-verify.md +25 -0
- package/commands/gsd-t-wave.md +8 -0
- package/package.json +12 -2
- package/templates/workflows/gsd-t-debug.workflow.js +81 -0
- package/templates/workflows/gsd-t-integrate.workflow.js +47 -1
- package/templates/workflows/gsd-t-phase.workflow.js +84 -0
- package/templates/workflows/gsd-t-quick.workflow.js +64 -0
- package/templates/workflows/gsd-t-scan.workflow.js +200 -9
- package/templates/workflows/gsd-t-verify.workflow.js +50 -1
- package/bin/graph-cgc.js +0 -510
- package/bin/graph-indexer.js +0 -147
- package/bin/graph-overlay.js +0 -195
- package/bin/graph-parsers.js +0 -327
- package/bin/graph-query.js +0 -453
- package/bin/graph-store.js +0 -154
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* gsd-t-graph-synthetic-gen — M94 D1-T1
|
|
5
|
+
*
|
|
6
|
+
* Synthetic ~1.5M-node graph generator for the K1 store bake-off spike.
|
|
7
|
+
* Produces a deterministic (seeded) node/edge set at configurable scale
|
|
8
|
+
* (default ~1.5M nodes, Atos scale hypothesis).
|
|
9
|
+
*
|
|
10
|
+
* Node types: FILE + FUNCTION/ENTITY nodes.
|
|
11
|
+
* Edge types: import (file→file) + call (function→function, funcId-keyed).
|
|
12
|
+
*
|
|
13
|
+
* Each node/edge carries the candidate schema fields:
|
|
14
|
+
* node: { id, kind, tier, contentHash, file, name? }
|
|
15
|
+
* edge: { kind, src, dst } — where src/dst are funcId / file paths
|
|
16
|
+
*
|
|
17
|
+
* Envelope emitted to stdout:
|
|
18
|
+
* { ok: true, nodes: [...], edges: [...], seed, scale, nodeCount, edgeCount }
|
|
19
|
+
* or { ok: false, error: "..." } on failure
|
|
20
|
+
*
|
|
21
|
+
* CLI:
|
|
22
|
+
* node bin/gsd-t-graph-synthetic-gen.cjs [--nodes N] [--seed S] [--out FILE]
|
|
23
|
+
* Flags:
|
|
24
|
+
* --nodes N total graph-node count (files + function/entity nodes), default 1500000
|
|
25
|
+
* --seed S integer seed for deterministic output, default 42
|
|
26
|
+
* --out FILE write envelope JSON to FILE instead of stdout
|
|
27
|
+
* --small alias for --nodes 10000 (fast test mode)
|
|
28
|
+
* --tiny alias for --nodes 500 (unit-test mode, returned in-memory)
|
|
29
|
+
*
|
|
30
|
+
* Exit: 0 on success, 1 on error.
|
|
31
|
+
* Zero external deps — Node built-ins only.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const fs = require("node:fs");
|
|
35
|
+
const path = require("node:path");
|
|
36
|
+
const crypto = require("node:crypto");
|
|
37
|
+
|
|
38
|
+
// ─── Seeded PRNG (xorshift32) ─────────────────────────────────────────────
|
|
39
|
+
// Reproducible across Node versions without external deps.
|
|
40
|
+
function makePrng(seed) {
|
|
41
|
+
let s = (seed >>> 0) || 1; // ensure non-zero uint32
|
|
42
|
+
return function () {
|
|
43
|
+
s ^= s << 13;
|
|
44
|
+
s ^= s >>> 17;
|
|
45
|
+
s ^= s << 5;
|
|
46
|
+
s = s >>> 0;
|
|
47
|
+
return s / 0x100000000;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ─── Schema-field generators ──────────────────────────────────────────────
|
|
52
|
+
const TIERS = ["compiler-accurate", "tree-sitter-floor"];
|
|
53
|
+
const LANGS = ["ts", "js", "py", "rs"];
|
|
54
|
+
const EXT_MAP = { ts: ".ts", js: ".js", py: ".py", rs: ".rs" };
|
|
55
|
+
const KINDS_FILE = ["FILE"];
|
|
56
|
+
const KINDS_ENTITY = ["FUNCTION", "CLASS", "EXPORT"];
|
|
57
|
+
|
|
58
|
+
// Stable short hash placeholder (not crypto-strong — content-hash placeholder)
|
|
59
|
+
function syntheticHash(rng) {
|
|
60
|
+
// 8-char hex (deterministic from rng, not filesystem)
|
|
61
|
+
let h = "";
|
|
62
|
+
for (let i = 0; i < 8; i++) {
|
|
63
|
+
h += Math.floor(rng() * 16).toString(16);
|
|
64
|
+
}
|
|
65
|
+
return h;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ─── Graph generation ─────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Generate a synthetic graph at `targetNodeCount` total nodes (FILE + FUNCTION/ENTITY).
|
|
72
|
+
*
|
|
73
|
+
* Atos-scale observation: real JS/TS repos have roughly 3–5 functions per file.
|
|
74
|
+
* We target FILE nodes = ~20% of total, ENTITY nodes = ~80%.
|
|
75
|
+
* Edge densities:
|
|
76
|
+
* import edges: ~3 per file node on average (fan-out import graph)
|
|
77
|
+
* call edges: ~4 per function entity on average (call graph)
|
|
78
|
+
*
|
|
79
|
+
* @param {number} targetNodeCount
|
|
80
|
+
* @param {number} seed
|
|
81
|
+
* @returns {{ nodes: object[], edges: object[], nodeCount: number, edgeCount: number }}
|
|
82
|
+
*/
|
|
83
|
+
function generateGraph(targetNodeCount, seed) {
|
|
84
|
+
const rng = makePrng(seed);
|
|
85
|
+
|
|
86
|
+
// ── Node counts ──────────────────────────────────────────────────────────
|
|
87
|
+
const fileCount = Math.max(1, Math.round(targetNodeCount * 0.2));
|
|
88
|
+
const entityCount = Math.max(1, targetNodeCount - fileCount);
|
|
89
|
+
|
|
90
|
+
const nodes = [];
|
|
91
|
+
const edges = [];
|
|
92
|
+
|
|
93
|
+
// ── FILE nodes ───────────────────────────────────────────────────────────
|
|
94
|
+
// Generate file paths that look like a real repo
|
|
95
|
+
const filePaths = [];
|
|
96
|
+
const dirs = [
|
|
97
|
+
"src", "lib", "bin", "core", "utils", "api", "models", "controllers",
|
|
98
|
+
"services", "handlers", "parsers", "validators", "transforms", "types",
|
|
99
|
+
"helpers", "middleware", "routes", "adapters", "stores", "queries"
|
|
100
|
+
];
|
|
101
|
+
const prefixes = [
|
|
102
|
+
"user", "auth", "index", "main", "core", "base", "common", "shared",
|
|
103
|
+
"graph", "cache", "config", "logger", "client", "server", "worker",
|
|
104
|
+
"loader", "parser", "builder", "factory", "manager"
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < fileCount; i++) {
|
|
108
|
+
const lang = LANGS[Math.floor(rng() * LANGS.length)];
|
|
109
|
+
const ext = EXT_MAP[lang];
|
|
110
|
+
const dir = dirs[Math.floor(rng() * dirs.length)];
|
|
111
|
+
const prefix = prefixes[Math.floor(rng() * prefixes.length)];
|
|
112
|
+
const filePath = `${dir}/${prefix}-${i}${ext}`;
|
|
113
|
+
const contentHash = syntheticHash(rng);
|
|
114
|
+
const tier = TIERS[Math.floor(rng() * TIERS.length)];
|
|
115
|
+
|
|
116
|
+
filePaths.push(filePath);
|
|
117
|
+
nodes.push({
|
|
118
|
+
id: filePath,
|
|
119
|
+
kind: "FILE",
|
|
120
|
+
tier,
|
|
121
|
+
contentHash,
|
|
122
|
+
file: filePath,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── ENTITY nodes (functions / classes / exports) ─────────────────────────
|
|
127
|
+
// Each entity is file-qualified: funcId = "file#name" or "file#name@line"
|
|
128
|
+
// for disambiguation when multiple same-named entities exist per file.
|
|
129
|
+
// Entities are distributed across file nodes.
|
|
130
|
+
const entityNodes = [];
|
|
131
|
+
const funcIds = [];
|
|
132
|
+
|
|
133
|
+
// Distribution: every file gets at least 1 entity; remainder distributed randomly
|
|
134
|
+
const basePerFile = Math.max(1, Math.floor(entityCount / fileCount));
|
|
135
|
+
let entityIdx = 0;
|
|
136
|
+
|
|
137
|
+
for (let fi = 0; fi < filePaths.length && entityIdx < entityCount; fi++) {
|
|
138
|
+
const filePath = filePaths[fi];
|
|
139
|
+
// How many entities for this file?
|
|
140
|
+
const extras = fi === filePaths.length - 1
|
|
141
|
+
? entityCount - entityIdx // last file gets all remaining
|
|
142
|
+
: Math.round(rng() * basePerFile * 1.5);
|
|
143
|
+
const count = Math.min(extras, entityCount - entityIdx);
|
|
144
|
+
|
|
145
|
+
const kindPool = KINDS_ENTITY;
|
|
146
|
+
const funcNamesUsed = new Set();
|
|
147
|
+
|
|
148
|
+
for (let ei = 0; ei < count && entityIdx < entityCount; ei++, entityIdx++) {
|
|
149
|
+
const kind = kindPool[Math.floor(rng() * kindPool.length)];
|
|
150
|
+
const baseName = `fn_${entityIdx}`;
|
|
151
|
+
// Make funcId file-qualified and unique within the file
|
|
152
|
+
const funcName = funcNamesUsed.has(baseName)
|
|
153
|
+
? `${baseName}_${ei}`
|
|
154
|
+
: baseName;
|
|
155
|
+
funcNamesUsed.add(funcName);
|
|
156
|
+
const funcId = `${filePath}#${funcName}`;
|
|
157
|
+
const tier = TIERS[Math.floor(rng() * TIERS.length)];
|
|
158
|
+
const contentHash = syntheticHash(rng);
|
|
159
|
+
|
|
160
|
+
funcIds.push(funcId);
|
|
161
|
+
const node = {
|
|
162
|
+
id: funcId,
|
|
163
|
+
kind,
|
|
164
|
+
tier,
|
|
165
|
+
contentHash,
|
|
166
|
+
file: filePath,
|
|
167
|
+
name: funcName,
|
|
168
|
+
funcId,
|
|
169
|
+
};
|
|
170
|
+
entityNodes.push(node);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// NOTE: `nodes.push(...entityNodes)` overflows the call stack at ~870K-scale
|
|
175
|
+
// (array spread becomes function args, which has a ~65–120K limit). Use a
|
|
176
|
+
// for-loop append so the generator scales to the real Atos node count.
|
|
177
|
+
for (let i = 0; i < entityNodes.length; i++) nodes.push(entityNodes[i]);
|
|
178
|
+
|
|
179
|
+
// ── IMPORT edges (file→file) ─────────────────────────────────────────────
|
|
180
|
+
// Avg ~3 import edges per file node. Fan-out from each file to random other files.
|
|
181
|
+
// No self-loops.
|
|
182
|
+
const avgImports = 3;
|
|
183
|
+
for (let fi = 0; fi < filePaths.length; fi++) {
|
|
184
|
+
const src = filePaths[fi];
|
|
185
|
+
const count = Math.max(0, Math.round(rng() * avgImports * 2));
|
|
186
|
+
for (let k = 0; k < count; k++) {
|
|
187
|
+
let dstIdx = Math.floor(rng() * filePaths.length);
|
|
188
|
+
if (dstIdx === fi) dstIdx = (fi + 1) % filePaths.length;
|
|
189
|
+
edges.push({ kind: "IMPORT", src, dst: filePaths[dstIdx] });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ── CALL edges (funcId→funcId) ───────────────────────────────────────────
|
|
194
|
+
// Avg ~4 call edges per entity node.
|
|
195
|
+
if (funcIds.length > 1) {
|
|
196
|
+
const avgCalls = 4;
|
|
197
|
+
for (let ei = 0; ei < funcIds.length; ei++) {
|
|
198
|
+
const caller = funcIds[ei];
|
|
199
|
+
const count = Math.max(0, Math.round(rng() * avgCalls * 2));
|
|
200
|
+
for (let k = 0; k < count; k++) {
|
|
201
|
+
let dstIdx = Math.floor(rng() * funcIds.length);
|
|
202
|
+
if (dstIdx === ei) dstIdx = (ei + 1) % funcIds.length;
|
|
203
|
+
edges.push({ kind: "CALL", src: caller, dst: funcIds[dstIdx] });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
nodes,
|
|
210
|
+
edges,
|
|
211
|
+
nodeCount: nodes.length,
|
|
212
|
+
edgeCount: edges.length,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ─── CLI entry point ──────────────────────────────────────────────────────
|
|
217
|
+
function parseArgs(argv) {
|
|
218
|
+
const args = argv.slice(2);
|
|
219
|
+
let nodes = 1_500_000;
|
|
220
|
+
let seed = 42;
|
|
221
|
+
let out = null;
|
|
222
|
+
let i = 0;
|
|
223
|
+
while (i < args.length) {
|
|
224
|
+
const a = args[i];
|
|
225
|
+
if (a === "--nodes" && args[i + 1]) {
|
|
226
|
+
nodes = parseInt(args[++i], 10);
|
|
227
|
+
} else if (a === "--seed" && args[i + 1]) {
|
|
228
|
+
seed = parseInt(args[++i], 10);
|
|
229
|
+
} else if (a === "--out" && args[i + 1]) {
|
|
230
|
+
out = args[++i];
|
|
231
|
+
} else if (a === "--small") {
|
|
232
|
+
nodes = 10_000;
|
|
233
|
+
} else if (a === "--tiny") {
|
|
234
|
+
nodes = 500;
|
|
235
|
+
}
|
|
236
|
+
i++;
|
|
237
|
+
}
|
|
238
|
+
return { nodes, seed, out };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Export for programmatic use (tests)
|
|
242
|
+
function generate(opts = {}) {
|
|
243
|
+
const nodes = opts.nodes ?? 500;
|
|
244
|
+
const seed = opts.seed ?? 42;
|
|
245
|
+
const result = generateGraph(nodes, seed);
|
|
246
|
+
return {
|
|
247
|
+
ok: true,
|
|
248
|
+
nodes: result.nodes,
|
|
249
|
+
edges: result.edges,
|
|
250
|
+
seed,
|
|
251
|
+
scale: nodes,
|
|
252
|
+
nodeCount: result.nodeCount,
|
|
253
|
+
edgeCount: result.edgeCount,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
module.exports = { generate, generateGraph, makePrng };
|
|
258
|
+
|
|
259
|
+
// ─── CLI main ─────────────────────────────────────────────────────────────
|
|
260
|
+
if (require.main === module) {
|
|
261
|
+
const { nodes, seed, out } = parseArgs(process.argv);
|
|
262
|
+
|
|
263
|
+
let envelope;
|
|
264
|
+
try {
|
|
265
|
+
if (isNaN(nodes) || nodes < 1) {
|
|
266
|
+
throw new Error(`--nodes must be a positive integer, got: ${nodes}`);
|
|
267
|
+
}
|
|
268
|
+
if (isNaN(seed)) {
|
|
269
|
+
throw new Error(`--seed must be an integer, got: ${seed}`);
|
|
270
|
+
}
|
|
271
|
+
const result = generateGraph(nodes, seed);
|
|
272
|
+
envelope = {
|
|
273
|
+
ok: true,
|
|
274
|
+
nodes: result.nodes,
|
|
275
|
+
edges: result.edges,
|
|
276
|
+
seed,
|
|
277
|
+
scale: nodes,
|
|
278
|
+
nodeCount: result.nodeCount,
|
|
279
|
+
edgeCount: result.edgeCount,
|
|
280
|
+
};
|
|
281
|
+
} catch (err) {
|
|
282
|
+
envelope = { ok: false, error: err.message };
|
|
283
|
+
process.stdout.write(JSON.stringify(envelope) + "\n");
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const json = JSON.stringify(envelope, null, 2);
|
|
288
|
+
if (out) {
|
|
289
|
+
fs.writeFileSync(out, json, "utf8");
|
|
290
|
+
// Summary to stdout when writing to file
|
|
291
|
+
const summary = {
|
|
292
|
+
ok: true,
|
|
293
|
+
seed: envelope.seed,
|
|
294
|
+
scale: envelope.scale,
|
|
295
|
+
nodeCount: envelope.nodeCount,
|
|
296
|
+
edgeCount: envelope.edgeCount,
|
|
297
|
+
writtenTo: out,
|
|
298
|
+
};
|
|
299
|
+
process.stdout.write(JSON.stringify(summary, null, 2) + "\n");
|
|
300
|
+
} else {
|
|
301
|
+
process.stdout.write(json + "\n");
|
|
302
|
+
}
|
|
303
|
+
process.exit(0);
|
|
304
|
+
}
|