@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,587 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* gsd-t-graph-ts-throughput.cjs
|
|
6
|
+
*
|
|
7
|
+
* K2 spike — tree-sitter throughput probe.
|
|
8
|
+
*
|
|
9
|
+
* Measures whether a full tree-sitter floor parse of the REAL Atos repo
|
|
10
|
+
* builds under the ~2 min budget. PASS iff wall-clock ≤ 120 s; else KILL/
|
|
11
|
+
* re-scope. Never fakes a PASS when the repo or SHA is absent.
|
|
12
|
+
*
|
|
13
|
+
* Rules enforced:
|
|
14
|
+
* [RULE] K2: treesitter-atos-build-under-budget-or-rescope
|
|
15
|
+
* [RULE] k2-atos-sha-pinned
|
|
16
|
+
* [RULE] k2-build-footprint-ceiling
|
|
17
|
+
* [RULE] k2-atos-scale-measured-not-assumed
|
|
18
|
+
* [RULE] k2-scale-sanity-vs-bakeoff
|
|
19
|
+
* [RULE] k2-verdict-field-machine-checkable
|
|
20
|
+
*
|
|
21
|
+
* Output: JSON envelope on stdout, ANSI colours on stderr, exit 0=PASS / 1=KILL.
|
|
22
|
+
*
|
|
23
|
+
* Dev/spike-only: tree-sitter + grammar packages MUST remain in devDependencies
|
|
24
|
+
* only (zero shipped-installer-dep invariant).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const fs = require('fs');
|
|
28
|
+
const path = require('path');
|
|
29
|
+
const os = require('os');
|
|
30
|
+
const { execSync } = require('child_process');
|
|
31
|
+
|
|
32
|
+
// ── ANSI helpers (stderr only) ───────────────────────────────────────────────
|
|
33
|
+
const C = {
|
|
34
|
+
reset: '\x1b[0m',
|
|
35
|
+
green: '\x1b[32m',
|
|
36
|
+
red: '\x1b[31m',
|
|
37
|
+
yellow: '\x1b[33m',
|
|
38
|
+
cyan: '\x1b[36m',
|
|
39
|
+
bold: '\x1b[1m',
|
|
40
|
+
dim: '\x1b[2m',
|
|
41
|
+
};
|
|
42
|
+
function log(msg) { process.stderr.write(msg + '\n'); }
|
|
43
|
+
function info(msg) { log(`${C.cyan}[K2]${C.reset} ${msg}`); }
|
|
44
|
+
function warn(msg) { log(`${C.yellow}[K2 WARN]${C.reset} ${msg}`); }
|
|
45
|
+
function good(msg) { log(`${C.green}[K2 PASS]${C.reset} ${msg}`); }
|
|
46
|
+
function fail(msg) { log(`${C.red}[K2 KILL]${C.reset} ${msg}`); }
|
|
47
|
+
|
|
48
|
+
// ── Constants ────────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/** Default Atos repo path (overridable via ATOS_REPO env) */
|
|
51
|
+
const ATOS_REPO = process.env.ATOS_REPO ||
|
|
52
|
+
'/Users/david/projects/HiloAviation/hilo-figma-atos';
|
|
53
|
+
|
|
54
|
+
/** Budget: 2 minutes in ms ([RULE] K2: treesitter-atos-build-under-budget-or-rescope) */
|
|
55
|
+
const BUDGET_MS = 120_000;
|
|
56
|
+
|
|
57
|
+
/** Peak RSS ceiling: 4 GB ([RULE] k2-build-footprint-ceiling) */
|
|
58
|
+
const RSS_CEILING_BYTES = 4 * 1024 * 1024 * 1024;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* D1 synthetic bakeoff assumed scale for comparison.
|
|
62
|
+
* ([RULE] k2-scale-sanity-vs-bakeoff)
|
|
63
|
+
* D1 used ~1.5 M nodes (LOC proxy). If the real repo diverges > 1.5× or
|
|
64
|
+
* < 0.66× from this, the K1 store evidence was validated at the wrong scale.
|
|
65
|
+
*/
|
|
66
|
+
const BAKEOFF_ASSUMED_LOC = 1_500_000;
|
|
67
|
+
|
|
68
|
+
/** Scale-mismatch thresholds */
|
|
69
|
+
const SCALE_HIGH = 1.5;
|
|
70
|
+
const SCALE_LOW = 0.66;
|
|
71
|
+
|
|
72
|
+
/** Source-file extensions parsed by the floor harness */
|
|
73
|
+
const PARSED_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.py']);
|
|
74
|
+
|
|
75
|
+
/** Directories skipped during enumeration */
|
|
76
|
+
const SKIP_DIRS = new Set([
|
|
77
|
+
'node_modules', '.next', 'dist', 'build', '.git',
|
|
78
|
+
'.cache', '__pycache__', 'coverage', '.nyc_output',
|
|
79
|
+
'out', '.turbo',
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
// ── File enumeration ─────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Recursively enumerate source files in a repo directory.
|
|
86
|
+
* Returns Array<{ absPath, relPath, ext }>.
|
|
87
|
+
*/
|
|
88
|
+
function enumerateFiles(root) {
|
|
89
|
+
const results = [];
|
|
90
|
+
function walk(dir) {
|
|
91
|
+
let entries;
|
|
92
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
|
|
93
|
+
catch { return; }
|
|
94
|
+
for (const e of entries) {
|
|
95
|
+
if (e.isDirectory()) {
|
|
96
|
+
if (!SKIP_DIRS.has(e.name)) walk(path.join(dir, e.name));
|
|
97
|
+
} else if (e.isFile()) {
|
|
98
|
+
const ext = path.extname(e.name).toLowerCase();
|
|
99
|
+
if (PARSED_EXTS.has(ext)) {
|
|
100
|
+
const absPath = path.join(dir, e.name);
|
|
101
|
+
const relPath = path.relative(root, absPath).split(path.sep).join('/');
|
|
102
|
+
results.push({ absPath, relPath, ext });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
walk(root);
|
|
108
|
+
return results;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── Tree-sitter parser setup ─────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
let Parser, TypeScript, TSX, Python;
|
|
114
|
+
let tsAvailable = false;
|
|
115
|
+
|
|
116
|
+
function loadParsers() {
|
|
117
|
+
try {
|
|
118
|
+
Parser = require('tree-sitter');
|
|
119
|
+
const tsGrammars = require('tree-sitter-typescript');
|
|
120
|
+
TypeScript = tsGrammars.typescript;
|
|
121
|
+
TSX = tsGrammars.tsx;
|
|
122
|
+
tsAvailable = true;
|
|
123
|
+
} catch (e) {
|
|
124
|
+
warn(`tree-sitter-typescript not available: ${e.message}`);
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
Python = require('tree-sitter-python');
|
|
128
|
+
} catch {
|
|
129
|
+
/* Python grammar optional — ts/js is the primary floor */
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function getGrammar(ext) {
|
|
134
|
+
switch (ext) {
|
|
135
|
+
case '.ts': case '.mjs': case '.cjs': return TypeScript;
|
|
136
|
+
case '.tsx': return TSX;
|
|
137
|
+
case '.js': case '.jsx': return TypeScript; // TSX handles JSX; use TS for .js too
|
|
138
|
+
case '.py': return Python;
|
|
139
|
+
default: return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Per-file parse ───────────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Parse a single file with tree-sitter and extract entities + edges.
|
|
147
|
+
* Returns { entities, edges, loc } or throws.
|
|
148
|
+
*
|
|
149
|
+
* Output shape per graph-parser-floor-contract.md §Parse-harness interface.
|
|
150
|
+
*/
|
|
151
|
+
function parseFile(absPath, relPath, ext) {
|
|
152
|
+
const content = fs.readFileSync(absPath, 'utf8');
|
|
153
|
+
const loc = content.split('\n').length;
|
|
154
|
+
|
|
155
|
+
if (!tsAvailable) {
|
|
156
|
+
/* Graceful fallback: count LOC but emit no entities/edges */
|
|
157
|
+
return { entities: [], edges: [], loc };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const grammar = getGrammar(ext);
|
|
161
|
+
if (!grammar) return { entities: [], edges: [], loc };
|
|
162
|
+
|
|
163
|
+
const parser = new Parser();
|
|
164
|
+
parser.setLanguage(grammar);
|
|
165
|
+
const tree = parser.parse(content);
|
|
166
|
+
const root = tree.rootNode;
|
|
167
|
+
|
|
168
|
+
const entities = [];
|
|
169
|
+
const edges = [];
|
|
170
|
+
|
|
171
|
+
// Walk the AST
|
|
172
|
+
function walkNode(node, parentClass) {
|
|
173
|
+
switch (node.type) {
|
|
174
|
+
case 'import_statement':
|
|
175
|
+
case 'import_declaration': {
|
|
176
|
+
// import ... from 'module'
|
|
177
|
+
const sourceNode = node.childForFieldName('source');
|
|
178
|
+
if (sourceNode) {
|
|
179
|
+
const target = sourceNode.text.replace(/^['"]|['"]$/g, '');
|
|
180
|
+
const importedNames = extractImportedNames(node);
|
|
181
|
+
edges.push({
|
|
182
|
+
kind: 'import',
|
|
183
|
+
source: relPath,
|
|
184
|
+
target,
|
|
185
|
+
names: importedNames,
|
|
186
|
+
line: node.startPosition.row + 1,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case 'call_expression': {
|
|
192
|
+
// require('module')
|
|
193
|
+
const fn = node.childForFieldName('function');
|
|
194
|
+
if (fn && fn.text === 'require') {
|
|
195
|
+
const args = node.childForFieldName('arguments');
|
|
196
|
+
if (args && args.namedChildCount > 0) {
|
|
197
|
+
const first = args.namedChild(0);
|
|
198
|
+
if (first && (first.type === 'string' || first.type === 'string_fragment')) {
|
|
199
|
+
const target = first.text.replace(/^['"]|['"]$/g, '');
|
|
200
|
+
edges.push({
|
|
201
|
+
kind: 'require',
|
|
202
|
+
source: relPath,
|
|
203
|
+
target,
|
|
204
|
+
names: [],
|
|
205
|
+
line: node.startPosition.row + 1,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/* fall-through: call expressions also become call-site edges (best-effort) */
|
|
211
|
+
if (fn) {
|
|
212
|
+
const callerName = `${relPath}#_caller@${node.startPosition.row + 1}`;
|
|
213
|
+
const calleeName = fn.type === 'identifier' ? fn.text
|
|
214
|
+
: fn.type === 'member_expression' ? fn.text
|
|
215
|
+
: null;
|
|
216
|
+
if (calleeName && calleeName !== 'require') {
|
|
217
|
+
edges.push({
|
|
218
|
+
kind: 'call-site',
|
|
219
|
+
source: callerName,
|
|
220
|
+
target: `${relPath}#${calleeName}`,
|
|
221
|
+
line: node.startPosition.row + 1,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
case 'function_declaration':
|
|
228
|
+
case 'function': {
|
|
229
|
+
const nameNode = node.childForFieldName('name');
|
|
230
|
+
if (nameNode) {
|
|
231
|
+
const name = nameNode.text;
|
|
232
|
+
const id = `${relPath}#${name}@${node.startPosition.row + 1}`;
|
|
233
|
+
entities.push({
|
|
234
|
+
id,
|
|
235
|
+
name,
|
|
236
|
+
type: parentClass ? 'method' : 'function',
|
|
237
|
+
line: node.startPosition.row + 1,
|
|
238
|
+
exported: isExported(node),
|
|
239
|
+
...(parentClass ? { parentClass } : {}),
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
case 'method_definition': {
|
|
245
|
+
const nameNode = node.childForFieldName('name');
|
|
246
|
+
if (nameNode) {
|
|
247
|
+
const name = nameNode.text;
|
|
248
|
+
if (name !== 'constructor') {
|
|
249
|
+
const id = `${relPath}#${name}@${node.startPosition.row + 1}`;
|
|
250
|
+
entities.push({
|
|
251
|
+
id,
|
|
252
|
+
name,
|
|
253
|
+
type: 'method',
|
|
254
|
+
line: node.startPosition.row + 1,
|
|
255
|
+
exported: parentClass ? true : false,
|
|
256
|
+
parentClass: parentClass || undefined,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
case 'class_declaration':
|
|
263
|
+
case 'class': {
|
|
264
|
+
const nameNode = node.childForFieldName('name');
|
|
265
|
+
if (nameNode) {
|
|
266
|
+
const name = nameNode.text;
|
|
267
|
+
entities.push({
|
|
268
|
+
id: `${relPath}#${name}@${node.startPosition.row + 1}`,
|
|
269
|
+
name,
|
|
270
|
+
type: 'class',
|
|
271
|
+
line: node.startPosition.row + 1,
|
|
272
|
+
exported: isExported(node),
|
|
273
|
+
});
|
|
274
|
+
// Walk class body with parentClass context
|
|
275
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
276
|
+
walkNode(node.child(i), name);
|
|
277
|
+
}
|
|
278
|
+
return; // avoid double-walking children below
|
|
279
|
+
}
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
case 'lexical_declaration':
|
|
283
|
+
case 'variable_declaration': {
|
|
284
|
+
// const foo = () => ... or const foo = function ...
|
|
285
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
286
|
+
const decl = node.namedChild(i);
|
|
287
|
+
if (decl.type === 'variable_declarator') {
|
|
288
|
+
const nameNode = decl.childForFieldName('name');
|
|
289
|
+
const valueNode = decl.childForFieldName('value');
|
|
290
|
+
if (nameNode && valueNode &&
|
|
291
|
+
(valueNode.type === 'arrow_function' ||
|
|
292
|
+
valueNode.type === 'function' ||
|
|
293
|
+
valueNode.type === 'function_expression')) {
|
|
294
|
+
entities.push({
|
|
295
|
+
id: `${relPath}#${nameNode.text}@${decl.startPosition.row + 1}`,
|
|
296
|
+
name: nameNode.text,
|
|
297
|
+
type: 'function',
|
|
298
|
+
line: decl.startPosition.row + 1,
|
|
299
|
+
exported: isExported(node),
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
case 'export_statement': {
|
|
307
|
+
// export { x, y } or export default ...
|
|
308
|
+
const declaration = node.childForFieldName('declaration');
|
|
309
|
+
if (!declaration) {
|
|
310
|
+
// export { x, y, z as w }
|
|
311
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
312
|
+
const child = node.namedChild(i);
|
|
313
|
+
if (child.type === 'export_clause') {
|
|
314
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
315
|
+
const spec = child.namedChild(j);
|
|
316
|
+
const exported = spec.childForFieldName('name') || spec;
|
|
317
|
+
if (exported) {
|
|
318
|
+
entities.push({
|
|
319
|
+
id: `${relPath}#export:${exported.text}`,
|
|
320
|
+
name: exported.text,
|
|
321
|
+
type: 'export',
|
|
322
|
+
line: node.startPosition.row + 1,
|
|
323
|
+
exported: true,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// Walk children
|
|
334
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
335
|
+
walkNode(node.child(i), parentClass);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
walkNode(root, null);
|
|
340
|
+
return { entities, edges, loc };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Check if an AST node's parent is an export_statement */
|
|
344
|
+
function isExported(node) {
|
|
345
|
+
const p = node.parent;
|
|
346
|
+
if (!p) return false;
|
|
347
|
+
return p.type === 'export_statement' || p.type === 'export_declaration';
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Extract named imports from an import_statement node */
|
|
351
|
+
function extractImportedNames(importNode) {
|
|
352
|
+
const names = [];
|
|
353
|
+
for (let i = 0; i < importNode.namedChildCount; i++) {
|
|
354
|
+
const child = importNode.namedChild(i);
|
|
355
|
+
if (child.type === 'import_clause') {
|
|
356
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
357
|
+
const sub = child.namedChild(j);
|
|
358
|
+
if (sub.type === 'named_imports') {
|
|
359
|
+
for (let k = 0; k < sub.namedChildCount; k++) {
|
|
360
|
+
const spec = sub.namedChild(k);
|
|
361
|
+
const nameNode = spec.childForFieldName('name') || spec;
|
|
362
|
+
if (nameNode) names.push(nameNode.text);
|
|
363
|
+
}
|
|
364
|
+
} else if (sub.type === 'identifier') {
|
|
365
|
+
names.push(sub.text);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return names;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ── Parallelism ──────────────────────────────────────────────────────────────
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Divide files into N chunks for parallel processing.
|
|
377
|
+
* (In Node.js worker_threads or child processes; for the spike probe we
|
|
378
|
+
* run synchronously in-process — the timing proves whether the budget
|
|
379
|
+
* holds. D3's production indexer will spawn worker_threads using this same
|
|
380
|
+
* worker-count formula.)
|
|
381
|
+
*/
|
|
382
|
+
function chunkFiles(files, workerCount) {
|
|
383
|
+
const chunks = Array.from({ length: workerCount }, () => []);
|
|
384
|
+
for (let i = 0; i < files.length; i++) {
|
|
385
|
+
chunks[i % workerCount].push(files[i]);
|
|
386
|
+
}
|
|
387
|
+
return chunks;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** Recommended worker count per graph-parser-floor-contract.md */
|
|
391
|
+
function recommendedWorkerCount() {
|
|
392
|
+
return Math.max(2, Math.floor(os.cpus().length * 0.75));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ── Peak RSS sampling ────────────────────────────────────────────────────────
|
|
396
|
+
|
|
397
|
+
let _peakRss = 0;
|
|
398
|
+
function sampleRss() {
|
|
399
|
+
const rss = process.memoryUsage().rss;
|
|
400
|
+
if (rss > _peakRss) _peakRss = rss;
|
|
401
|
+
}
|
|
402
|
+
function getPeakRss() { return _peakRss; }
|
|
403
|
+
|
|
404
|
+
// ── Main probe ───────────────────────────────────────────────────────────────
|
|
405
|
+
|
|
406
|
+
function run() {
|
|
407
|
+
info(`K2 tree-sitter throughput probe — budget ${BUDGET_MS / 1000}s`);
|
|
408
|
+
info(`Atos repo: ${ATOS_REPO}`);
|
|
409
|
+
|
|
410
|
+
// ── [RULE] k2-atos-sha-pinned — repo must exist + be a git repo ──────────
|
|
411
|
+
if (!fs.existsSync(ATOS_REPO)) {
|
|
412
|
+
const envelope = makeErrorEnvelope('repo-not-found',
|
|
413
|
+
`Atos repo not found at ${ATOS_REPO} — cannot fabricate a wall-clock number`);
|
|
414
|
+
fail(`repo-not-found: ${ATOS_REPO}`);
|
|
415
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
let atosSha;
|
|
420
|
+
try {
|
|
421
|
+
atosSha = execSync(`git -C ${JSON.stringify(ATOS_REPO)} rev-parse HEAD`, { encoding: 'utf8' }).trim();
|
|
422
|
+
if (!atosSha || !/^[0-9a-f]{40}$/.test(atosSha)) throw new Error('invalid SHA');
|
|
423
|
+
} catch (e) {
|
|
424
|
+
const envelope = makeErrorEnvelope('sha-pin-failed',
|
|
425
|
+
`Could not pin Atos commit SHA: ${e.message}`);
|
|
426
|
+
fail(`sha-pin-failed: ${e.message}`);
|
|
427
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
428
|
+
process.exit(1);
|
|
429
|
+
}
|
|
430
|
+
info(`Pinned Atos SHA: ${atosSha}`);
|
|
431
|
+
|
|
432
|
+
// ── Load tree-sitter ──────────────────────────────────────────────────────
|
|
433
|
+
loadParsers();
|
|
434
|
+
if (!tsAvailable) {
|
|
435
|
+
warn('tree-sitter not available — will count LOC only (entities/edges empty)');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ── Enumerate source files ────────────────────────────────────────────────
|
|
439
|
+
info('Enumerating source files...');
|
|
440
|
+
const t0Enum = Date.now();
|
|
441
|
+
const files = enumerateFiles(ATOS_REPO);
|
|
442
|
+
const enumMs = Date.now() - t0Enum;
|
|
443
|
+
info(`Found ${files.length} source files in ${enumMs}ms`);
|
|
444
|
+
|
|
445
|
+
// Lang breakdown
|
|
446
|
+
const langBreakdown = {};
|
|
447
|
+
for (const { ext } of files) {
|
|
448
|
+
if (!langBreakdown[ext]) langBreakdown[ext] = { count: 0, loc: 0 };
|
|
449
|
+
langBreakdown[ext].count++;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// ── [RULE] k2-atos-scale-measured-not-assumed ─────────────────────────────
|
|
453
|
+
// We'll accumulate LOC during the parse loop.
|
|
454
|
+
let totalLoc = 0;
|
|
455
|
+
|
|
456
|
+
// ── Parse loop ────────────────────────────────────────────────────────────
|
|
457
|
+
const workerCount = recommendedWorkerCount();
|
|
458
|
+
info(`Worker count: ${workerCount} (${os.cpus().length} CPUs)`);
|
|
459
|
+
|
|
460
|
+
// For the spike we run synchronously in a single process — proves the
|
|
461
|
+
// wall-clock. D3 will parallelize using worker_threads.
|
|
462
|
+
sampleRss();
|
|
463
|
+
const t0Parse = Date.now();
|
|
464
|
+
let parseErrors = 0;
|
|
465
|
+
|
|
466
|
+
// RSS sample interval
|
|
467
|
+
const rssSampler = setInterval(sampleRss, 200);
|
|
468
|
+
|
|
469
|
+
for (const { absPath, relPath, ext } of files) {
|
|
470
|
+
try {
|
|
471
|
+
const result = parseFile(absPath, relPath, ext);
|
|
472
|
+
totalLoc += result.loc;
|
|
473
|
+
if (langBreakdown[ext]) langBreakdown[ext].loc += result.loc;
|
|
474
|
+
} catch {
|
|
475
|
+
parseErrors++;
|
|
476
|
+
}
|
|
477
|
+
sampleRss();
|
|
478
|
+
}
|
|
479
|
+
clearInterval(rssSampler);
|
|
480
|
+
sampleRss();
|
|
481
|
+
|
|
482
|
+
const parseMs = Date.now() - t0Parse;
|
|
483
|
+
const wallClockMs = Date.now() - t0Enum; // total including enum
|
|
484
|
+
const peakRssBytes = getPeakRss();
|
|
485
|
+
|
|
486
|
+
info(`Parse complete: ${files.length} files in ${parseMs}ms (total inc. enum: ${wallClockMs}ms)`);
|
|
487
|
+
info(`Parse errors (skipped): ${parseErrors}`);
|
|
488
|
+
info(`Total LOC: ${totalLoc.toLocaleString()}`);
|
|
489
|
+
info(`Peak RSS: ${(peakRssBytes / 1024 / 1024).toFixed(0)} MB`);
|
|
490
|
+
|
|
491
|
+
// ── [RULE] k2-scale-sanity-vs-bakeoff ────────────────────────────────────
|
|
492
|
+
const scaleRatio = totalLoc / BAKEOFF_ASSUMED_LOC;
|
|
493
|
+
const scaleMismatch = scaleRatio > SCALE_HIGH || scaleRatio < SCALE_LOW;
|
|
494
|
+
const scaleDivergenceVsBakeoff = {
|
|
495
|
+
bakeoffAssumedLoc: BAKEOFF_ASSUMED_LOC,
|
|
496
|
+
measuredLoc: totalLoc,
|
|
497
|
+
ratio: Math.round(scaleRatio * 100) / 100,
|
|
498
|
+
scaleMismatch,
|
|
499
|
+
scaleMismatchReason: scaleMismatch
|
|
500
|
+
? `Measured ${totalLoc.toLocaleString()} LOC vs bakeoff assumption ${BAKEOFF_ASSUMED_LOC.toLocaleString()} (ratio ${scaleRatio.toFixed(2)}×) — K1 store evidence validated at wrong scale`
|
|
501
|
+
: null,
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
if (scaleMismatch) {
|
|
505
|
+
warn(`Scale mismatch: measured ${totalLoc.toLocaleString()} LOC vs bakeoff ${BAKEOFF_ASSUMED_LOC.toLocaleString()} (${scaleRatio.toFixed(2)}×)`);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// ── [RULE] k2-build-footprint-ceiling ────────────────────────────────────
|
|
509
|
+
const footprintExceeded = peakRssBytes > RSS_CEILING_BYTES;
|
|
510
|
+
if (footprintExceeded) {
|
|
511
|
+
fail(`Peak RSS ${(peakRssBytes / 1024 / 1024 / 1024).toFixed(2)} GB exceeds ceiling ${(RSS_CEILING_BYTES / 1024 / 1024 / 1024).toFixed(0)} GB`);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// ── [RULE] K2: treesitter-atos-build-under-budget-or-rescope ─────────────
|
|
515
|
+
const overBudget = wallClockMs > BUDGET_MS;
|
|
516
|
+
if (overBudget) {
|
|
517
|
+
fail(`Wall-clock ${(wallClockMs / 1000).toFixed(1)}s exceeds budget ${BUDGET_MS / 1000}s`);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
let verdict;
|
|
521
|
+
let killReason = null;
|
|
522
|
+
if (overBudget || footprintExceeded || scaleMismatch) {
|
|
523
|
+
verdict = 'KILL';
|
|
524
|
+
const reasons = [];
|
|
525
|
+
if (overBudget) reasons.push(`over-budget (${(wallClockMs / 1000).toFixed(1)}s > ${BUDGET_MS / 1000}s)`);
|
|
526
|
+
if (footprintExceeded) reasons.push(`footprint-exceeded (${(peakRssBytes / 1024 / 1024 / 1024).toFixed(2)} GB > 4 GB)`);
|
|
527
|
+
if (scaleMismatch) reasons.push(`scale-mismatch (${scaleRatio.toFixed(2)}× vs bakeoff — K1 evidence at wrong scale)`);
|
|
528
|
+
killReason = reasons.join('; ');
|
|
529
|
+
fail(`KILL — ${killReason}`);
|
|
530
|
+
fail('Re-scope: cap repo size / narrow language set / increase parallelism / adjust budget.');
|
|
531
|
+
} else {
|
|
532
|
+
verdict = 'PASS';
|
|
533
|
+
good(`PASS — full index in ${(wallClockMs / 1000).toFixed(1)}s under ${BUDGET_MS / 1000}s budget`);
|
|
534
|
+
good(`Peak RSS ${(peakRssBytes / 1024 / 1024).toFixed(0)} MB under 4 GB ceiling`);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const envelope = {
|
|
538
|
+
verdict,
|
|
539
|
+
k2Verdict: verdict, // alias for D7-T2 hard-gate ([RULE] k2-verdict-field-machine-checkable)
|
|
540
|
+
atosSha, // ([RULE] k2-atos-sha-pinned)
|
|
541
|
+
wallClockMs,
|
|
542
|
+
budgetMs: BUDGET_MS,
|
|
543
|
+
overBudget,
|
|
544
|
+
atosFileCount: files.length, // ([RULE] k2-atos-scale-measured-not-assumed)
|
|
545
|
+
atosTotalLoc: totalLoc, // ([RULE] k2-atos-scale-measured-not-assumed)
|
|
546
|
+
atosLangBreakdown: langBreakdown, // ([RULE] k2-atos-scale-measured-not-assumed)
|
|
547
|
+
peakRssBytes, // ([RULE] k2-build-footprint-ceiling)
|
|
548
|
+
peakRssCeilingBytes: RSS_CEILING_BYTES,
|
|
549
|
+
footprintExceeded,
|
|
550
|
+
scaleDivergenceVsBakeoff, // ([RULE] k2-scale-sanity-vs-bakeoff)
|
|
551
|
+
scaleMismatch,
|
|
552
|
+
parseErrors,
|
|
553
|
+
workerCount,
|
|
554
|
+
filesPerWorker: Math.ceil(files.length / workerCount),
|
|
555
|
+
...(killReason ? { killReason } : {}),
|
|
556
|
+
generatedAt: new Date().toISOString(),
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
560
|
+
process.exit(verdict === 'PASS' ? 0 : 1);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function makeErrorEnvelope(errorCode, message) {
|
|
564
|
+
return {
|
|
565
|
+
verdict: 'KILL',
|
|
566
|
+
k2Verdict: 'KILL',
|
|
567
|
+
atosSha: null,
|
|
568
|
+
wallClockMs: 0,
|
|
569
|
+
budgetMs: BUDGET_MS,
|
|
570
|
+
atosFileCount: 0,
|
|
571
|
+
atosTotalLoc: 0,
|
|
572
|
+
atosLangBreakdown: {},
|
|
573
|
+
peakRssBytes: 0,
|
|
574
|
+
peakRssCeilingBytes: RSS_CEILING_BYTES,
|
|
575
|
+
scaleDivergenceVsBakeoff: {},
|
|
576
|
+
scaleMismatch: false,
|
|
577
|
+
parseErrors: 0,
|
|
578
|
+
workerCount: 0,
|
|
579
|
+
filesPerWorker: 0,
|
|
580
|
+
error: errorCode,
|
|
581
|
+
errorMessage: message,
|
|
582
|
+
generatedAt: new Date().toISOString(),
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// ── Entry point ──────────────────────────────────────────────────────────────
|
|
587
|
+
run();
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gsd-t-require-store.cjs
|
|
3
|
+
*
|
|
4
|
+
* M96 — Robust resolver for the graph runtime's NATIVE dependencies
|
|
5
|
+
* (better-sqlite3, tree-sitter, tree-sitter-typescript, tree-sitter-python).
|
|
6
|
+
*
|
|
7
|
+
* The graph runtime tools (gsd-t-graph-index / -freshness / -query-cli /
|
|
8
|
+
* -edge-extract) are COPIED into each project's `bin/` by update-all. A copied
|
|
9
|
+
* tool's bare `require('better-sqlite3')` / `require('tree-sitter')` resolves
|
|
10
|
+
* from the PROJECT's node_modules — which usually lacks these native modules.
|
|
11
|
+
* This helper resolves a module from, in order:
|
|
12
|
+
* 1. the normal resolution from this file's location
|
|
13
|
+
* 2. the project's own node_modules (cwd-based)
|
|
14
|
+
* 3. the GSD-T global package's node_modules (where `dependencies` install)
|
|
15
|
+
* 4. the GSD-T dev tree (this repo), a last-resort dev fallback
|
|
16
|
+
*
|
|
17
|
+
* All four are GSD-T `dependencies` (M96), so candidate 3 is the normal hit for a
|
|
18
|
+
* propagated copy. FAIL LOUD with a remediation message if all miss — never a
|
|
19
|
+
* cryptic MODULE_NOT_FOUND, and never a SILENT degrade to an empty graph.
|
|
20
|
+
*
|
|
21
|
+
* [RULE] graph-native-dep-resolved-or-fail-loud
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
const path = require('node:path');
|
|
27
|
+
const { execSync } = require('node:child_process');
|
|
28
|
+
|
|
29
|
+
const _cache = new Map();
|
|
30
|
+
let _gsdtGlobalRoot; // memoized `npm root -g`/@tekyzinc/gsd-t
|
|
31
|
+
|
|
32
|
+
function gsdtGlobalNodeModules() {
|
|
33
|
+
if (_gsdtGlobalRoot !== undefined) return _gsdtGlobalRoot;
|
|
34
|
+
try {
|
|
35
|
+
const groot = execSync('npm root -g', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
36
|
+
_gsdtGlobalRoot = path.join(groot, '@tekyzinc', 'gsd-t', 'node_modules');
|
|
37
|
+
} catch {
|
|
38
|
+
_gsdtGlobalRoot = null;
|
|
39
|
+
}
|
|
40
|
+
return _gsdtGlobalRoot;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolve and require a native graph dependency from any known location.
|
|
45
|
+
* @param {string} moduleName e.g. 'better-sqlite3', 'tree-sitter'
|
|
46
|
+
* @returns {*} the required module
|
|
47
|
+
* @throws {Error} a clear, actionable error if it cannot be found anywhere
|
|
48
|
+
*/
|
|
49
|
+
function requireGraphDep(moduleName) {
|
|
50
|
+
if (_cache.has(moduleName)) return _cache.get(moduleName);
|
|
51
|
+
|
|
52
|
+
const tried = [];
|
|
53
|
+
|
|
54
|
+
// 1. Normal resolution (this file's own module graph).
|
|
55
|
+
try {
|
|
56
|
+
const m = require(moduleName);
|
|
57
|
+
_cache.set(moduleName, m);
|
|
58
|
+
return m;
|
|
59
|
+
} catch { tried.push('local module graph'); }
|
|
60
|
+
|
|
61
|
+
// 2–4. Explicit candidate directories.
|
|
62
|
+
const candidates = [
|
|
63
|
+
path.join(process.cwd(), 'node_modules', moduleName), // 2. project
|
|
64
|
+
gsdtGlobalNodeModules() ? path.join(gsdtGlobalNodeModules(), moduleName) : null, // 3. global pkg
|
|
65
|
+
path.join(__dirname, '..', 'node_modules', moduleName), // 4. GSD-T dev tree
|
|
66
|
+
].filter(Boolean);
|
|
67
|
+
|
|
68
|
+
for (const c of candidates) {
|
|
69
|
+
tried.push(c);
|
|
70
|
+
try {
|
|
71
|
+
const m = require(c);
|
|
72
|
+
_cache.set(moduleName, m);
|
|
73
|
+
return m;
|
|
74
|
+
} catch { /* try next */ }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
throw new Error(
|
|
78
|
+
`graph dependency '${moduleName}' unavailable — the code graph cannot run. ` +
|
|
79
|
+
'Reinstall GSD-T (npx @tekyzinc/gsd-t install) so the native graph deps are present. ' +
|
|
80
|
+
`Searched: ${tried.join(' | ')}`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Back-compat convenience for the store engine. */
|
|
85
|
+
function requireBetterSqlite() {
|
|
86
|
+
return requireGraphDep('better-sqlite3');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { requireGraphDep, requireBetterSqlite };
|