gitnexus 1.6.7-rc.1 → 1.6.7-rc.3

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 (64) hide show
  1. package/dist/_shared/graph/types.d.ts +18 -2
  2. package/dist/_shared/graph/types.d.ts.map +1 -1
  3. package/dist/_shared/lbug/schema-constants.d.ts +2 -2
  4. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  5. package/dist/_shared/lbug/schema-constants.js +10 -0
  6. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  7. package/dist/cli/analyze.js +7 -5
  8. package/dist/cli/optional-grammars.d.ts +18 -4
  9. package/dist/cli/optional-grammars.js +51 -8
  10. package/dist/core/ingestion/languages/dart/query.js +11 -3
  11. package/dist/core/ingestion/languages/kotlin/query.js +11 -3
  12. package/dist/core/ingestion/languages/swift/query.js +11 -3
  13. package/dist/core/ingestion/model/registration-table.js +3 -0
  14. package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
  15. package/dist/core/ingestion/pipeline-phases/index.js +1 -0
  16. package/dist/core/ingestion/pipeline-phases/parse-impl.js +11 -3
  17. package/dist/core/ingestion/pipeline-phases/registry.d.ts +56 -0
  18. package/dist/core/ingestion/pipeline-phases/registry.js +48 -0
  19. package/dist/core/ingestion/pipeline.d.ts +21 -0
  20. package/dist/core/ingestion/pipeline.js +28 -20
  21. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +11 -0
  22. package/dist/core/ingestion/taint/source-sink-config.d.ts +36 -0
  23. package/dist/core/ingestion/taint/source-sink-config.js +16 -0
  24. package/dist/core/ingestion/taint/source-sink-registry.d.ts +27 -0
  25. package/dist/core/ingestion/taint/source-sink-registry.js +35 -0
  26. package/dist/core/ingestion/workers/parse-worker.js +13 -0
  27. package/dist/core/lbug/csv-generator.js +14 -0
  28. package/dist/core/lbug/lbug-adapter.js +12 -0
  29. package/dist/core/lbug/schema.d.ts +2 -1
  30. package/dist/core/lbug/schema.js +22 -0
  31. package/dist/core/tree-sitter/parser-loader.d.ts +8 -0
  32. package/dist/core/tree-sitter/parser-loader.js +70 -0
  33. package/dist/server/api.d.ts +1 -0
  34. package/dist/server/api.js +17 -2
  35. package/package.json +1 -1
  36. package/scripts/spikes/s1-reaching-def-index-bench.ts +151 -0
  37. package/scripts/spikes/s2-postdom-prototype.ts +162 -0
  38. package/web/assets/{agent-EkX7webX.js → agent-CKPMqImC.js} +1 -1
  39. package/web/assets/{architectureDiagram-UL44E2DR-BoczH8cv.js → architectureDiagram-UL44E2DR-D1EXI0zA.js} +1 -1
  40. package/web/assets/{chunk-LCXTWHL2-By5HB3b-.js → chunk-LCXTWHL2-Dfmux4m1.js} +1 -1
  41. package/web/assets/{chunk-RG4AUYOV-BQE_DkpL.js → chunk-RG4AUYOV-CAkzcoRj.js} +1 -1
  42. package/web/assets/{classDiagram-KGZ6W3CR-Dj_02GUH.js → classDiagram-KGZ6W3CR-Bv93af_b.js} +1 -1
  43. package/web/assets/{classDiagram-v2-72OJOZXJ-D-NArMpf.js → classDiagram-v2-72OJOZXJ-CLfEqHUa.js} +1 -1
  44. package/web/assets/{diagram-3NCE3AQN-VXtXDO95.js → diagram-3NCE3AQN-BqAtKUpW.js} +1 -1
  45. package/web/assets/{diagram-GF46GFSD-B1qab2Ah.js → diagram-GF46GFSD-CaBG6n6o.js} +1 -1
  46. package/web/assets/{diagram-QXG6HAR7-BSCHGzzT.js → diagram-QXG6HAR7-CZ-O3rcV.js} +1 -1
  47. package/web/assets/{diagram-WEQXMOUZ-DHtknmNT.js → diagram-WEQXMOUZ-P4lSL4GH.js} +1 -1
  48. package/web/assets/{erDiagram-L5TCEMPS-Db-IYTmm.js → erDiagram-L5TCEMPS-QN2eEP1e.js} +1 -1
  49. package/web/assets/{flowDiagram-H6V6AXG4-BHDoyVoW.js → flowDiagram-H6V6AXG4-CXbXImlN.js} +1 -1
  50. package/web/assets/{index-BR0IhgbR.js → index-CG6q8eTs.js} +6 -6
  51. package/web/assets/{infoDiagram-3YFTVSEB-6h8wbWEe.js → infoDiagram-3YFTVSEB-DmLICZx1.js} +1 -1
  52. package/web/assets/{ishikawaDiagram-BNXS4ZKH-Dx2OkPk1.js → ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js} +1 -1
  53. package/web/assets/{kanban-definition-75IXJCU3-CIIEZfqV.js → kanban-definition-75IXJCU3-DgDi9oJT.js} +1 -1
  54. package/web/assets/{mindmap-definition-2TDM6QVE-DUAqydRq.js → mindmap-definition-2TDM6QVE-CwR5sBB-.js} +1 -1
  55. package/web/assets/{pieDiagram-CU6KROY3-BntYuYZa.js → pieDiagram-CU6KROY3-By8g6f6B.js} +1 -1
  56. package/web/assets/{requirementDiagram-JXO7QTGE-QCD6I29n.js → requirementDiagram-JXO7QTGE-7oDcJ1_J.js} +1 -1
  57. package/web/assets/{sequenceDiagram-VS2MUI6T-DScFwLz2.js → sequenceDiagram-VS2MUI6T-FySeKCUy.js} +1 -1
  58. package/web/assets/{stateDiagram-7D4R322I-AkRAm6Hl.js → stateDiagram-7D4R322I-CB2nABwH.js} +1 -1
  59. package/web/assets/{stateDiagram-v2-36443NZ5-BCK1vigR.js → stateDiagram-v2-36443NZ5-COBGd2RL.js} +1 -1
  60. package/web/assets/{timeline-definition-O6YCAMPW-BvpIVXjX.js → timeline-definition-O6YCAMPW-Ds2CnVZK.js} +1 -1
  61. package/web/assets/{vennDiagram-MWXL3ELB-BYGWk6dK.js → vennDiagram-MWXL3ELB-DUIEwXWp.js} +1 -1
  62. package/web/assets/{wardleyDiagram-CUQ6CDDI-Dk1z7H2T.js → wardleyDiagram-CUQ6CDDI-DEiFPQih.js} +1 -1
  63. package/web/assets/{xychartDiagram-N2JHSOCM-2N6NCSWN.js → xychartDiagram-N2JHSOCM-BUbayhST.js} +1 -1
  64. package/web/index.html +1 -1
