@harness-engineering/graph 0.11.1 → 0.11.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.
package/dist/index.mjs CHANGED
@@ -3857,29 +3857,41 @@ var KnowledgeLinker = class {
3857
3857
  // src/ingest/StructuralDriftDetector.ts
3858
3858
  var StructuralDriftDetector = class {
3859
3859
  detect(current, fresh) {
3860
- const findings = [];
3861
3860
  const currentById = new Map(current.entries.map((e) => [e.id, e]));
3862
3861
  const freshById = new Map(fresh.entries.map((e) => [e.id, e]));
3862
+ const findings = [
3863
+ ...this.findNew(currentById, freshById),
3864
+ ...this.findStale(currentById, freshById),
3865
+ ...this.findDrifted(currentById, freshById)
3866
+ ];
3867
+ findings.push(...this.findContradicting(fresh.entries, findings));
3868
+ const totalEntries = (/* @__PURE__ */ new Set([...currentById.keys(), ...freshById.keys()])).size;
3869
+ const driftScore = totalEntries > 0 ? findings.length / totalEntries : 0;
3870
+ return { findings, driftScore, summary: this.summarize(findings) };
3871
+ }
3872
+ /** 1. NEW: entries in fresh but not in current */
3873
+ findNew(currentById, freshById) {
3874
+ const findings = [];
3863
3875
  for (const [id, entry] of freshById) {
3864
3876
  if (!currentById.has(id)) {
3865
- findings.push({
3866
- entryId: id,
3867
- classification: "new",
3868
- fresh: entry,
3869
- severity: "low"
3870
- });
3877
+ findings.push({ entryId: id, classification: "new", fresh: entry, severity: "low" });
3871
3878
  }
3872
3879
  }
3880
+ return findings;
3881
+ }
3882
+ /** 2. STALE: entries in current but not in fresh */
3883
+ findStale(currentById, freshById) {
3884
+ const findings = [];
3873
3885
  for (const [id, entry] of currentById) {
3874
3886
  if (!freshById.has(id)) {
3875
- findings.push({
3876
- entryId: id,
3877
- classification: "stale",
3878
- current: entry,
3879
- severity: "high"
3880
- });
3887
+ findings.push({ entryId: id, classification: "stale", current: entry, severity: "high" });
3881
3888
  }
3882
3889
  }
3890
+ return findings;
3891
+ }
3892
+ /** 3. DRIFTED: entries in both but contentHash differs */
3893
+ findDrifted(currentById, freshById) {
3894
+ const findings = [];
3883
3895
  for (const [id, freshEntry] of freshById) {
3884
3896
  const currentEntry = currentById.get(id);
3885
3897
  if (currentEntry && currentEntry.contentHash !== freshEntry.contentHash) {
@@ -3892,43 +3904,56 @@ var StructuralDriftDetector = class {
3892
3904
  });
3893
3905
  }
3894
3906
  }
3907
+ return findings;
3908
+ }
3909
+ /** 4. CONTRADICTING: same entity name from different sources with different content */
3910
+ findContradicting(entries, existing) {
3911
+ const result = [];
3912
+ const alreadyContradicting = new Set(
3913
+ existing.filter((f) => f.classification === "contradicting").map((f) => f.entryId)
3914
+ );
3915
+ for (const [, group] of this.groupByName(entries)) {
3916
+ const finding = this.contradictionForGroup(group, alreadyContradicting);
3917
+ if (finding) {
3918
+ result.push(finding);
3919
+ alreadyContradicting.add(finding.entryId);
3920
+ }
3921
+ }
3922
+ return result;
3923
+ }
3924
+ groupByName(entries) {
3895
3925
  const byName = /* @__PURE__ */ new Map();
3896
- for (const entry of fresh.entries) {
3926
+ for (const entry of entries) {
3897
3927
  const group = byName.get(entry.name) ?? [];
3898
3928
  group.push(entry);
3899
3929
  byName.set(entry.name, group);
3900
3930
  }
3901
- for (const [, group] of byName) {
3902
- if (group.length > 1) {
3903
- const sources = new Set(group.map((e) => e.source));
3904
- const hashes = new Set(group.map((e) => e.contentHash));
3905
- if (sources.size > 1 && hashes.size > 1) {
3906
- const alreadyContradicting = new Set(
3907
- findings.filter((f) => f.classification === "contradicting").map((f) => f.entryId)
3908
- );
3909
- for (const entry of group) {
3910
- if (!alreadyContradicting.has(entry.id)) {
3911
- findings.push({
3912
- entryId: entry.id,
3913
- classification: "contradicting",
3914
- fresh: entry,
3915
- severity: "critical"
3916
- });
3917
- break;
3918
- }
3919
- }
3920
- }
3931
+ return byName;
3932
+ }
3933
+ contradictionForGroup(group, alreadyContradicting) {
3934
+ if (group.length <= 1) return void 0;
3935
+ const sources = new Set(group.map((e) => e.source));
3936
+ const hashes = new Set(group.map((e) => e.contentHash));
3937
+ if (sources.size <= 1 || hashes.size <= 1) return void 0;
3938
+ for (const entry of group) {
3939
+ if (!alreadyContradicting.has(entry.id)) {
3940
+ return {
3941
+ entryId: entry.id,
3942
+ classification: "contradicting",
3943
+ fresh: entry,
3944
+ severity: "critical"
3945
+ };
3921
3946
  }
3922
3947
  }
3923
- const totalEntries = (/* @__PURE__ */ new Set([...currentById.keys(), ...freshById.keys()])).size;
3924
- const driftScore = totalEntries > 0 ? findings.length / totalEntries : 0;
3925
- const summary = {
3948
+ return void 0;
3949
+ }
3950
+ summarize(findings) {
3951
+ return {
3926
3952
  new: findings.filter((f) => f.classification === "new").length,
3927
3953
  drifted: findings.filter((f) => f.classification === "drifted").length,
3928
3954
  stale: findings.filter((f) => f.classification === "stale").length,
3929
3955
  contradicting: findings.filter((f) => f.classification === "contradicting").length
3930
3956
  };
3931
- return { findings, driftScore, summary };
3932
3957
  }
3933
3958
  };
3934
3959
 
@@ -3977,6 +4002,29 @@ var KnowledgeStagingAggregator = class {
3977
4002
  return titleMatch ? titleMatch[1].trim() : path9.basename(filePath, ".md");
3978
4003
  }
3979
4004
  async generateGapReport(knowledgeDir, store) {
4005
+ const { domainDocNames, domainEntryCounts, totalEntries } = await this.collectDocumentedEntries(
4006
+ knowledgeDir,
4007
+ store
4008
+ );
4009
+ if (!store) {
4010
+ return this.buildEntryOnlyReport(domainEntryCounts, totalEntries);
4011
+ }
4012
+ const extractedByDomain = this.collectExtractedByDomain(store);
4013
+ const { domains, totalExtracted, totalGaps } = this.buildDomainCoverage(
4014
+ domainEntryCounts,
4015
+ extractedByDomain,
4016
+ domainDocNames
4017
+ );
4018
+ return {
4019
+ domains,
4020
+ totalEntries,
4021
+ totalExtracted,
4022
+ totalGaps,
4023
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
4024
+ };
4025
+ }
4026
+ /** Step 1-2: count `.md` entries per domain, capturing normalized names when a store is present. */
4027
+ async collectDocumentedEntries(knowledgeDir, store) {
3980
4028
  const domainDocNames = /* @__PURE__ */ new Map();
3981
4029
  const domainEntryCounts = /* @__PURE__ */ new Map();
3982
4030
  let totalEntries = 0;
@@ -3987,33 +4035,41 @@ var KnowledgeStagingAggregator = class {
3987
4035
  const domainPath = path9.join(knowledgeDir, dir.name);
3988
4036
  const files = await fs8.readdir(domainPath);
3989
4037
  const mdFiles = files.filter((f) => f.endsWith(".md"));
3990
- const entryCount = mdFiles.length;
3991
- totalEntries += entryCount;
3992
- domainEntryCounts.set(dir.name, entryCount);
4038
+ totalEntries += mdFiles.length;
4039
+ domainEntryCounts.set(dir.name, mdFiles.length);
3993
4040
  if (store) {
3994
- const names = [];
3995
- for (const file of mdFiles) {
3996
- const name = await this.extractDocName(path9.join(domainPath, file));
3997
- names.push(name.toLowerCase().trim());
3998
- }
3999
- domainDocNames.set(dir.name, names);
4041
+ domainDocNames.set(dir.name, await this.extractDocNames(domainPath, mdFiles));
4000
4042
  }
4001
4043
  }
4002
4044
  } catch {
4003
4045
  }
4004
- if (!store) {
4005
- const domains2 = [];
4006
- for (const [domain, entryCount] of domainEntryCounts) {
4007
- domains2.push({ domain, entryCount, extractedCount: 0, gapCount: 0, gapEntries: [] });
4008
- }
4009
- return {
4010
- domains: domains2,
4011
- totalEntries,
4012
- totalExtracted: 0,
4013
- totalGaps: 0,
4014
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
4015
- };
4046
+ return { domainDocNames, domainEntryCounts, totalEntries };
4047
+ }
4048
+ /** Extract and normalize the documented title from each markdown file in a domain. */
4049
+ async extractDocNames(domainPath, mdFiles) {
4050
+ const names = [];
4051
+ for (const file of mdFiles) {
4052
+ const name = await this.extractDocName(path9.join(domainPath, file));
4053
+ names.push(name.toLowerCase().trim());
4016
4054
  }
4055
+ return names;
4056
+ }
4057
+ /** Step 3: backward-compatible report when no store is available. */
4058
+ buildEntryOnlyReport(domainEntryCounts, totalEntries) {
4059
+ const domains = [];
4060
+ for (const [domain, entryCount] of domainEntryCounts) {
4061
+ domains.push({ domain, entryCount, extractedCount: 0, gapCount: 0, gapEntries: [] });
4062
+ }
4063
+ return {
4064
+ domains,
4065
+ totalEntries,
4066
+ totalExtracted: 0,
4067
+ totalGaps: 0,
4068
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
4069
+ };
4070
+ }
4071
+ /** Step 4: query store for all business nodes, group by domain, deduplicate by name. */
4072
+ collectExtractedByDomain(store) {
4017
4073
  const extractedByDomain = /* @__PURE__ */ new Map();
4018
4074
  for (const nodeType of BUSINESS_NODE_TYPES) {
4019
4075
  const nodes = store.findNodes({ type: nodeType });
@@ -4025,15 +4081,22 @@ var KnowledgeStagingAggregator = class {
4025
4081
  }
4026
4082
  }
4027
4083
  for (const [domain, nodes] of extractedByDomain) {
4028
- const seen = /* @__PURE__ */ new Set();
4029
- const deduped = nodes.filter((n) => {
4030
- const key = n.name.toLowerCase().trim();
4031
- if (seen.has(key)) return false;
4032
- seen.add(key);
4033
- return true;
4034
- });
4035
- extractedByDomain.set(domain, deduped);
4084
+ extractedByDomain.set(domain, this.dedupeByName(nodes));
4036
4085
  }
4086
+ return extractedByDomain;
4087
+ }
4088
+ /** Keep the first occurrence of each normalized name, preserving order. */
4089
+ dedupeByName(nodes) {
4090
+ const seen = /* @__PURE__ */ new Set();
4091
+ return nodes.filter((n) => {
4092
+ const key = n.name.toLowerCase().trim();
4093
+ if (seen.has(key)) return false;
4094
+ seen.add(key);
4095
+ return true;
4096
+ });
4097
+ }
4098
+ /** Step 5: build per-domain coverage with gap analysis and roll up totals. */
4099
+ buildDomainCoverage(domainEntryCounts, extractedByDomain, domainDocNames) {
4037
4100
  const allDomains = /* @__PURE__ */ new Set([...domainEntryCounts.keys(), ...extractedByDomain.keys()]);
4038
4101
  const domains = [];
4039
4102
  let totalExtracted = 0;
@@ -4044,29 +4107,28 @@ var KnowledgeStagingAggregator = class {
4044
4107
  const extractedCount = extractedNodes.length;
4045
4108
  totalExtracted += extractedCount;
4046
4109
  const docNames = domainDocNames.get(domain) ?? [];
4047
- const gapEntries = [];
4048
- for (const node of extractedNodes) {
4049
- const normalizedName = node.name.toLowerCase().trim();
4050
- if (!docNames.includes(normalizedName)) {
4051
- gapEntries.push({
4052
- nodeId: node.id,
4053
- name: node.name,
4054
- nodeType: node.type,
4055
- source: node.metadata?.source ?? "unknown",
4056
- hasContent: Boolean(node.content && node.content.trim().length >= 10)
4057
- });
4058
- }
4059
- }
4110
+ const gapEntries = this.computeGapEntries(extractedNodes, docNames);
4060
4111
  totalGaps += gapEntries.length;
4061
4112
  domains.push({ domain, entryCount, extractedCount, gapCount: gapEntries.length, gapEntries });
4062
4113
  }
4063
- return {
4064
- domains,
4065
- totalEntries,
4066
- totalExtracted,
4067
- totalGaps,
4068
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
4069
- };
4114
+ return { domains, totalExtracted, totalGaps };
4115
+ }
4116
+ /** Collect extracted nodes that have no matching documented name. */
4117
+ computeGapEntries(extractedNodes, docNames) {
4118
+ const gapEntries = [];
4119
+ for (const node of extractedNodes) {
4120
+ const normalizedName = node.name.toLowerCase().trim();
4121
+ if (!docNames.includes(normalizedName)) {
4122
+ gapEntries.push({
4123
+ nodeId: node.id,
4124
+ name: node.name,
4125
+ nodeType: node.type,
4126
+ source: node.metadata?.source ?? "unknown",
4127
+ hasContent: Boolean(node.content && node.content.trim().length >= 10)
4128
+ });
4129
+ }
4130
+ }
4131
+ return gapEntries;
4070
4132
  }
4071
4133
  async writeGapReport(report) {
4072
4134
  const gapsDir = path9.join(this.projectDir, ".harness", "knowledge");
@@ -4313,6 +4375,9 @@ var PATTERNS = {
4313
4375
  /@Test\s*\n\s*(?:public\s+|private\s+|protected\s+)?(?:static\s+)?(?:void|[\w<>]+)\s+(\w+)\s*\(/g
4314
4376
  ]
4315
4377
  };
4378
+ var RUST_FN_RE = /(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/;
4379
+ var JAVA_DISPLAY_RE = /@DisplayName\s*\(\s*"([^"]+)"\s*\)/;
4380
+ var JAVA_METHOD_RE = /^\s*(?:(?:public|private|protected)\s+)?(?:static\s+)?(?:void|[\w<>[\]]+)\s+(\w+)\s*\(/;
4316
4381
  var TestDescriptionExtractor = class {
4317
4382
  name = "test-descriptions";
4318
4383
  supportedExtensions = [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs", ".java"];
@@ -4342,83 +4407,87 @@ var TestDescriptionExtractor = class {
4342
4407
  const records = [];
4343
4408
  const describeStack = [];
4344
4409
  for (let i = 0; i < lines.length; i++) {
4345
- const line = lines[i];
4346
- const describeMatch = line.match(/describe\s*\(\s*(['"`])((?:(?!\1).)*)\1/);
4347
- if (describeMatch) {
4348
- describeStack.push(describeMatch[2]);
4349
- }
4350
- const itMatch = line.match(/(?:it|test)\s*\(\s*(['"`])((?:(?!\1).)*)\1/);
4351
- if (itMatch) {
4352
- const testName = itMatch[2];
4353
- const fullPath = [...describeStack, testName].join(" > ");
4354
- const patternKey = fullPath;
4355
- records.push({
4356
- id: `extracted:test-descriptions:${hash(filePath + ":" + patternKey)}`,
4357
- extractor: "test-descriptions",
4358
- language,
4359
- filePath,
4360
- line: i + 1,
4361
- nodeType: "business_rule",
4362
- name: testName,
4363
- content: fullPath,
4364
- confidence: 0.7,
4365
- metadata: {
4366
- suite: describeStack.length > 0 ? describeStack[describeStack.length - 1] : void 0,
4367
- framework: "vitest"
4368
- }
4369
- });
4370
- }
4371
- if (line.match(/^\s*\}\s*\)\s*;?\s*$/) && describeStack.length > 0) {
4372
- describeStack.pop();
4373
- }
4410
+ this.scanJsTsLine(lines[i], i, language, filePath, describeStack, records);
4374
4411
  }
4375
4412
  return records;
4376
4413
  }
4414
+ scanJsTsLine(line, index, language, filePath, describeStack, records) {
4415
+ const describeMatch = line.match(/describe\s*\(\s*(['"`])((?:(?!\1).)*)\1/);
4416
+ if (describeMatch) describeStack.push(describeMatch[2]);
4417
+ const itMatch = line.match(/(?:it|test)\s*\(\s*(['"`])((?:(?!\1).)*)\1/);
4418
+ if (itMatch) {
4419
+ records.push(this.buildJsTsRecord(itMatch[2], index, language, filePath, describeStack));
4420
+ }
4421
+ if (/^\s*\}\s*\)\s*;?\s*$/.test(line) && describeStack.length > 0) describeStack.pop();
4422
+ }
4423
+ buildJsTsRecord(testName, index, language, filePath, describeStack) {
4424
+ const fullPath = [...describeStack, testName].join(" > ");
4425
+ const patternKey = fullPath;
4426
+ return {
4427
+ id: `extracted:test-descriptions:${hash(filePath + ":" + patternKey)}`,
4428
+ extractor: "test-descriptions",
4429
+ language,
4430
+ filePath,
4431
+ line: index + 1,
4432
+ nodeType: "business_rule",
4433
+ name: testName,
4434
+ content: fullPath,
4435
+ confidence: 0.7,
4436
+ metadata: {
4437
+ suite: describeStack.length > 0 ? describeStack[describeStack.length - 1] : void 0,
4438
+ framework: "vitest"
4439
+ }
4440
+ };
4441
+ }
4377
4442
  extractPython(_content, filePath, language, lines) {
4378
4443
  const records = [];
4379
4444
  let currentClass;
4380
4445
  for (let i = 0; i < lines.length; i++) {
4381
4446
  const line = lines[i];
4382
4447
  const classMatch = line.match(/^class\s+(Test\w+)/);
4383
- if (classMatch) {
4384
- currentClass = classMatch[1];
4385
- }
4448
+ if (classMatch) currentClass = classMatch[1];
4386
4449
  const funcMatch = line.match(/^\s*def\s+(test_\w+)\s*\(/);
4387
4450
  if (funcMatch) {
4388
- const testName = funcMatch[1];
4389
- const humanName = testName.replace(/^test_/, "").replace(/_/g, " ");
4390
- let docstring;
4391
- if (i + 1 < lines.length) {
4392
- const nextLine = lines[i + 1].trim();
4393
- const docMatch = nextLine.match(/^"""(.+?)"""/);
4394
- if (docMatch) {
4395
- docstring = docMatch[1];
4396
- }
4397
- }
4398
- const patternKey = currentClass ? `${currentClass}.${testName}` : testName;
4399
- records.push({
4400
- id: `extracted:test-descriptions:${hash(filePath + ":" + patternKey)}`,
4401
- extractor: "test-descriptions",
4402
- language,
4403
- filePath,
4404
- line: i + 1,
4405
- nodeType: "business_rule",
4406
- name: docstring ?? humanName,
4407
- content: patternKey,
4408
- confidence: docstring ? 0.7 : 0.5,
4409
- metadata: {
4410
- suite: currentClass,
4411
- framework: "pytest",
4412
- functionName: testName
4413
- }
4414
- });
4415
- }
4416
- if (currentClass && /^\S/.test(line) && !line.startsWith("class ") && !line.startsWith("#") && line.trim() !== "") {
4417
- currentClass = void 0;
4451
+ records.push(
4452
+ this.buildPythonRecord(funcMatch[1], i, language, filePath, currentClass, lines)
4453
+ );
4418
4454
  }
4455
+ if (this.isPythonClassReset(line, currentClass)) currentClass = void 0;
4419
4456
  }
4420
4457
  return records;
4421
4458
  }
4459
+ isPythonClassReset(line, currentClass) {
4460
+ return Boolean(
4461
+ currentClass && /^\S/.test(line) && !line.startsWith("class ") && !line.startsWith("#") && line.trim() !== ""
4462
+ );
4463
+ }
4464
+ findPythonDocstring(lines, index) {
4465
+ if (index + 1 >= lines.length) return void 0;
4466
+ const nextLine = lines[index + 1].trim();
4467
+ const docMatch = nextLine.match(/^"""(.+?)"""/);
4468
+ return docMatch ? docMatch[1] : void 0;
4469
+ }
4470
+ buildPythonRecord(testName, index, language, filePath, currentClass, lines) {
4471
+ const humanName = testName.replace(/^test_/, "").replace(/_/g, " ");
4472
+ const docstring = this.findPythonDocstring(lines, index);
4473
+ const patternKey = currentClass ? `${currentClass}.${testName}` : testName;
4474
+ return {
4475
+ id: `extracted:test-descriptions:${hash(filePath + ":" + patternKey)}`,
4476
+ extractor: "test-descriptions",
4477
+ language,
4478
+ filePath,
4479
+ line: index + 1,
4480
+ nodeType: "business_rule",
4481
+ name: docstring ?? humanName,
4482
+ content: patternKey,
4483
+ confidence: docstring ? 0.7 : 0.5,
4484
+ metadata: {
4485
+ suite: currentClass,
4486
+ framework: "pytest",
4487
+ functionName: testName
4488
+ }
4489
+ };
4490
+ }
4422
4491
  extractGo(_content, filePath, language, lines) {
4423
4492
  const records = [];
4424
4493
  let currentTest;
@@ -4467,89 +4536,107 @@ var TestDescriptionExtractor = class {
4467
4536
  extractRust(_content, filePath, language, lines) {
4468
4537
  const records = [];
4469
4538
  for (let i = 0; i < lines.length; i++) {
4470
- const line = lines[i];
4471
- if (line.trim() === "#[test]") {
4472
- for (let j = i + 1; j < lines.length && j <= i + 3; j++) {
4473
- const fnLine = lines[j];
4474
- const fnMatch = fnLine.match(/(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/);
4475
- if (fnMatch) {
4476
- const testName = fnMatch[1];
4477
- const humanName = testName.replace(/^test_/, "").replace(/_/g, " ");
4478
- let docComment;
4479
- if (i > 0) {
4480
- const prevLine = lines[i - 1].trim();
4481
- const docMatch = prevLine.match(/^\/\/\/\s*(.+)/);
4482
- if (docMatch) {
4483
- docComment = docMatch[1];
4484
- }
4485
- }
4486
- records.push({
4487
- id: `extracted:test-descriptions:${hash(filePath + ":" + testName)}`,
4488
- extractor: "test-descriptions",
4489
- language,
4490
- filePath,
4491
- line: j + 1,
4492
- nodeType: "business_rule",
4493
- name: docComment ?? humanName,
4494
- content: testName,
4495
- confidence: docComment ? 0.7 : 0.5,
4496
- metadata: { framework: "rust-test" }
4497
- });
4498
- break;
4499
- }
4500
- }
4501
- }
4539
+ if (lines[i].trim() !== "#[test]") continue;
4540
+ const record = this.buildRustRecord(lines, i, language, filePath);
4541
+ if (record) records.push(record);
4502
4542
  }
4503
4543
  return records;
4504
4544
  }
4545
+ buildRustRecord(lines, testIdx, language, filePath) {
4546
+ for (let j = testIdx + 1; j < lines.length && j <= testIdx + 3; j++) {
4547
+ const fnMatch = lines[j].match(RUST_FN_RE);
4548
+ if (!fnMatch) continue;
4549
+ const testName = fnMatch[1];
4550
+ const humanName = testName.replace(/^test_/, "").replace(/_/g, " ");
4551
+ const docComment = this.findRustDocComment(lines, testIdx);
4552
+ return {
4553
+ id: `extracted:test-descriptions:${hash(filePath + ":" + testName)}`,
4554
+ extractor: "test-descriptions",
4555
+ language,
4556
+ filePath,
4557
+ line: j + 1,
4558
+ nodeType: "business_rule",
4559
+ name: docComment ?? humanName,
4560
+ content: testName,
4561
+ confidence: docComment ? 0.7 : 0.5,
4562
+ metadata: { framework: "rust-test" }
4563
+ };
4564
+ }
4565
+ return void 0;
4566
+ }
4567
+ findRustDocComment(lines, testIdx) {
4568
+ if (testIdx <= 0) return void 0;
4569
+ const prevLine = lines[testIdx - 1].trim();
4570
+ const docMatch = prevLine.match(/^\/\/\/\s*(.+)/);
4571
+ return docMatch ? docMatch[1] : void 0;
4572
+ }
4505
4573
  extractJava(_content, filePath, language, lines) {
4506
4574
  const records = [];
4507
4575
  for (let i = 0; i < lines.length; i++) {
4508
- const line = lines[i];
4509
- const testMatch = line.match(/@Test\s*$/);
4510
- if (testMatch) {
4511
- let displayName;
4512
- for (let k = Math.max(0, i - 3); k < i; k++) {
4513
- const prevLine = lines[k];
4514
- const dm = prevLine.match(/@DisplayName\s*\(\s*"([^"]+)"\s*\)/);
4515
- if (dm) displayName = dm[1];
4516
- }
4517
- for (let j = i + 1; j < lines.length && j <= i + 5; j++) {
4518
- const scanLine = lines[j];
4519
- const adjacentDisplay = scanLine.match(/@DisplayName\s*\(\s*"([^"]+)"\s*\)/);
4520
- if (adjacentDisplay) {
4521
- displayName = adjacentDisplay[1];
4522
- continue;
4523
- }
4524
- const methodMatch = scanLine.match(
4525
- /^\s*(?:(?:public|private|protected)\s+)?(?:static\s+)?(?:void|[\w<>[\]]+)\s+(\w+)\s*\(/
4526
- );
4527
- if (methodMatch) {
4528
- const methodName = methodMatch[1];
4529
- const name = displayName ?? methodName;
4530
- const patternKey = displayName ?? methodName;
4531
- records.push({
4532
- id: `extracted:test-descriptions:${hash(filePath + ":" + patternKey)}`,
4533
- extractor: "test-descriptions",
4534
- language,
4535
- filePath,
4536
- line: j + 1,
4537
- nodeType: "business_rule",
4538
- name,
4539
- content: patternKey,
4540
- confidence: displayName ? 0.7 : 0.5,
4541
- metadata: { framework: "junit5" }
4542
- });
4543
- break;
4544
- }
4545
- }
4546
- }
4576
+ if (!/@Test\s*$/.test(lines[i])) continue;
4577
+ const record = this.buildJavaRecord(lines, i, language, filePath);
4578
+ if (record) records.push(record);
4547
4579
  }
4548
4580
  return records;
4549
4581
  }
4582
+ buildJavaRecord(lines, testIdx, language, filePath) {
4583
+ let displayName = this.findJavaDisplayNameBefore(lines, testIdx);
4584
+ for (let j = testIdx + 1; j < lines.length && j <= testIdx + 5; j++) {
4585
+ const scanLine = lines[j];
4586
+ const adjacentDisplay = scanLine.match(JAVA_DISPLAY_RE);
4587
+ if (adjacentDisplay) {
4588
+ displayName = adjacentDisplay[1];
4589
+ continue;
4590
+ }
4591
+ const methodMatch = scanLine.match(JAVA_METHOD_RE);
4592
+ if (methodMatch) {
4593
+ return this.buildJavaRecordFromMethod(methodMatch[1], displayName, j, language, filePath);
4594
+ }
4595
+ }
4596
+ return void 0;
4597
+ }
4598
+ findJavaDisplayNameBefore(lines, testIdx) {
4599
+ let displayName;
4600
+ for (let k = Math.max(0, testIdx - 3); k < testIdx; k++) {
4601
+ const dm = lines[k].match(JAVA_DISPLAY_RE);
4602
+ if (dm) displayName = dm[1];
4603
+ }
4604
+ return displayName;
4605
+ }
4606
+ buildJavaRecordFromMethod(methodName, displayName, index, language, filePath) {
4607
+ const name = displayName ?? methodName;
4608
+ const patternKey = displayName ?? methodName;
4609
+ return {
4610
+ id: `extracted:test-descriptions:${hash(filePath + ":" + patternKey)}`,
4611
+ extractor: "test-descriptions",
4612
+ language,
4613
+ filePath,
4614
+ line: index + 1,
4615
+ nodeType: "business_rule",
4616
+ name,
4617
+ content: patternKey,
4618
+ confidence: displayName ? 0.7 : 0.5,
4619
+ metadata: { framework: "junit5" }
4620
+ };
4621
+ }
4550
4622
  };
4551
4623
 
4552
4624
  // src/ingest/extractors/EnumConstantExtractor.ts
4625
+ var TS_ENUM_RE = /(?:export\s+)?enum\s+(\w+)/;
4626
+ var TS_AS_CONST_RE = /(?:export\s+)?const\s+(\w+)\s*=\s*\{/;
4627
+ var TS_UNION_RE = /(?:export\s+)?type\s+(\w+)\s*=\s*(['"`][\w]+['"`](?:\s*\|\s*['"`][\w]+['"`])+)/;
4628
+ var JS_FREEZE_RE = /(?:export\s+)?const\s+(\w+)\s*=\s*Object\.freeze\s*\(\s*\{/;
4629
+ var JS_CONST_OBJECT_RE = /(?:export\s+)?const\s+([A-Z][A-Z_\d]*)\s*=\s*\{/;
4630
+ var JS_UPPER_KEY_RE = /^[A-Z][A-Z_\d]*$/;
4631
+ var PY_ENUM_RE = /^class\s+(\w+)\s*\(\s*(?:str\s*,\s*)?(?:Enum|StrEnum|IntEnum|Flag|IntFlag)\s*\)/;
4632
+ var PY_LITERAL_RE = /(\w+)\s*=\s*Literal\s*\[([^\]]+)\]/;
4633
+ var GO_TYPE_RE = /^type\s+(\w+)\s+(?:int|string|uint|int32|int64|uint32|uint64)/;
4634
+ var GO_CONST_BLOCK_RE = /^const\s*\(/;
4635
+ var GO_IOTA_RE = /^(\w+)\s+(\w+)\s*=\s*iota/;
4636
+ var GO_TYPED_RE = /^(\w+)\s+(\w+)\s*=\s*"[^"]*"/;
4637
+ var GO_BARE_RE = /^(\w+)\s*$/;
4638
+ var RUST_ENUM_RE = /^(?:pub\s+)?enum\s+(\w+)/;
4639
+ var JAVA_ENUM_RE = /(?:public\s+|private\s+|protected\s+)?enum\s+(\w+)/;
4553
4640
  var EnumConstantExtractor = class {
4554
4641
  name = "enum-constants";
4555
4642
  supportedExtensions = [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs", ".java"];
@@ -4573,213 +4660,235 @@ var EnumConstantExtractor = class {
4573
4660
  const records = [];
4574
4661
  const lines = content.split("\n");
4575
4662
  for (let i = 0; i < lines.length; i++) {
4576
- const line = lines[i];
4577
- const enumMatch = line.match(/(?:export\s+)?enum\s+(\w+)/);
4578
- if (enumMatch) {
4579
- const enumName = enumMatch[1];
4580
- const members = this.collectEnumMembers(lines, i + 1);
4581
- records.push({
4582
- id: `extracted:enum-constants:${hash(filePath + ":" + enumName)}`,
4583
- extractor: "enum-constants",
4584
- language,
4585
- filePath,
4586
- line: i + 1,
4587
- nodeType: "business_term",
4588
- name: enumName,
4589
- content: `enum ${enumName} { ${members.join(", ")} }`,
4590
- confidence: 0.8,
4591
- metadata: { kind: "enum", members }
4592
- });
4593
- }
4594
- const constMatch = line.match(/(?:export\s+)?const\s+(\w+)\s*=\s*\{/);
4595
- if (constMatch && content.includes("as const")) {
4596
- const blockEnd = this.findClosingBrace(lines, i);
4597
- const block = lines.slice(i, blockEnd + 1).join("\n");
4598
- if (block.includes("as const")) {
4599
- const constName = constMatch[1];
4600
- const members = this.collectObjectKeys(lines, i + 1);
4601
- records.push({
4602
- id: `extracted:enum-constants:${hash(filePath + ":" + constName)}`,
4603
- extractor: "enum-constants",
4604
- language,
4605
- filePath,
4606
- line: i + 1,
4607
- nodeType: "business_term",
4608
- name: constName,
4609
- content: `const ${constName} = { ${members.join(", ")} } as const`,
4610
- confidence: 0.8,
4611
- metadata: { kind: "as-const", members }
4612
- });
4613
- }
4614
- }
4615
- const unionMatch = line.match(
4616
- /(?:export\s+)?type\s+(\w+)\s*=\s*(['"`][\w]+['"`](?:\s*\|\s*['"`][\w]+['"`])+)/
4617
- );
4618
- if (unionMatch) {
4619
- const typeName = unionMatch[1];
4620
- const values = unionMatch[2].split("|").map((v) => v.trim().replace(/['"`]/g, ""));
4621
- records.push({
4622
- id: `extracted:enum-constants:${hash(filePath + ":" + typeName)}`,
4623
- extractor: "enum-constants",
4624
- language,
4625
- filePath,
4626
- line: i + 1,
4627
- nodeType: "business_term",
4628
- name: typeName,
4629
- content: `type ${typeName} = ${values.map((v) => `'${v}'`).join(" | ")}`,
4630
- confidence: 0.7,
4631
- metadata: { kind: "union-type", members: values }
4632
- });
4633
- }
4663
+ const enumRecord = this.matchTsEnum(lines, i, filePath, language);
4664
+ if (enumRecord) records.push(enumRecord);
4665
+ const asConstRecord = this.matchTsAsConst(content, lines, i, filePath, language);
4666
+ if (asConstRecord) records.push(asConstRecord);
4667
+ const unionRecord = this.matchTsUnion(lines, i, filePath, language);
4668
+ if (unionRecord) records.push(unionRecord);
4634
4669
  }
4635
4670
  return records;
4636
4671
  }
4672
+ matchTsEnum(lines, i, filePath, language) {
4673
+ const enumMatch = lines[i].match(TS_ENUM_RE);
4674
+ if (!enumMatch) return void 0;
4675
+ const enumName = enumMatch[1];
4676
+ const members = this.collectEnumMembers(lines, i + 1);
4677
+ return {
4678
+ id: `extracted:enum-constants:${hash(filePath + ":" + enumName)}`,
4679
+ extractor: "enum-constants",
4680
+ language,
4681
+ filePath,
4682
+ line: i + 1,
4683
+ nodeType: "business_term",
4684
+ name: enumName,
4685
+ content: `enum ${enumName} { ${members.join(", ")} }`,
4686
+ confidence: 0.8,
4687
+ metadata: { kind: "enum", members }
4688
+ };
4689
+ }
4690
+ matchTsAsConst(content, lines, i, filePath, language) {
4691
+ const constMatch = lines[i].match(TS_AS_CONST_RE);
4692
+ if (!constMatch || !content.includes("as const")) return void 0;
4693
+ const blockEnd = this.findClosingBrace(lines, i);
4694
+ const block = lines.slice(i, blockEnd + 1).join("\n");
4695
+ if (!block.includes("as const")) return void 0;
4696
+ const constName = constMatch[1];
4697
+ const members = this.collectObjectKeys(lines, i + 1);
4698
+ return {
4699
+ id: `extracted:enum-constants:${hash(filePath + ":" + constName)}`,
4700
+ extractor: "enum-constants",
4701
+ language,
4702
+ filePath,
4703
+ line: i + 1,
4704
+ nodeType: "business_term",
4705
+ name: constName,
4706
+ content: `const ${constName} = { ${members.join(", ")} } as const`,
4707
+ confidence: 0.8,
4708
+ metadata: { kind: "as-const", members }
4709
+ };
4710
+ }
4711
+ matchTsUnion(lines, i, filePath, language) {
4712
+ const unionMatch = lines[i].match(TS_UNION_RE);
4713
+ if (!unionMatch) return void 0;
4714
+ const typeName = unionMatch[1];
4715
+ const values = unionMatch[2].split("|").map((v) => v.trim().replace(/['"`]/g, ""));
4716
+ return {
4717
+ id: `extracted:enum-constants:${hash(filePath + ":" + typeName)}`,
4718
+ extractor: "enum-constants",
4719
+ language,
4720
+ filePath,
4721
+ line: i + 1,
4722
+ nodeType: "business_term",
4723
+ name: typeName,
4724
+ content: `type ${typeName} = ${values.map((v) => `'${v}'`).join(" | ")}`,
4725
+ confidence: 0.7,
4726
+ metadata: { kind: "union-type", members: values }
4727
+ };
4728
+ }
4637
4729
  extractJavaScript(content, filePath, language) {
4638
4730
  const records = [];
4639
4731
  const lines = content.split("\n");
4640
4732
  for (let i = 0; i < lines.length; i++) {
4641
- const line = lines[i];
4642
- const freezeMatch = line.match(/(?:export\s+)?const\s+(\w+)\s*=\s*Object\.freeze\s*\(\s*\{/);
4733
+ const freezeMatch = lines[i].match(JS_FREEZE_RE);
4643
4734
  if (freezeMatch) {
4644
- const name = freezeMatch[1];
4645
- const members = this.collectObjectKeys(lines, i + 1);
4646
- records.push({
4647
- id: `extracted:enum-constants:${hash(filePath + ":" + name)}`,
4648
- extractor: "enum-constants",
4649
- language,
4650
- filePath,
4651
- line: i + 1,
4652
- nodeType: "business_term",
4653
- name,
4654
- content: `const ${name} = Object.freeze({ ${members.join(", ")} })`,
4655
- confidence: 0.8,
4656
- metadata: { kind: "frozen-object", members }
4657
- });
4735
+ records.push(this.buildJsFreezeRecord(freezeMatch[1], lines, i, filePath, language));
4658
4736
  }
4659
- const constMatch = line.match(/(?:export\s+)?const\s+([A-Z][A-Z_\d]*)\s*=\s*\{/);
4660
- if (constMatch && !freezeMatch) {
4661
- const name = constMatch[1];
4662
- const members = this.collectObjectKeys(lines, i + 1);
4663
- if (members.length > 0 && members.every((m) => /^[A-Z][A-Z_\d]*$/.test(m))) {
4664
- records.push({
4665
- id: `extracted:enum-constants:${hash(filePath + ":" + name)}`,
4666
- extractor: "enum-constants",
4667
- language,
4668
- filePath,
4669
- line: i + 1,
4670
- nodeType: "business_term",
4671
- name,
4672
- content: `const ${name} = { ${members.join(", ")} }`,
4673
- confidence: 0.6,
4674
- metadata: { kind: "const-object", members }
4675
- });
4676
- }
4737
+ if (!freezeMatch) {
4738
+ const constRecord = this.matchJsConstObject(lines, i, filePath, language);
4739
+ if (constRecord) records.push(constRecord);
4677
4740
  }
4678
4741
  }
4679
4742
  return records;
4680
4743
  }
4744
+ buildJsFreezeRecord(name, lines, i, filePath, language) {
4745
+ const members = this.collectObjectKeys(lines, i + 1);
4746
+ return {
4747
+ id: `extracted:enum-constants:${hash(filePath + ":" + name)}`,
4748
+ extractor: "enum-constants",
4749
+ language,
4750
+ filePath,
4751
+ line: i + 1,
4752
+ nodeType: "business_term",
4753
+ name,
4754
+ content: `const ${name} = Object.freeze({ ${members.join(", ")} })`,
4755
+ confidence: 0.8,
4756
+ metadata: { kind: "frozen-object", members }
4757
+ };
4758
+ }
4759
+ matchJsConstObject(lines, i, filePath, language) {
4760
+ const constMatch = lines[i].match(JS_CONST_OBJECT_RE);
4761
+ if (!constMatch) return void 0;
4762
+ const name = constMatch[1];
4763
+ const members = this.collectObjectKeys(lines, i + 1);
4764
+ if (members.length === 0 || !members.every((m) => JS_UPPER_KEY_RE.test(m))) return void 0;
4765
+ return {
4766
+ id: `extracted:enum-constants:${hash(filePath + ":" + name)}`,
4767
+ extractor: "enum-constants",
4768
+ language,
4769
+ filePath,
4770
+ line: i + 1,
4771
+ nodeType: "business_term",
4772
+ name,
4773
+ content: `const ${name} = { ${members.join(", ")} }`,
4774
+ confidence: 0.6,
4775
+ metadata: { kind: "const-object", members }
4776
+ };
4777
+ }
4681
4778
  extractPython(content, filePath, language) {
4682
4779
  const records = [];
4683
4780
  const lines = content.split("\n");
4684
4781
  for (let i = 0; i < lines.length; i++) {
4685
- const line = lines[i];
4686
- const enumMatch = line.match(
4687
- /^class\s+(\w+)\s*\(\s*(?:str\s*,\s*)?(?:Enum|StrEnum|IntEnum|Flag|IntFlag)\s*\)/
4688
- );
4689
- if (enumMatch) {
4690
- const enumName = enumMatch[1];
4691
- const members = this.collectPythonEnumMembers(lines, i + 1);
4692
- records.push({
4693
- id: `extracted:enum-constants:${hash(filePath + ":" + enumName)}`,
4694
- extractor: "enum-constants",
4695
- language,
4696
- filePath,
4697
- line: i + 1,
4698
- nodeType: "business_term",
4699
- name: enumName,
4700
- content: `class ${enumName}(Enum) { ${members.join(", ")} }`,
4701
- confidence: 0.8,
4702
- metadata: { kind: "enum", members }
4703
- });
4704
- }
4705
- const literalMatch = line.match(/(\w+)\s*=\s*Literal\s*\[([^\]]+)\]/);
4706
- if (literalMatch) {
4707
- const typeName = literalMatch[1];
4708
- const values = literalMatch[2].split(",").map((v) => v.trim().replace(/["']/g, ""));
4709
- records.push({
4710
- id: `extracted:enum-constants:${hash(filePath + ":" + typeName)}`,
4711
- extractor: "enum-constants",
4712
- language,
4713
- filePath,
4714
- line: i + 1,
4715
- nodeType: "business_term",
4716
- name: typeName,
4717
- content: `${typeName} = Literal[${values.map((v) => `"${v}"`).join(", ")}]`,
4718
- confidence: 0.7,
4719
- metadata: { kind: "literal-type", members: values }
4720
- });
4721
- }
4782
+ const enumRecord = this.matchPythonEnum(lines, i, filePath, language);
4783
+ if (enumRecord) records.push(enumRecord);
4784
+ const literalRecord = this.matchPythonLiteral(lines, i, filePath, language);
4785
+ if (literalRecord) records.push(literalRecord);
4722
4786
  }
4723
4787
  return records;
4724
4788
  }
4789
+ matchPythonEnum(lines, i, filePath, language) {
4790
+ const enumMatch = lines[i].match(PY_ENUM_RE);
4791
+ if (!enumMatch) return void 0;
4792
+ const enumName = enumMatch[1];
4793
+ const members = this.collectPythonEnumMembers(lines, i + 1);
4794
+ return {
4795
+ id: `extracted:enum-constants:${hash(filePath + ":" + enumName)}`,
4796
+ extractor: "enum-constants",
4797
+ language,
4798
+ filePath,
4799
+ line: i + 1,
4800
+ nodeType: "business_term",
4801
+ name: enumName,
4802
+ content: `class ${enumName}(Enum) { ${members.join(", ")} }`,
4803
+ confidence: 0.8,
4804
+ metadata: { kind: "enum", members }
4805
+ };
4806
+ }
4807
+ matchPythonLiteral(lines, i, filePath, language) {
4808
+ const literalMatch = lines[i].match(PY_LITERAL_RE);
4809
+ if (!literalMatch) return void 0;
4810
+ const typeName = literalMatch[1];
4811
+ const values = literalMatch[2].split(",").map((v) => v.trim().replace(/["']/g, ""));
4812
+ return {
4813
+ id: `extracted:enum-constants:${hash(filePath + ":" + typeName)}`,
4814
+ extractor: "enum-constants",
4815
+ language,
4816
+ filePath,
4817
+ line: i + 1,
4818
+ nodeType: "business_term",
4819
+ name: typeName,
4820
+ content: `${typeName} = Literal[${values.map((v) => `"${v}"`).join(", ")}]`,
4821
+ confidence: 0.7,
4822
+ metadata: { kind: "literal-type", members: values }
4823
+ };
4824
+ }
4725
4825
  extractGo(content, filePath, language) {
4726
4826
  const records = [];
4727
4827
  const lines = content.split("\n");
4728
4828
  for (let i = 0; i < lines.length; i++) {
4729
4829
  const line = lines[i];
4730
- const typeMatch = line.match(/^type\s+(\w+)\s+(?:int|string|uint|int32|int64|uint32|uint64)/);
4830
+ const typeMatch = line.match(GO_TYPE_RE);
4731
4831
  if (typeMatch) {
4732
4832
  }
4733
- const constBlockMatch = line.match(/^const\s*\(/);
4833
+ const constBlockMatch = line.match(GO_CONST_BLOCK_RE);
4734
4834
  if (constBlockMatch) {
4735
- const consts = [];
4736
- let typeName;
4737
- for (let j = i + 1; j < lines.length; j++) {
4738
- const constLine = lines[j].trim();
4739
- if (constLine === ")") break;
4740
- if (constLine === "" || constLine.startsWith("//")) continue;
4741
- const iotaMatch = constLine.match(/^(\w+)\s+(\w+)\s*=\s*iota/);
4742
- if (iotaMatch) {
4743
- typeName = iotaMatch[2];
4744
- consts.push(iotaMatch[1]);
4745
- continue;
4746
- }
4747
- const typedMatch = constLine.match(/^(\w+)\s+(\w+)\s*=\s*"[^"]*"/);
4748
- if (typedMatch) {
4749
- typeName = typeName ?? typedMatch[2];
4750
- consts.push(typedMatch[1]);
4751
- continue;
4752
- }
4753
- const bareMatch = constLine.match(/^(\w+)\s*$/);
4754
- if (bareMatch) {
4755
- consts.push(bareMatch[1]);
4756
- }
4757
- }
4758
- if (consts.length > 0) {
4759
- const name = typeName ?? consts[0];
4760
- records.push({
4761
- id: `extracted:enum-constants:${hash(filePath + ":" + name)}`,
4762
- extractor: "enum-constants",
4763
- language,
4764
- filePath,
4765
- line: i + 1,
4766
- nodeType: "business_term",
4767
- name,
4768
- content: `const ( ${consts.join(", ")} )`,
4769
- confidence: typeName ? 0.8 : 0.6,
4770
- metadata: { kind: typeName ? "typed-const" : "const-block", members: consts }
4771
- });
4772
- }
4835
+ const record = this.buildGoConstBlockRecord(lines, i, filePath, language);
4836
+ if (record) records.push(record);
4773
4837
  }
4774
4838
  }
4775
4839
  return records;
4776
4840
  }
4841
+ buildGoConstBlockRecord(lines, i, filePath, language) {
4842
+ const { consts, typeName } = this.collectGoConsts(lines, i + 1);
4843
+ if (consts.length === 0) return void 0;
4844
+ const name = typeName ?? consts[0];
4845
+ return {
4846
+ id: `extracted:enum-constants:${hash(filePath + ":" + name)}`,
4847
+ extractor: "enum-constants",
4848
+ language,
4849
+ filePath,
4850
+ line: i + 1,
4851
+ nodeType: "business_term",
4852
+ name,
4853
+ content: `const ( ${consts.join(", ")} )`,
4854
+ confidence: typeName ? 0.8 : 0.6,
4855
+ metadata: { kind: typeName ? "typed-const" : "const-block", members: consts }
4856
+ };
4857
+ }
4858
+ collectGoConsts(lines, startLine) {
4859
+ const consts = [];
4860
+ let typeName;
4861
+ for (let j = startLine; j < lines.length; j++) {
4862
+ const constLine = lines[j].trim();
4863
+ if (constLine === ")") break;
4864
+ if (constLine === "" || constLine.startsWith("//")) continue;
4865
+ const parsed = this.parseGoConstLine(constLine);
4866
+ if (!parsed) continue;
4867
+ typeName = this.resolveGoTypeName(typeName, parsed);
4868
+ consts.push(parsed.name);
4869
+ }
4870
+ return { consts, typeName };
4871
+ }
4872
+ parseGoConstLine(constLine) {
4873
+ const iotaMatch = constLine.match(GO_IOTA_RE);
4874
+ if (iotaMatch) return { name: iotaMatch[1], typeName: iotaMatch[2], overwriteType: true };
4875
+ const typedMatch = constLine.match(GO_TYPED_RE);
4876
+ if (typedMatch) return { name: typedMatch[1], typeName: typedMatch[2], overwriteType: false };
4877
+ const bareMatch = constLine.match(GO_BARE_RE);
4878
+ if (bareMatch) return { name: bareMatch[1] };
4879
+ return void 0;
4880
+ }
4881
+ resolveGoTypeName(current, parsed) {
4882
+ if (parsed.overwriteType) return parsed.typeName;
4883
+ if (parsed.typeName !== void 0 && current === void 0) return parsed.typeName;
4884
+ return current;
4885
+ }
4777
4886
  extractRust(content, filePath, language) {
4778
4887
  const records = [];
4779
4888
  const lines = content.split("\n");
4780
4889
  for (let i = 0; i < lines.length; i++) {
4781
4890
  const line = lines[i];
4782
- const enumMatch = line.match(/^(?:pub\s+)?enum\s+(\w+)/);
4891
+ const enumMatch = line.match(RUST_ENUM_RE);
4783
4892
  if (enumMatch) {
4784
4893
  const enumName = enumMatch[1];
4785
4894
  const members = this.collectRustEnumVariants(lines, i + 1);
@@ -4804,7 +4913,7 @@ var EnumConstantExtractor = class {
4804
4913
  const lines = content.split("\n");
4805
4914
  for (let i = 0; i < lines.length; i++) {
4806
4915
  const line = lines[i];
4807
- const enumMatch = line.match(/(?:public\s+|private\s+|protected\s+)?enum\s+(\w+)/);
4916
+ const enumMatch = line.match(JAVA_ENUM_RE);
4808
4917
  if (enumMatch) {
4809
4918
  const enumName = enumMatch[1];
4810
4919
  const members = this.collectJavaEnumConstants(lines, i + 1);
@@ -4852,10 +4961,7 @@ var EnumConstantExtractor = class {
4852
4961
  for (let i = startLine; i < lines.length; i++) {
4853
4962
  for (const ch of lines[i]) {
4854
4963
  if (ch === "{") depth++;
4855
- if (ch === "}") {
4856
- depth--;
4857
- if (depth === 0) return i;
4858
- }
4964
+ else if (ch === "}" && --depth === 0) return i;
4859
4965
  }
4860
4966
  }
4861
4967
  return lines.length - 1;
@@ -6216,34 +6322,42 @@ var KnowledgePipelineRunner = class {
6216
6322
  // src/ingest/connectors/ConnectorUtils.ts
6217
6323
  var CODE_NODE_TYPES5 = ["file", "function", "class", "method", "interface", "variable"];
6218
6324
  var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
6219
- function withRetry(client, options) {
6220
- const maxRetries = options?.maxRetries ?? 3;
6221
- const baseDelayMs = options?.baseDelayMs ?? 1e3;
6222
- const maxDelayMs = options?.maxDelayMs ?? 3e4;
6223
- return async (url, requestOptions) => {
6224
- let lastError;
6225
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
6226
- try {
6227
- const response = await client(url, requestOptions);
6228
- if (response.ok || !RETRYABLE_STATUSES.has(response.status ?? 0)) {
6229
- return response;
6230
- }
6231
- if (attempt === maxRetries) {
6232
- return response;
6233
- }
6234
- } catch (err) {
6235
- lastError = err instanceof Error ? err : new Error(String(err));
6236
- if (attempt === maxRetries) {
6237
- throw lastError;
6238
- }
6325
+ function computeBackoffDelay(config, attempt) {
6326
+ const exponentialDelay = config.baseDelayMs * 2 ** attempt;
6327
+ const cappedDelay = Math.min(exponentialDelay, config.maxDelayMs);
6328
+ return cappedDelay * (0.5 + Math.random() * 0.5);
6329
+ }
6330
+ function shouldReturnResponse(response, attempt, maxRetries) {
6331
+ if (response.ok || !RETRYABLE_STATUSES.has(response.status ?? 0)) {
6332
+ return true;
6333
+ }
6334
+ return attempt === maxRetries;
6335
+ }
6336
+ async function executeWithRetry(client, url, requestOptions, config) {
6337
+ let lastError;
6338
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
6339
+ try {
6340
+ const response = await client(url, requestOptions);
6341
+ if (shouldReturnResponse(response, attempt, config.maxRetries)) {
6342
+ return response;
6343
+ }
6344
+ } catch (err) {
6345
+ lastError = err instanceof Error ? err : new Error(String(err));
6346
+ if (attempt === config.maxRetries) {
6347
+ throw lastError;
6239
6348
  }
6240
- const exponentialDelay = baseDelayMs * 2 ** attempt;
6241
- const cappedDelay = Math.min(exponentialDelay, maxDelayMs);
6242
- const jitteredDelay = cappedDelay * (0.5 + Math.random() * 0.5);
6243
- await new Promise((resolve) => setTimeout(resolve, jitteredDelay));
6244
6349
  }
6245
- throw lastError ?? new Error("Request failed after retries");
6350
+ await new Promise((resolve) => setTimeout(resolve, computeBackoffDelay(config, attempt)));
6351
+ }
6352
+ throw lastError ?? new Error("Request failed after retries");
6353
+ }
6354
+ function withRetry(client, options) {
6355
+ const config = {
6356
+ maxRetries: options?.maxRetries ?? 3,
6357
+ baseDelayMs: options?.baseDelayMs ?? 1e3,
6358
+ maxDelayMs: options?.maxDelayMs ?? 3e4
6246
6359
  };
6360
+ return (url, requestOptions) => executeWithRetry(client, url, requestOptions, config);
6247
6361
  }
6248
6362
  var SANITIZE_RULES = [
6249
6363
  // Strip XML/HTML-like instruction tags that could be interpreted as system prompts