@@ -27,6 +27,7 @@ import { getPhaseOutput } from '../../pipeline-phases/types.js';
27
27
  import { getLanguageFromFilename } from '../../../../_shared/index.js';
28
28
  import { readFileContents } from '../../filesystem-walker.js';
29
29
  import { runScopeResolution } from './run.js';
30
+ import { isLanguageAvailable } from '../../../tree-sitter/parser-loader.js';
30
31
  import { buildGraphNodeLookup } from '../graph-bridge/node-lookup.js';
31
32
  import { SCOPE_RESOLVERS } from './registry.js';
32
33
  import { isDev, isSemanticModelValidatorEnabled } from '../../utils/env.js';
@@ -120,6 +121,16 @@ export const scopeResolutionPhase = {
120
121
  const fileLang = getLanguageFromFilename(f.path);
121
122
  if (fileLang === null)
122
123
  continue;
124
+ // Skip files whose grammar isn't available (optional grammars like
125
+ // swift/dart/kotlin on an install where the binding is absent or the
126
+ // user set GITNEXUS_SKIP_OPTIONAL_GRAMMARS). The parse phase already
127
+ // excluded and warned about these (parse-impl.ts); without this guard the
128
+ // file would fall through to the main-thread re-extract in run.ts and
129
+ // throw "Unsupported language" (caught, but noisy, and it needlessly
130
+ // loads the grammar on the main thread). `isLanguageAvailable` is
131
+ // memoized, so this stays O(1) per language. (#2091, #2093)
132
+ if (!isLanguageAvailable(fileLang))
133
+ continue;
123
134
  let bucket = filesByLang.get(fileLang);
124
135
  if (bucket === undefined) {
125
136
  bucket = [];
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Source/sink/sanitizer config model (issue #2080, taint/PDG substrate M0).
3
+ *
4
+ * The per-language taint configuration *shape*. M0 ships only the type and an
5
+ * (empty) registry seam — no analysis consumes it yet. M3 (#2083, intra-proc
6
+ * taint) populates per-language specs and reads them when emitting TAINTED /
7
+ * SANITIZES edges.
8
+ *
9
+ * Kept deliberately minimal: enough for M3 to express "callable X is a
10
+ * source / sink / sanitizer, optionally for argument position N" without M0
11
+ * committing to matcher semantics it cannot yet validate. The shape is
12
+ * expected to grow (e.g. sanitizer escape conditions, return-position taint)
13
+ * when M3 makes contact with real flows; that is a forward-declared-interface
14
+ * design choice, not a finished contract.
15
+ */
16
+ /**
17
+ * Identifies a callable that participates in taint flow. `name` is matched
18
+ * against a resolved callable (simple or qualified name — exact matching
19
+ * semantics are M3's call). `args` optionally narrows to specific 0-based
20
+ * argument positions that carry taint (for a source/sink) or clear it (for a
21
+ * sanitizer); omit to mean "unspecified / all".
22
+ */
23
+ export interface TaintCallableMatcher {
24
+ readonly name: string;
25
+ readonly args?: readonly number[];
26
+ }
27
+ /**
28
+ * The taint configuration for a single language: which callables introduce
29
+ * taint (sources), which are dangerous to reach with tainted input (sinks),
30
+ * and which clear taint (sanitizers).
31
+ */
32
+ export interface SourceSinkSanitizerSpec {
33
+ readonly sources: readonly TaintCallableMatcher[];
34
+ readonly sinks: readonly TaintCallableMatcher[];
35
+ readonly sanitizers: readonly TaintCallableMatcher[];
36
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Source/sink/sanitizer config model (issue #2080, taint/PDG substrate M0).
3
+ *
4
+ * The per-language taint configuration *shape*. M0 ships only the type and an
5
+ * (empty) registry seam — no analysis consumes it yet. M3 (#2083, intra-proc
6
+ * taint) populates per-language specs and reads them when emitting TAINTED /
7
+ * SANITIZES edges.
8
+ *
9
+ * Kept deliberately minimal: enough for M3 to express "callable X is a
10
+ * source / sink / sanitizer, optionally for argument position N" without M0
11
+ * committing to matcher semantics it cannot yet validate. The shape is
12
+ * expected to grow (e.g. sanitizer escape conditions, return-position taint)
13
+ * when M3 makes contact with real flows; that is a forward-declared-interface
14
+ * design choice, not a finished contract.
15
+ */
16
+ export {};
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Per-language source/sink/sanitizer registry seam (issue #2080).
3
+ *
4
+ * A keyed registry of {@link SourceSinkSanitizerSpec} by language id. M0 stands
5
+ * up the empty seam — no language is registered and nothing in the pipeline
6
+ * reads it. M3 (#2083) registers per-language specs and queries this registry
7
+ * when emitting taint edges.
8
+ *
9
+ * The store is module-level (matching the codebase's other per-language
10
+ * registries). {@link clearSourceSinkRegistry} resets it for test isolation.
11
+ */
12
+ import type { SourceSinkSanitizerSpec } from './source-sink-config.js';
13
+ /**
14
+ * Register the taint config for a language. Last-write-wins: re-registering
15
+ * the same `languageId` overwrites the previous spec (so M3 can override a
16
+ * built-in default). Returns nothing.
17
+ */
18
+ export declare function registerSourceSinkConfig(languageId: string, spec: SourceSinkSanitizerSpec): void;
19
+ /**
20
+ * Look up the taint config for a language. Returns `undefined` when no spec is
21
+ * registered (the M0 default for every language) — never throws.
22
+ */
23
+ export declare function getSourceSinkConfig(languageId: string): SourceSinkSanitizerSpec | undefined;
24
+ /** Language ids that currently have a registered spec. Empty in M0. */
25
+ export declare function registeredTaintLanguages(): string[];
26
+ /** Reset the registry. Primarily for test isolation. */
27
+ export declare function clearSourceSinkRegistry(): void;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Per-language source/sink/sanitizer registry seam (issue #2080).
3
+ *
4
+ * A keyed registry of {@link SourceSinkSanitizerSpec} by language id. M0 stands
5
+ * up the empty seam — no language is registered and nothing in the pipeline
6
+ * reads it. M3 (#2083) registers per-language specs and queries this registry
7
+ * when emitting taint edges.
8
+ *
9
+ * The store is module-level (matching the codebase's other per-language
10
+ * registries). {@link clearSourceSinkRegistry} resets it for test isolation.
11
+ */
12
+ const registry = new Map();
13
+ /**
14
+ * Register the taint config for a language. Last-write-wins: re-registering
15
+ * the same `languageId` overwrites the previous spec (so M3 can override a
16
+ * built-in default). Returns nothing.
17
+ */
18
+ export function registerSourceSinkConfig(languageId, spec) {
19
+ registry.set(languageId, spec);
20
+ }
21
+ /**
22
+ * Look up the taint config for a language. Returns `undefined` when no spec is
23
+ * registered (the M0 default for every language) — never throws.
24
+ */
25
+ export function getSourceSinkConfig(languageId) {
26
+ return registry.get(languageId);
27
+ }
28
+ /** Language ids that currently have a registered spec. Empty in M0. */
29
+ export function registeredTaintLanguages() {
30
+ return [...registry.keys()];
31
+ }
32
+ /** Reset the registry. Primarily for test isolation. */
33
+ export function clearSourceSinkRegistry() {
34
+ registry.clear();
35
+ }
@@ -18,6 +18,19 @@ import { getProvider } from '../languages/index.js';
18
18
  import { getTreeSitterBufferSize, getTreeSitterContentByteLength, TREE_SITTER_MAX_BUFFER, } from '../constants.js';
19
19
  import { ARRAY_METHOD_HOC_BLOCKLIST_SET, DEFAULT_EXPORT_IDENTIFIER_BLOCKLIST_SET, deriveDefaultExportHocName, } from '../ts-js-hoc-utils.js';
20
20
  import { parseSourceSafe } from '../../tree-sitter/safe-parse.js';
21
+ // ── Worker grammar loading — enforcement boundary (#2091/#2093, #2101) ───────
22
+ // The worker maintains its own grammar table (the guarded `_require`s below +
23
+ // `languageMap`) and intentionally does NOT consult the runtime
24
+ // `GITNEXUS_SKIP_OPTIONAL_GRAMMARS` opt-out. It does not need to: the MAIN
25
+ // THREAD's `parseableScanned` filter (pipeline-phases/parse-impl.ts, gated on
26
+ // `parser-loader.isLanguageAvailable`, which honors the runtime opt-out and a
27
+ // genuinely-absent binding alike) excludes files of an unavailable/opted-out
28
+ // language BEFORE any chunk is dispatched, so the worker never receives them.
29
+ // That main-thread filter is the single enforcement point. Any future change
30
+ // that dispatches files to the worker WITHOUT first passing them through
31
+ // `isLanguageAvailable` must re-introduce the gate here. (The cleaner end-state
32
+ // — routing this table through `parser-loader.getLanguageGrammar` so there is
33
+ // one loader — is the deferred Tier-1 consolidation.)
21
34
  // tree-sitter-swift is an optionalDependency — may not be installed
22
35
  const _require = createRequire(import.meta.url);
23
36
  let Swift = null;
@@ -232,6 +232,9 @@ export const streamAllCSVsToDisk = async (graph, repoPath, csvDir) => {
232
232
  const routeWriter = new BufferedCSVWriter(path.join(csvDir, 'route.csv'), 'id,name,filePath,responseKeys,errorKeys,middleware');
233
233
  // Tool nodes for MCP tool definitions
234
234
  const toolWriter = new BufferedCSVWriter(path.join(csvDir, 'tool.csv'), 'id,name,filePath,description');
235
+ // BasicBlock nodes — taint/PDG substrate (issue #2080). No `name` column;
236
+ // blocks are identified by id + source span. Emitted by no phase yet.
237
+ const basicBlockWriter = new BufferedCSVWriter(path.join(csvDir, 'basicblock.csv'), 'id,filePath,startLine,endLine,text');
235
238
  // Multi-language node types share the same CSV shape (no isExported column)
236
239
  const multiLangHeader = 'id,name,filePath,startLine,endLine,content,description';
237
240
  const MULTI_LANG_TYPES = [
@@ -380,6 +383,15 @@ export const streamAllCSVsToDisk = async (graph, repoPath, csvDir) => {
380
383
  escapeCSVField(node.properties.description || ''),
381
384
  ].join(','));
382
385
  break;
386
+ case 'BasicBlock':
387
+ await basicBlockWriter.addRow([
388
+ escapeCSVField(node.id),
389
+ escapeCSVField(node.properties.filePath || ''),
390
+ escapeCSVNumber(node.properties.startLine, -1),
391
+ escapeCSVNumber(node.properties.endLine, -1),
392
+ escapeCSVField(node.properties.text || ''),
393
+ ].join(','));
394
+ break;
383
395
  default: {
384
396
  // Code element nodes (Function, Class, Interface, CodeElement)
385
397
  const writer = codeWriterMap[node.label];
@@ -433,6 +445,7 @@ export const streamAllCSVsToDisk = async (graph, repoPath, csvDir) => {
433
445
  sectionWriter,
434
446
  routeWriter,
435
447
  toolWriter,
448
+ basicBlockWriter,
436
449
  ...multiLangWriters.values(),
437
450
  ];
438
451
  await Promise.all(allWriters.map((w) => w.finish()));
@@ -465,6 +478,7 @@ export const streamAllCSVsToDisk = async (graph, repoPath, csvDir) => {
465
478
  ['Section', sectionWriter],
466
479
  ['Route', routeWriter],
467
480
  ['Tool', toolWriter],
481
+ ['BasicBlock', basicBlockWriter],
468
482
  ...Array.from(multiLangWriters.entries()).map(([name, w]) => [name, w]),
469
483
  ];
470
484
  for (const [name, writer] of tableMap) {
@@ -962,6 +962,10 @@ const getCopyQuery = (table, filePath) => {
962
962
  if (table === 'Tool') {
963
963
  return `COPY ${t}(id, name, filePath, description) FROM "${filePath}" ${COPY_CSV_OPTS}`;
964
964
  }
965
+ if (table === 'BasicBlock') {
966
+ // Taint/PDG substrate (issue #2080) — no name column.
967
+ return `COPY ${t}(id, filePath, startLine, endLine, text) FROM "${filePath}" ${COPY_CSV_OPTS}`;
968
+ }
965
969
  if (table === 'Method') {
966
970
  return `COPY ${t}(id, name, filePath, startLine, endLine, isExported, content, description, parameterCount, returnType) FROM "${filePath}" ${COPY_CSV_OPTS}`;
967
971
  }
@@ -1011,6 +1015,10 @@ export const insertNodeToLbug = async (label, properties, dbPath) => {
1011
1015
  : '';
1012
1016
  query = `CREATE (n:Section {id: ${escapeValue(properties.id)}, name: ${escapeValue(properties.name)}, filePath: ${escapeValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, level: ${properties.level || 1}, content: ${escapeValue(properties.content || '')}${descPart}})`;
1013
1017
  }
1018
+ else if (label === 'BasicBlock') {
1019
+ // Taint/PDG substrate (issue #2080) — no name column.
1020
+ query = `CREATE (n:BasicBlock {id: ${escapeValue(properties.id)}, filePath: ${escapeValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, text: ${escapeValue(properties.text || '')}})`;
1021
+ }
1014
1022
  else if (TABLES_WITH_EXPORTED.has(label)) {
1015
1023
  const descPart = properties.description
1016
1024
  ? `, description: ${escapeValue(properties.description)}`
@@ -1094,6 +1102,10 @@ export const batchInsertNodesToLbug = async (nodes, dbPath) => {
1094
1102
  : '';
1095
1103
  query = `MERGE (n:Section {id: ${escapeValue(properties.id)}}) SET n.name = ${escapeValue(properties.name)}, n.filePath = ${escapeValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.level = ${properties.level || 1}, n.content = ${escapeValue(properties.content || '')}${descPart}`;
1096
1104
  }
1105
+ else if (label === 'BasicBlock') {
1106
+ // Taint/PDG substrate (issue #2080) — no name column.
1107
+ query = `MERGE (n:BasicBlock {id: ${escapeValue(properties.id)}}) SET n.filePath = ${escapeValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.text = ${escapeValue(properties.text || '')}`;
1108
+ }
1097
1109
  else if (TABLES_WITH_EXPORTED.has(label)) {
1098
1110
  const descPart = properties.description
1099
1111
  ? `, n.description = ${escapeValue(properties.description)}`
@@ -42,7 +42,8 @@ export declare const MODULE_SCHEMA: string;
42
42
  export declare const ROUTE_SCHEMA = "\nCREATE NODE TABLE Route (\n id STRING,\n name STRING,\n filePath STRING,\n responseKeys STRING[],\n errorKeys STRING[],\n middleware STRING[],\n PRIMARY KEY (id)\n)";
43
43
  export declare const TOOL_SCHEMA = "\nCREATE NODE TABLE Tool (\n id STRING,\n name STRING,\n filePath STRING,\n description STRING,\n PRIMARY KEY (id)\n)";
44
44
  export declare const SECTION_SCHEMA = "\nCREATE NODE TABLE Section (\n id STRING,\n name STRING,\n filePath STRING,\n startLine INT64,\n endLine INT64,\n level INT64,\n content STRING,\n description STRING,\n PRIMARY KEY (id)\n)";
45
- export declare const RELATION_SCHEMA = "\nCREATE REL TABLE CodeRelation (\n FROM File TO File,\n FROM File TO Folder,\n FROM File TO Function,\n FROM File TO Class,\n FROM File TO Interface,\n FROM File TO Method,\n FROM File TO CodeElement,\n FROM File TO `Struct`,\n FROM File TO `Enum`,\n FROM File TO `Macro`,\n FROM File TO `Typedef`,\n FROM File TO `Union`,\n FROM File TO `Namespace`,\n FROM File TO `Trait`,\n FROM File TO `Impl`,\n FROM File TO `TypeAlias`,\n FROM File TO `Const`,\n FROM File TO `Static`,\n FROM File TO `Variable`,\n FROM File TO `Property`,\n FROM File TO `Record`,\n FROM File TO `Delegate`,\n FROM File TO `Annotation`,\n FROM File TO `Constructor`,\n FROM File TO `Template`,\n FROM File TO `Module`,\n FROM File TO Section,\n FROM Folder TO Folder,\n FROM Folder TO File,\n FROM Function TO Function,\n FROM Function TO Method,\n FROM Function TO Class,\n FROM Function TO Community,\n FROM Function TO `Macro`,\n FROM Function TO `Struct`,\n FROM Function TO `Template`,\n FROM Function TO `Enum`,\n FROM Function TO `Namespace`,\n FROM Function TO `TypeAlias`,\n FROM Function TO `Module`,\n FROM Function TO `Impl`,\n FROM Function TO Interface,\n FROM Function TO `Constructor`,\n FROM Function TO `Const`,\n FROM Function TO `Typedef`,\n FROM Function TO `Union`,\n FROM Function TO `Property`,\n FROM Function TO CodeElement,\n FROM Class TO Method,\n FROM Class TO Function,\n FROM Class TO Class,\n FROM Class TO Interface,\n FROM Class TO Community,\n FROM Class TO `Template`,\n FROM Class TO `TypeAlias`,\n FROM Class TO `Struct`,\n FROM Class TO `Enum`,\n FROM Class TO `Annotation`,\n FROM Class TO `Constructor`,\n FROM Class TO `Trait`,\n FROM Class TO `Macro`,\n FROM Class TO `Impl`,\n FROM Class TO `Union`,\n FROM Class TO `Namespace`,\n FROM Class TO `Typedef`,\n FROM Class TO `Property`,\n FROM Method TO Function,\n FROM Method TO Method,\n FROM Method TO Class,\n FROM Method TO Community,\n FROM Method TO `Template`,\n FROM Method TO `Struct`,\n FROM Method TO `TypeAlias`,\n FROM Method TO `Enum`,\n FROM Method TO `Macro`,\n FROM Method TO `Namespace`,\n FROM Method TO `Module`,\n FROM Method TO `Impl`,\n FROM Method TO Interface,\n FROM Method TO `Constructor`,\n FROM Method TO `Property`,\n FROM Method TO CodeElement,\n FROM `Template` TO `Template`,\n FROM `Template` TO Function,\n FROM `Template` TO Method,\n FROM `Template` TO Class,\n FROM `Template` TO `Struct`,\n FROM `Template` TO `TypeAlias`,\n FROM `Template` TO `Enum`,\n FROM `Template` TO `Macro`,\n FROM `Template` TO Interface,\n FROM `Template` TO `Constructor`,\n FROM `Module` TO `Module`,\n FROM Section TO Section,\n FROM Section TO File,\n FROM File TO Route,\n FROM Function TO Route,\n FROM Method TO Route,\n FROM File TO Tool,\n FROM Function TO Tool,\n FROM Method TO Tool,\n FROM CodeElement TO Community,\n FROM Interface TO Community,\n FROM Interface TO Function,\n FROM Interface TO Method,\n FROM Interface TO Class,\n FROM Interface TO Interface,\n FROM Interface TO `TypeAlias`,\n FROM Interface TO `Struct`,\n FROM Interface TO `Constructor`,\n FROM Interface TO `Property`,\n FROM `Struct` TO Community,\n FROM `Struct` TO `Trait`,\n FROM `Struct` TO `Struct`,\n FROM `Struct` TO Class,\n FROM `Struct` TO `Enum`,\n FROM `Struct` TO Function,\n FROM `Struct` TO Method,\n FROM `Struct` TO Interface,\n FROM `Struct` TO `Constructor`,\n FROM `Struct` TO `Property`,\n FROM `Enum` TO `Enum`,\n FROM `Enum` TO Community,\n FROM `Enum` TO Class,\n FROM `Enum` TO Interface,\n FROM `Macro` TO Community,\n FROM `Macro` TO Function,\n FROM `Macro` TO Method,\n FROM `Module` TO Function,\n FROM `Module` TO Method,\n FROM `Typedef` TO Community,\n FROM `Union` TO Community,\n FROM `Namespace` TO Community,\n FROM `Namespace` TO `Struct`,\n FROM `Trait` TO Method,\n FROM `Trait` TO `Constructor`,\n FROM `Trait` TO `Property`,\n FROM `Trait` TO Community,\n FROM `Impl` TO Method,\n FROM `Impl` TO `Constructor`,\n FROM `Impl` TO `Property`,\n FROM `Impl` TO Community,\n FROM `Impl` TO `Trait`,\n FROM `Impl` TO `Struct`,\n FROM `Impl` TO `Impl`,\n FROM `TypeAlias` TO Community,\n FROM `TypeAlias` TO `Trait`,\n FROM `TypeAlias` TO Class,\n FROM `Const` TO Community,\n FROM `Static` TO Community,\n FROM `Variable` TO Community,\n FROM `Property` TO Community,\n FROM `Record` TO Method,\n FROM `Record` TO `Constructor`,\n FROM `Record` TO `Property`,\n FROM `Record` TO Community,\n FROM `Delegate` TO Community,\n FROM `Annotation` TO Community,\n FROM `Constructor` TO Community,\n FROM `Constructor` TO Interface,\n FROM `Constructor` TO Class,\n FROM `Constructor` TO Method,\n FROM `Constructor` TO Function,\n FROM `Constructor` TO `Constructor`,\n FROM `Constructor` TO `Struct`,\n FROM `Constructor` TO `Macro`,\n FROM `Constructor` TO `Template`,\n FROM `Constructor` TO `TypeAlias`,\n FROM `Constructor` TO `Enum`,\n FROM `Constructor` TO `Annotation`,\n FROM `Constructor` TO `Impl`,\n FROM `Constructor` TO `Namespace`,\n FROM `Constructor` TO `Module`,\n FROM `Constructor` TO `Property`,\n FROM `Constructor` TO `Typedef`,\n FROM `Template` TO Community,\n FROM `Module` TO Community,\n FROM Function TO Process,\n FROM Method TO Process,\n FROM Class TO Process,\n FROM Interface TO Process,\n FROM `Struct` TO Process,\n FROM `Constructor` TO Process,\n FROM `Module` TO Process,\n FROM `Macro` TO Process,\n FROM `Impl` TO Process,\n FROM `Typedef` TO Process,\n FROM `TypeAlias` TO Process,\n FROM `Enum` TO Process,\n FROM `Union` TO Process,\n FROM `Namespace` TO Process,\n FROM `Trait` TO Process,\n FROM `Const` TO Process,\n FROM `Static` TO Process,\n FROM `Variable` TO Process,\n FROM `Property` TO Process,\n FROM `Record` TO Process,\n FROM `Delegate` TO Process,\n FROM `Annotation` TO Process,\n FROM `Template` TO Process,\n FROM CodeElement TO Process,\n FROM Route TO Process,\n FROM Tool TO Process,\n type STRING,\n confidence DOUBLE,\n reason STRING,\n step INT32\n)";
45
+ export declare const BASICBLOCK_SCHEMA = "\nCREATE NODE TABLE BasicBlock (\n id STRING,\n filePath STRING,\n startLine INT64,\n endLine INT64,\n text STRING,\n PRIMARY KEY (id)\n)";
46
+ export declare const RELATION_SCHEMA = "\nCREATE REL TABLE CodeRelation (\n FROM File TO File,\n FROM File TO Folder,\n FROM File TO Function,\n FROM File TO Class,\n FROM File TO Interface,\n FROM File TO Method,\n FROM File TO CodeElement,\n FROM File TO `Struct`,\n FROM File TO `Enum`,\n FROM File TO `Macro`,\n FROM File TO `Typedef`,\n FROM File TO `Union`,\n FROM File TO `Namespace`,\n FROM File TO `Trait`,\n FROM File TO `Impl`,\n FROM File TO `TypeAlias`,\n FROM File TO `Const`,\n FROM File TO `Static`,\n FROM File TO `Variable`,\n FROM File TO `Property`,\n FROM File TO `Record`,\n FROM File TO `Delegate`,\n FROM File TO `Annotation`,\n FROM File TO `Constructor`,\n FROM File TO `Template`,\n FROM File TO `Module`,\n FROM File TO Section,\n FROM Folder TO Folder,\n FROM Folder TO File,\n FROM Function TO Function,\n FROM Function TO Method,\n FROM Function TO Class,\n FROM Function TO Community,\n FROM Function TO `Macro`,\n FROM Function TO `Struct`,\n FROM Function TO `Template`,\n FROM Function TO `Enum`,\n FROM Function TO `Namespace`,\n FROM Function TO `TypeAlias`,\n FROM Function TO `Module`,\n FROM Function TO `Impl`,\n FROM Function TO Interface,\n FROM Function TO `Constructor`,\n FROM Function TO `Const`,\n FROM Function TO `Typedef`,\n FROM Function TO `Union`,\n FROM Function TO `Property`,\n FROM Function TO CodeElement,\n FROM Class TO Method,\n FROM Class TO Function,\n FROM Class TO Class,\n FROM Class TO Interface,\n FROM Class TO Community,\n FROM Class TO `Template`,\n FROM Class TO `TypeAlias`,\n FROM Class TO `Struct`,\n FROM Class TO `Enum`,\n FROM Class TO `Annotation`,\n FROM Class TO `Constructor`,\n FROM Class TO `Trait`,\n FROM Class TO `Macro`,\n FROM Class TO `Impl`,\n FROM Class TO `Union`,\n FROM Class TO `Namespace`,\n FROM Class TO `Typedef`,\n FROM Class TO `Property`,\n FROM Method TO Function,\n FROM Method TO Method,\n FROM Method TO Class,\n FROM Method TO Community,\n FROM Method TO `Template`,\n FROM Method TO `Struct`,\n FROM Method TO `TypeAlias`,\n FROM Method TO `Enum`,\n FROM Method TO `Macro`,\n FROM Method TO `Namespace`,\n FROM Method TO `Module`,\n FROM Method TO `Impl`,\n FROM Method TO Interface,\n FROM Method TO `Constructor`,\n FROM Method TO `Property`,\n FROM Method TO CodeElement,\n FROM `Template` TO `Template`,\n FROM `Template` TO Function,\n FROM `Template` TO Method,\n FROM `Template` TO Class,\n FROM `Template` TO `Struct`,\n FROM `Template` TO `TypeAlias`,\n FROM `Template` TO `Enum`,\n FROM `Template` TO `Macro`,\n FROM `Template` TO Interface,\n FROM `Template` TO `Constructor`,\n FROM `Module` TO `Module`,\n FROM Section TO Section,\n FROM Section TO File,\n FROM File TO Route,\n FROM Function TO Route,\n FROM Method TO Route,\n FROM File TO Tool,\n FROM Function TO Tool,\n FROM Method TO Tool,\n FROM CodeElement TO Community,\n FROM Interface TO Community,\n FROM Interface TO Function,\n FROM Interface TO Method,\n FROM Interface TO Class,\n FROM Interface TO Interface,\n FROM Interface TO `TypeAlias`,\n FROM Interface TO `Struct`,\n FROM Interface TO `Constructor`,\n FROM Interface TO `Property`,\n FROM `Struct` TO Community,\n FROM `Struct` TO `Trait`,\n FROM `Struct` TO `Struct`,\n FROM `Struct` TO Class,\n FROM `Struct` TO `Enum`,\n FROM `Struct` TO Function,\n FROM `Struct` TO Method,\n FROM `Struct` TO Interface,\n FROM `Struct` TO `Constructor`,\n FROM `Struct` TO `Property`,\n FROM `Enum` TO `Enum`,\n FROM `Enum` TO Community,\n FROM `Enum` TO Class,\n FROM `Enum` TO Interface,\n FROM `Macro` TO Community,\n FROM `Macro` TO Function,\n FROM `Macro` TO Method,\n FROM `Module` TO Function,\n FROM `Module` TO Method,\n FROM `Typedef` TO Community,\n FROM `Union` TO Community,\n FROM `Namespace` TO Community,\n FROM `Namespace` TO `Struct`,\n FROM `Trait` TO Method,\n FROM `Trait` TO `Constructor`,\n FROM `Trait` TO `Property`,\n FROM `Trait` TO Community,\n FROM `Impl` TO Method,\n FROM `Impl` TO `Constructor`,\n FROM `Impl` TO `Property`,\n FROM `Impl` TO Community,\n FROM `Impl` TO `Trait`,\n FROM `Impl` TO `Struct`,\n FROM `Impl` TO `Impl`,\n FROM `TypeAlias` TO Community,\n FROM `TypeAlias` TO `Trait`,\n FROM `TypeAlias` TO Class,\n FROM `Const` TO Community,\n FROM `Static` TO Community,\n FROM `Variable` TO Community,\n FROM `Property` TO Community,\n FROM `Record` TO Method,\n FROM `Record` TO `Constructor`,\n FROM `Record` TO `Property`,\n FROM `Record` TO Community,\n FROM `Delegate` TO Community,\n FROM `Annotation` TO Community,\n FROM `Constructor` TO Community,\n FROM `Constructor` TO Interface,\n FROM `Constructor` TO Class,\n FROM `Constructor` TO Method,\n FROM `Constructor` TO Function,\n FROM `Constructor` TO `Constructor`,\n FROM `Constructor` TO `Struct`,\n FROM `Constructor` TO `Macro`,\n FROM `Constructor` TO `Template`,\n FROM `Constructor` TO `TypeAlias`,\n FROM `Constructor` TO `Enum`,\n FROM `Constructor` TO `Annotation`,\n FROM `Constructor` TO `Impl`,\n FROM `Constructor` TO `Namespace`,\n FROM `Constructor` TO `Module`,\n FROM `Constructor` TO `Property`,\n FROM `Constructor` TO `Typedef`,\n FROM `Template` TO Community,\n FROM `Module` TO Community,\n FROM Function TO Process,\n FROM Method TO Process,\n FROM Class TO Process,\n FROM Interface TO Process,\n FROM `Struct` TO Process,\n FROM `Constructor` TO Process,\n FROM `Module` TO Process,\n FROM `Macro` TO Process,\n FROM `Impl` TO Process,\n FROM `Typedef` TO Process,\n FROM `TypeAlias` TO Process,\n FROM `Enum` TO Process,\n FROM `Union` TO Process,\n FROM `Namespace` TO Process,\n FROM `Trait` TO Process,\n FROM `Const` TO Process,\n FROM `Static` TO Process,\n FROM `Variable` TO Process,\n FROM `Property` TO Process,\n FROM `Record` TO Process,\n FROM `Delegate` TO Process,\n FROM `Annotation` TO Process,\n FROM `Template` TO Process,\n FROM CodeElement TO Process,\n FROM Route TO Process,\n FROM Tool TO Process,\n FROM BasicBlock TO BasicBlock,\n type STRING,\n confidence DOUBLE,\n reason STRING,\n step INT32\n)";
46
47
  export declare const EMBEDDING_DIMS: number;
47
48
  /** HNSW vector index name for the CodeEmbedding table. */
48
49
  export declare const EMBEDDING_INDEX_NAME = "code_embedding_idx";
@@ -201,6 +201,22 @@ CREATE NODE TABLE Section (
201
201
  description STRING,
202
202
  PRIMARY KEY (id)
203
203
  )`;
204
+ // Taint/PDG substrate (issue #2080) — intra-procedural control-flow node.
205
+ // Emitted by no phase yet; M1 (#2081) populates these behind an opt-in.
206
+ // REACHING_DEF carries its variable name in the relation's existing `reason`
207
+ // column (see RELATION_SCHEMA) — LadybugDB has no secondary index on rel
208
+ // properties, so a dedicated indexed column would buy nothing for the
209
+ // variable-filtered path query (M0/S1 verdict). No `name` column: blocks are
210
+ // identified by id + source span, not a symbol name.
211
+ export const BASICBLOCK_SCHEMA = `
212
+ CREATE NODE TABLE BasicBlock (
213
+ id STRING,
214
+ filePath STRING,
215
+ startLine INT64,
216
+ endLine INT64,
217
+ text STRING,
218
+ PRIMARY KEY (id)
219
+ )`;
204
220
  // ============================================================================
205
221
  // RELATION TABLE SCHEMA
206
222
  // Single table with 'type' property - connects all node tables
@@ -410,6 +426,7 @@ CREATE REL TABLE ${REL_TABLE_NAME} (
410
426
  FROM CodeElement TO Process,
411
427
  FROM Route TO Process,
412
428
  FROM Tool TO Process,
429
+ FROM BasicBlock TO BasicBlock,
413
430
  type STRING,
414
431
  confidence DOUBLE,
415
432
  reason STRING,
@@ -490,6 +507,11 @@ export const NODE_SCHEMA_QUERIES = [
490
507
  ROUTE_SCHEMA,
491
508
  // MCP tools
492
509
  TOOL_SCHEMA,
510
+ // Taint/PDG substrate (issue #2080) — must be appended here, not just
511
+ // declared above: SCHEMA_QUERIES (the list initLbug actually runs) is built
512
+ // from NODE_SCHEMA_QUERIES. Omitting this leaves the BasicBlock table
513
+ // uncreated and the bulk-COPY round-trip fails with "table does not exist".
514
+ BASICBLOCK_SCHEMA,
493
515
  ];
494
516
  export const REL_SCHEMA_QUERIES = [RELATION_SCHEMA];
495
517
  export const SCHEMA_QUERIES = [...NODE_SCHEMA_QUERIES, ...REL_SCHEMA_QUERIES, EMBEDDING_SCHEMA];
@@ -15,6 +15,14 @@ export interface GrammarSourceDescriptor {
15
15
  export declare const listGrammarSources: () => GrammarSourceDescriptor[];
16
16
  export declare const resolveLanguageKey: (language: SupportedLanguages, filePath?: string) => string;
17
17
  export declare const isLanguageAvailable: (language: SupportedLanguages, filePath?: string) => boolean;
18
+ /**
19
+ * True when `language`'s grammar is being treated as unavailable specifically
20
+ * because of the runtime GITNEXUS_SKIP_OPTIONAL_GRAMMARS opt-out — as opposed
21
+ * to a genuinely-missing/broken native binding. Lets callers surface an
22
+ * accurate "skipped on purpose" message instead of a spurious "npm rebuild"
23
+ * recovery hint. Returns false for required grammars and for an absent env.
24
+ */
25
+ export declare const isGrammarRuntimeSkipped: (language: SupportedLanguages, filePath?: string) => boolean;
18
26
  export declare const getLanguageGrammar: (language: SupportedLanguages, filePath?: string) => unknown;
19
27
  export declare const loadParser: () => Promise<Parser>;
20
28
  export declare const loadLanguage: (language: SupportedLanguages, filePath?: string) => Promise<void>;
@@ -86,6 +86,7 @@ const SOURCES = {
86
86
  [SupportedLanguages.Swift]: {
87
87
  load: () => _require('tree-sitter-swift'),
88
88
  optional: true,
89
+ userSkippable: true,
89
90
  unavailableNote: 'Swift parsing disabled: vendored `tree-sitter-swift` (under ' +
90
91
  '`gitnexus/vendor/tree-sitter-swift`) failed to load. ' +
91
92
  'Likely cause: no prebuilt `.node` for this platform/architecture. ' +
@@ -94,6 +95,7 @@ const SOURCES = {
94
95
  [SupportedLanguages.Dart]: {
95
96
  load: () => _require('tree-sitter-dart'),
96
97
  optional: true,
98
+ userSkippable: true,
97
99
  unavailableNote: 'Dart parsing disabled: vendored `tree-sitter-dart` (under ' +
98
100
  '`gitnexus/vendor/tree-sitter-dart`) failed to load. ' +
99
101
  'Likely cause: native compile failed at install (missing python3/make/g++). ' +
@@ -102,6 +104,7 @@ const SOURCES = {
102
104
  [SupportedLanguages.Kotlin]: {
103
105
  load: () => _require('tree-sitter-kotlin'),
104
106
  optional: true,
107
+ userSkippable: true,
105
108
  unavailableNote: 'Kotlin parsing disabled: `tree-sitter-kotlin` is an optionalDependency ' +
106
109
  'and is not installed (or its native binding failed to build).',
107
110
  },
@@ -112,6 +115,43 @@ export const listGrammarSources = () => Object.entries(SOURCES).map(([key, sourc
112
115
  }));
113
116
  const loadCache = new Map();
114
117
  const logged = new Set();
118
+ // Parsed form of GITNEXUS_SKIP_OPTIONAL_GRAMMARS, resolved lazily ONCE per
119
+ // process. The env is set before analyze runs, so re-reading + re-allocating a
120
+ // Set on every call was wasted work (and a latent trap for any future per-file
121
+ // caller). `vi.resetModules()` gives the unit tests a fresh module — and thus a
122
+ // fresh memo — per case, so this stays test-friendly.
123
+ // 'all' → every userSkippable grammar; Set → only the named ids
124
+ // (and `tree-sitter-<id>` spellings); null → env unset/empty (nothing).
125
+ let _skipDirective;
126
+ const skipDirective = () => {
127
+ if (_skipDirective !== undefined)
128
+ return _skipDirective;
129
+ const raw = (process.env.GITNEXUS_SKIP_OPTIONAL_GRAMMARS ?? '').trim().toLowerCase();
130
+ if (raw === '')
131
+ return (_skipDirective = null);
132
+ if (raw === '1' || raw === 'true' || raw === 'all' || raw === '*')
133
+ return (_skipDirective = 'all');
134
+ return (_skipDirective = new Set(raw
135
+ .split(',')
136
+ .map((s) => s.trim())
137
+ .filter(Boolean)
138
+ .flatMap((s) => [s, s.replace(/^tree-sitter-/, '')])));
139
+ };
140
+ const isRuntimeSkippedGrammar = (key, source) => {
141
+ // Only grammars explicitly flagged user-skippable (swift/dart/kotlin) — never
142
+ // required deps that use the optional machinery for ABI safety (C carries no
143
+ // `userSkippable`).
144
+ if (source.userSkippable !== true)
145
+ return false;
146
+ const directive = skipDirective();
147
+ if (directive === null)
148
+ return false;
149
+ if (directive === 'all')
150
+ return true;
151
+ // `key` is the SupportedLanguages value (e.g. `swift`); the directive Set
152
+ // already holds both the bare id and the `tree-sitter-<id>` spelling.
153
+ return directive.has(key) || directive.has(`tree-sitter-${key}`);
154
+ };
115
155
  const logFailure = (key, result) => {
116
156
  if (result.ok === true)
117
157
  return;
@@ -148,6 +188,24 @@ const loadGrammar = (key) => {
148
188
  loadCache.set(key, result);
149
189
  return result;
150
190
  }
191
+ // Runtime opt-out: treat a user-skipped optional grammar exactly like an
192
+ // absent binding (non-fatal unavailable + one warning), without attempting
193
+ // the native load. See `isRuntimeSkippedGrammar`.
194
+ if (isRuntimeSkippedGrammar(key, source)) {
195
+ // Deliberate opt-out: emit an accurate "disabled on purpose" note rather
196
+ // than `source.unavailableNote` (which blames a missing/unbuilt binding and
197
+ // would mislead a user who set the env intentionally — #2101 review).
198
+ const result = {
199
+ ok: false,
200
+ error: new Error('runtime opt-out'),
201
+ note: `${key} parsing disabled via GITNEXUS_SKIP_OPTIONAL_GRAMMARS (unset it to re-enable).`,
202
+ fatal: false,
203
+ severity: 'warn',
204
+ };
205
+ loadCache.set(key, result);
206
+ logFailure(key, result);
207
+ return result;
208
+ }
151
209
  let result;
152
210
  try {
153
211
  result = { ok: true, grammar: source.load() };
@@ -168,6 +226,18 @@ const loadGrammar = (key) => {
168
226
  return result;
169
227
  };
170
228
  export const isLanguageAvailable = (language, filePath) => loadGrammar(resolveLanguageKey(language, filePath)).ok;
229
+ /**
230
+ * True when `language`'s grammar is being treated as unavailable specifically
231
+ * because of the runtime GITNEXUS_SKIP_OPTIONAL_GRAMMARS opt-out — as opposed
232
+ * to a genuinely-missing/broken native binding. Lets callers surface an
233
+ * accurate "skipped on purpose" message instead of a spurious "npm rebuild"
234
+ * recovery hint. Returns false for required grammars and for an absent env.
235
+ */
236
+ export const isGrammarRuntimeSkipped = (language, filePath) => {
237
+ const key = resolveLanguageKey(language, filePath);
238
+ const source = SOURCES[key];
239
+ return source !== undefined && isRuntimeSkippedGrammar(key, source);
240
+ };
171
241
  export const getLanguageGrammar = (language, filePath) => {
172
242
  const key = resolveLanguageKey(language, filePath);
173
243
  const result = loadGrammar(key);
@@ -47,6 +47,7 @@ export declare const landingPageHtml: () => string;
47
47
  export declare const staticCacheControlSetHeaders: (res: express.Response, filePath: string) => void;
48
48
  export declare const registerWebUI: (app: express.Express, staticDir: string | null) => void;
49
49
  export declare const writeNdjsonRecord: (res: express.Response, record: GraphStreamRecord, signal?: AbortSignal) => Promise<void>;
50
+ export declare const getNodeQuery: (table: string, includeContent: boolean) => string;
50
51
  export declare const streamGraphNdjson: (res: express.Response, includeContent?: boolean, signal?: AbortSignal) => Promise<void>;
51
52
  /**
52
53
  * Handle a GET /api/file request body. Extracted from createServer's route
@@ -290,8 +290,16 @@ const buildGraph = async (includeContent = false) => {
290
290
  const GRAPH_RELATIONSHIP_QUERY = `MATCH (a)-[r:CodeRelation]->(b) RETURN a.id AS sourceId, b.id AS targetId, ` +
291
291
  `r.type AS type, r.confidence AS confidence, r.reason AS reason, r.step AS step`;
292
292
  const quoteNodeTable = (table) => `\`${table.replace(/`/g, '``')}\``;
293
- const getNodeQuery = (table, includeContent) => {
293
+ export const getNodeQuery = (table, includeContent) => {
294
294
  const tableLabel = quoteNodeTable(table);
295
+ if (table === 'BasicBlock') {
296
+ // Taint/PDG substrate (issue #2080) — BasicBlock has no name/content
297
+ // columns. Project only its declared columns: a default `n.name`
298
+ // projection raises a Ladybug "Cannot find property name" binder error
299
+ // (not matched by isIgnorableGraphQueryError), which would 500 the graph
300
+ // endpoint the moment BasicBlock joins NODE_TABLES, even on an empty table.
301
+ return `MATCH (n:${tableLabel}) RETURN n.id AS id, n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine, n.text AS text`;
302
+ }
295
303
  if (table === 'File') {
296
304
  return includeContent
297
305
  ? `MATCH (n:${tableLabel}) RETURN n.id AS id, n.name AS name, n.filePath AS filePath, n.content AS content`
@@ -320,10 +328,17 @@ const mapGraphNodeRow = (table, row, includeContent) => ({
320
328
  id: row.id ?? row[0],
321
329
  label: table,
322
330
  properties: {
323
- name: row.name ?? row.label ?? row[1],
331
+ // `?? ''` keeps NodeProperties.name a `string` even for label rows that
332
+ // project no name/label column (BasicBlock — taint/PDG substrate #2080).
333
+ // Without it, BasicBlock rows carry name:undefined (masked by the cast
334
+ // below) and the web layer (Header search, circles/tree layout) derefs
335
+ // `.name` unguarded → TypeError once M1 emits blocks. `row.text` gives a
336
+ // BasicBlock a sensible fallback name before the empty-string floor.
337
+ name: row.name ?? row.label ?? row.text ?? row[1] ?? '',
324
338
  filePath: row.filePath ?? row[2],
325
339
  startLine: row.startLine,
326
340
  endLine: row.endLine,
341
+ text: row.text,
327
342
  content: includeContent ? row.content : undefined,
328
343
  responseKeys: row.responseKeys,
329
344
  errorKeys: row.errorKeys,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.7-rc.1",
3
+ "version": "1.6.7-rc.3",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",