@harness-engineering/graph 0.11.2 → 0.11.4

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.js CHANGED
@@ -3972,29 +3972,41 @@ var KnowledgeLinker = class {
3972
3972
  // src/ingest/StructuralDriftDetector.ts
3973
3973
  var StructuralDriftDetector = class {
3974
3974
  detect(current, fresh) {
3975
- const findings = [];
3976
3975
  const currentById = new Map(current.entries.map((e) => [e.id, e]));
3977
3976
  const freshById = new Map(fresh.entries.map((e) => [e.id, e]));
3977
+ const findings = [
3978
+ ...this.findNew(currentById, freshById),
3979
+ ...this.findStale(currentById, freshById),
3980
+ ...this.findDrifted(currentById, freshById)
3981
+ ];
3982
+ findings.push(...this.findContradicting(fresh.entries, findings));
3983
+ const totalEntries = (/* @__PURE__ */ new Set([...currentById.keys(), ...freshById.keys()])).size;
3984
+ const driftScore = totalEntries > 0 ? findings.length / totalEntries : 0;
3985
+ return { findings, driftScore, summary: this.summarize(findings) };
3986
+ }
3987
+ /** 1. NEW: entries in fresh but not in current */
3988
+ findNew(currentById, freshById) {
3989
+ const findings = [];
3978
3990
  for (const [id, entry] of freshById) {
3979
3991
  if (!currentById.has(id)) {
3980
- findings.push({
3981
- entryId: id,
3982
- classification: "new",
3983
- fresh: entry,
3984
- severity: "low"
3985
- });
3992
+ findings.push({ entryId: id, classification: "new", fresh: entry, severity: "low" });
3986
3993
  }
3987
3994
  }
3995
+ return findings;
3996
+ }
3997
+ /** 2. STALE: entries in current but not in fresh */
3998
+ findStale(currentById, freshById) {
3999
+ const findings = [];
3988
4000
  for (const [id, entry] of currentById) {
3989
4001
  if (!freshById.has(id)) {
3990
- findings.push({
3991
- entryId: id,
3992
- classification: "stale",
3993
- current: entry,
3994
- severity: "high"
3995
- });
4002
+ findings.push({ entryId: id, classification: "stale", current: entry, severity: "high" });
3996
4003
  }
3997
4004
  }
4005
+ return findings;
4006
+ }
4007
+ /** 3. DRIFTED: entries in both but contentHash differs */
4008
+ findDrifted(currentById, freshById) {
4009
+ const findings = [];
3998
4010
  for (const [id, freshEntry] of freshById) {
3999
4011
  const currentEntry = currentById.get(id);
4000
4012
  if (currentEntry && currentEntry.contentHash !== freshEntry.contentHash) {
@@ -4007,43 +4019,56 @@ var StructuralDriftDetector = class {
4007
4019
  });
4008
4020
  }
4009
4021
  }
4022
+ return findings;
4023
+ }
4024
+ /** 4. CONTRADICTING: same entity name from different sources with different content */
4025
+ findContradicting(entries, existing) {
4026
+ const result = [];
4027
+ const alreadyContradicting = new Set(
4028
+ existing.filter((f) => f.classification === "contradicting").map((f) => f.entryId)
4029
+ );
4030
+ for (const [, group] of this.groupByName(entries)) {
4031
+ const finding = this.contradictionForGroup(group, alreadyContradicting);
4032
+ if (finding) {
4033
+ result.push(finding);
4034
+ alreadyContradicting.add(finding.entryId);
4035
+ }
4036
+ }
4037
+ return result;
4038
+ }
4039
+ groupByName(entries) {
4010
4040
  const byName = /* @__PURE__ */ new Map();
4011
- for (const entry of fresh.entries) {
4041
+ for (const entry of entries) {
4012
4042
  const group = byName.get(entry.name) ?? [];
4013
4043
  group.push(entry);
4014
4044
  byName.set(entry.name, group);
4015
4045
  }
4016
- for (const [, group] of byName) {
4017
- if (group.length > 1) {
4018
- const sources = new Set(group.map((e) => e.source));
4019
- const hashes = new Set(group.map((e) => e.contentHash));
4020
- if (sources.size > 1 && hashes.size > 1) {
4021
- const alreadyContradicting = new Set(
4022
- findings.filter((f) => f.classification === "contradicting").map((f) => f.entryId)
4023
- );
4024
- for (const entry of group) {
4025
- if (!alreadyContradicting.has(entry.id)) {
4026
- findings.push({
4027
- entryId: entry.id,
4028
- classification: "contradicting",
4029
- fresh: entry,
4030
- severity: "critical"
4031
- });
4032
- break;
4033
- }
4034
- }
4035
- }
4046
+ return byName;
4047
+ }
4048
+ contradictionForGroup(group, alreadyContradicting) {
4049
+ if (group.length <= 1) return void 0;
4050
+ const sources = new Set(group.map((e) => e.source));
4051
+ const hashes = new Set(group.map((e) => e.contentHash));
4052
+ if (sources.size <= 1 || hashes.size <= 1) return void 0;
4053
+ for (const entry of group) {
4054
+ if (!alreadyContradicting.has(entry.id)) {
4055
+ return {
4056
+ entryId: entry.id,
4057
+ classification: "contradicting",
4058
+ fresh: entry,
4059
+ severity: "critical"
4060
+ };
4036
4061
  }
4037
4062
  }
4038
- const totalEntries = (/* @__PURE__ */ new Set([...currentById.keys(), ...freshById.keys()])).size;
4039
- const driftScore = totalEntries > 0 ? findings.length / totalEntries : 0;
4040
- const summary = {
4063
+ return void 0;
4064
+ }
4065
+ summarize(findings) {
4066
+ return {
4041
4067
  new: findings.filter((f) => f.classification === "new").length,
4042
4068
  drifted: findings.filter((f) => f.classification === "drifted").length,
4043
4069
  stale: findings.filter((f) => f.classification === "stale").length,
4044
4070
  contradicting: findings.filter((f) => f.classification === "contradicting").length
4045
4071
  };
4046
- return { findings, driftScore, summary };
4047
4072
  }
4048
4073
  };
4049
4074
 
@@ -4092,6 +4117,29 @@ var KnowledgeStagingAggregator = class {
4092
4117
  return titleMatch ? titleMatch[1].trim() : path9.basename(filePath, ".md");
4093
4118
  }
4094
4119
  async generateGapReport(knowledgeDir, store) {
4120
+ const { domainDocNames, domainEntryCounts, totalEntries } = await this.collectDocumentedEntries(
4121
+ knowledgeDir,
4122
+ store
4123
+ );
4124
+ if (!store) {
4125
+ return this.buildEntryOnlyReport(domainEntryCounts, totalEntries);
4126
+ }
4127
+ const extractedByDomain = this.collectExtractedByDomain(store);
4128
+ const { domains, totalExtracted, totalGaps } = this.buildDomainCoverage(
4129
+ domainEntryCounts,
4130
+ extractedByDomain,
4131
+ domainDocNames
4132
+ );
4133
+ return {
4134
+ domains,
4135
+ totalEntries,
4136
+ totalExtracted,
4137
+ totalGaps,
4138
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
4139
+ };
4140
+ }
4141
+ /** Step 1-2: count `.md` entries per domain, capturing normalized names when a store is present. */
4142
+ async collectDocumentedEntries(knowledgeDir, store) {
4095
4143
  const domainDocNames = /* @__PURE__ */ new Map();
4096
4144
  const domainEntryCounts = /* @__PURE__ */ new Map();
4097
4145
  let totalEntries = 0;
@@ -4102,33 +4150,41 @@ var KnowledgeStagingAggregator = class {
4102
4150
  const domainPath = path9.join(knowledgeDir, dir.name);
4103
4151
  const files = await fs8.readdir(domainPath);
4104
4152
  const mdFiles = files.filter((f) => f.endsWith(".md"));
4105
- const entryCount = mdFiles.length;
4106
- totalEntries += entryCount;
4107
- domainEntryCounts.set(dir.name, entryCount);
4153
+ totalEntries += mdFiles.length;
4154
+ domainEntryCounts.set(dir.name, mdFiles.length);
4108
4155
  if (store) {
4109
- const names = [];
4110
- for (const file of mdFiles) {
4111
- const name = await this.extractDocName(path9.join(domainPath, file));
4112
- names.push(name.toLowerCase().trim());
4113
- }
4114
- domainDocNames.set(dir.name, names);
4156
+ domainDocNames.set(dir.name, await this.extractDocNames(domainPath, mdFiles));
4115
4157
  }
4116
4158
  }
4117
4159
  } catch {
4118
4160
  }
4119
- if (!store) {
4120
- const domains2 = [];
4121
- for (const [domain, entryCount] of domainEntryCounts) {
4122
- domains2.push({ domain, entryCount, extractedCount: 0, gapCount: 0, gapEntries: [] });
4123
- }
4124
- return {
4125
- domains: domains2,
4126
- totalEntries,
4127
- totalExtracted: 0,
4128
- totalGaps: 0,
4129
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
4130
- };
4161
+ return { domainDocNames, domainEntryCounts, totalEntries };
4162
+ }
4163
+ /** Extract and normalize the documented title from each markdown file in a domain. */
4164
+ async extractDocNames(domainPath, mdFiles) {
4165
+ const names = [];
4166
+ for (const file of mdFiles) {
4167
+ const name = await this.extractDocName(path9.join(domainPath, file));
4168
+ names.push(name.toLowerCase().trim());
4131
4169
  }
4170
+ return names;
4171
+ }
4172
+ /** Step 3: backward-compatible report when no store is available. */
4173
+ buildEntryOnlyReport(domainEntryCounts, totalEntries) {
4174
+ const domains = [];
4175
+ for (const [domain, entryCount] of domainEntryCounts) {
4176
+ domains.push({ domain, entryCount, extractedCount: 0, gapCount: 0, gapEntries: [] });
4177
+ }
4178
+ return {
4179
+ domains,
4180
+ totalEntries,
4181
+ totalExtracted: 0,
4182
+ totalGaps: 0,
4183
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
4184
+ };
4185
+ }
4186
+ /** Step 4: query store for all business nodes, group by domain, deduplicate by name. */
4187
+ collectExtractedByDomain(store) {
4132
4188
  const extractedByDomain = /* @__PURE__ */ new Map();
4133
4189
  for (const nodeType of BUSINESS_NODE_TYPES) {
4134
4190
  const nodes = store.findNodes({ type: nodeType });
@@ -4140,15 +4196,22 @@ var KnowledgeStagingAggregator = class {
4140
4196
  }
4141
4197
  }
4142
4198
  for (const [domain, nodes] of extractedByDomain) {
4143
- const seen = /* @__PURE__ */ new Set();
4144
- const deduped = nodes.filter((n) => {
4145
- const key = n.name.toLowerCase().trim();
4146
- if (seen.has(key)) return false;
4147
- seen.add(key);
4148
- return true;
4149
- });
4150
- extractedByDomain.set(domain, deduped);
4199
+ extractedByDomain.set(domain, this.dedupeByName(nodes));
4151
4200
  }
4201
+ return extractedByDomain;
4202
+ }
4203
+ /** Keep the first occurrence of each normalized name, preserving order. */
4204
+ dedupeByName(nodes) {
4205
+ const seen = /* @__PURE__ */ new Set();
4206
+ return nodes.filter((n) => {
4207
+ const key = n.name.toLowerCase().trim();
4208
+ if (seen.has(key)) return false;
4209
+ seen.add(key);
4210
+ return true;
4211
+ });
4212
+ }
4213
+ /** Step 5: build per-domain coverage with gap analysis and roll up totals. */
4214
+ buildDomainCoverage(domainEntryCounts, extractedByDomain, domainDocNames) {
4152
4215
  const allDomains = /* @__PURE__ */ new Set([...domainEntryCounts.keys(), ...extractedByDomain.keys()]);
4153
4216
  const domains = [];
4154
4217
  let totalExtracted = 0;
@@ -4159,29 +4222,28 @@ var KnowledgeStagingAggregator = class {
4159
4222
  const extractedCount = extractedNodes.length;
4160
4223
  totalExtracted += extractedCount;
4161
4224
  const docNames = domainDocNames.get(domain) ?? [];
4162
- const gapEntries = [];
4163
- for (const node of extractedNodes) {
4164
- const normalizedName = node.name.toLowerCase().trim();
4165
- if (!docNames.includes(normalizedName)) {
4166
- gapEntries.push({
4167
- nodeId: node.id,
4168
- name: node.name,
4169
- nodeType: node.type,
4170
- source: node.metadata?.source ?? "unknown",
4171
- hasContent: Boolean(node.content && node.content.trim().length >= 10)
4172
- });
4173
- }
4174
- }
4225
+ const gapEntries = this.computeGapEntries(extractedNodes, docNames);
4175
4226
  totalGaps += gapEntries.length;
4176
4227
  domains.push({ domain, entryCount, extractedCount, gapCount: gapEntries.length, gapEntries });
4177
4228
  }
4178
- return {
4179
- domains,
4180
- totalEntries,
4181
- totalExtracted,
4182
- totalGaps,
4183
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
4184
- };
4229
+ return { domains, totalExtracted, totalGaps };
4230
+ }
4231
+ /** Collect extracted nodes that have no matching documented name. */
4232
+ computeGapEntries(extractedNodes, docNames) {
4233
+ const gapEntries = [];
4234
+ for (const node of extractedNodes) {
4235
+ const normalizedName = node.name.toLowerCase().trim();
4236
+ if (!docNames.includes(normalizedName)) {
4237
+ gapEntries.push({
4238
+ nodeId: node.id,
4239
+ name: node.name,
4240
+ nodeType: node.type,
4241
+ source: node.metadata?.source ?? "unknown",
4242
+ hasContent: Boolean(node.content && node.content.trim().length >= 10)
4243
+ });
4244
+ }
4245
+ }
4246
+ return gapEntries;
4185
4247
  }
4186
4248
  async writeGapReport(report) {
4187
4249
  const gapsDir = path9.join(this.projectDir, ".harness", "knowledge");
@@ -4428,6 +4490,9 @@ var PATTERNS = {
4428
4490
  /@Test\s*\n\s*(?:public\s+|private\s+|protected\s+)?(?:static\s+)?(?:void|[\w<>]+)\s+(\w+)\s*\(/g
4429
4491
  ]
4430
4492
  };
4493
+ var RUST_FN_RE = /(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/;
4494
+ var JAVA_DISPLAY_RE = /@DisplayName\s*\(\s*"([^"]+)"\s*\)/;
4495
+ var JAVA_METHOD_RE = /^\s*(?:(?:public|private|protected)\s+)?(?:static\s+)?(?:void|[\w<>[\]]+)\s+(\w+)\s*\(/;
4431
4496
  var TestDescriptionExtractor = class {
4432
4497
  name = "test-descriptions";
4433
4498
  supportedExtensions = [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs", ".java"];
@@ -4457,83 +4522,87 @@ var TestDescriptionExtractor = class {
4457
4522
  const records = [];
4458
4523
  const describeStack = [];
4459
4524
  for (let i = 0; i < lines.length; i++) {
4460
- const line = lines[i];
4461
- const describeMatch = line.match(/describe\s*\(\s*(['"`])((?:(?!\1).)*)\1/);
4462
- if (describeMatch) {
4463
- describeStack.push(describeMatch[2]);
4464
- }
4465
- const itMatch = line.match(/(?:it|test)\s*\(\s*(['"`])((?:(?!\1).)*)\1/);
4466
- if (itMatch) {
4467
- const testName = itMatch[2];
4468
- const fullPath = [...describeStack, testName].join(" > ");
4469
- const patternKey = fullPath;
4470
- records.push({
4471
- id: `extracted:test-descriptions:${hash(filePath + ":" + patternKey)}`,
4472
- extractor: "test-descriptions",
4473
- language,
4474
- filePath,
4475
- line: i + 1,
4476
- nodeType: "business_rule",
4477
- name: testName,
4478
- content: fullPath,
4479
- confidence: 0.7,
4480
- metadata: {
4481
- suite: describeStack.length > 0 ? describeStack[describeStack.length - 1] : void 0,
4482
- framework: "vitest"
4483
- }
4484
- });
4485
- }
4486
- if (line.match(/^\s*\}\s*\)\s*;?\s*$/) && describeStack.length > 0) {
4487
- describeStack.pop();
4488
- }
4525
+ this.scanJsTsLine(lines[i], i, language, filePath, describeStack, records);
4489
4526
  }
4490
4527
  return records;
4491
4528
  }
4529
+ scanJsTsLine(line, index, language, filePath, describeStack, records) {
4530
+ const describeMatch = line.match(/describe\s*\(\s*(['"`])((?:(?!\1).)*)\1/);
4531
+ if (describeMatch) describeStack.push(describeMatch[2]);
4532
+ const itMatch = line.match(/(?:it|test)\s*\(\s*(['"`])((?:(?!\1).)*)\1/);
4533
+ if (itMatch) {
4534
+ records.push(this.buildJsTsRecord(itMatch[2], index, language, filePath, describeStack));
4535
+ }
4536
+ if (/^\s*\}\s*\)\s*;?\s*$/.test(line) && describeStack.length > 0) describeStack.pop();
4537
+ }
4538
+ buildJsTsRecord(testName, index, language, filePath, describeStack) {
4539
+ const fullPath = [...describeStack, testName].join(" > ");
4540
+ const patternKey = fullPath;
4541
+ return {
4542
+ id: `extracted:test-descriptions:${hash(filePath + ":" + patternKey)}`,
4543
+ extractor: "test-descriptions",
4544
+ language,
4545
+ filePath,
4546
+ line: index + 1,
4547
+ nodeType: "business_rule",
4548
+ name: testName,
4549
+ content: fullPath,
4550
+ confidence: 0.7,
4551
+ metadata: {
4552
+ suite: describeStack.length > 0 ? describeStack[describeStack.length - 1] : void 0,
4553
+ framework: "vitest"
4554
+ }
4555
+ };
4556
+ }
4492
4557
  extractPython(_content, filePath, language, lines) {
4493
4558
  const records = [];
4494
4559
  let currentClass;
4495
4560
  for (let i = 0; i < lines.length; i++) {
4496
4561
  const line = lines[i];
4497
4562
  const classMatch = line.match(/^class\s+(Test\w+)/);
4498
- if (classMatch) {
4499
- currentClass = classMatch[1];
4500
- }
4563
+ if (classMatch) currentClass = classMatch[1];
4501
4564
  const funcMatch = line.match(/^\s*def\s+(test_\w+)\s*\(/);
4502
4565
  if (funcMatch) {
4503
- const testName = funcMatch[1];
4504
- const humanName = testName.replace(/^test_/, "").replace(/_/g, " ");
4505
- let docstring;
4506
- if (i + 1 < lines.length) {
4507
- const nextLine = lines[i + 1].trim();
4508
- const docMatch = nextLine.match(/^"""(.+?)"""/);
4509
- if (docMatch) {
4510
- docstring = docMatch[1];
4511
- }
4512
- }
4513
- const patternKey = currentClass ? `${currentClass}.${testName}` : testName;
4514
- records.push({
4515
- id: `extracted:test-descriptions:${hash(filePath + ":" + patternKey)}`,
4516
- extractor: "test-descriptions",
4517
- language,
4518
- filePath,
4519
- line: i + 1,
4520
- nodeType: "business_rule",
4521
- name: docstring ?? humanName,
4522
- content: patternKey,
4523
- confidence: docstring ? 0.7 : 0.5,
4524
- metadata: {
4525
- suite: currentClass,
4526
- framework: "pytest",
4527
- functionName: testName
4528
- }
4529
- });
4530
- }
4531
- if (currentClass && /^\S/.test(line) && !line.startsWith("class ") && !line.startsWith("#") && line.trim() !== "") {
4532
- currentClass = void 0;
4566
+ records.push(
4567
+ this.buildPythonRecord(funcMatch[1], i, language, filePath, currentClass, lines)
4568
+ );
4533
4569
  }
4570
+ if (this.isPythonClassReset(line, currentClass)) currentClass = void 0;
4534
4571
  }
4535
4572
  return records;
4536
4573
  }
4574
+ isPythonClassReset(line, currentClass) {
4575
+ return Boolean(
4576
+ currentClass && /^\S/.test(line) && !line.startsWith("class ") && !line.startsWith("#") && line.trim() !== ""
4577
+ );
4578
+ }
4579
+ findPythonDocstring(lines, index) {
4580
+ if (index + 1 >= lines.length) return void 0;
4581
+ const nextLine = lines[index + 1].trim();
4582
+ const docMatch = nextLine.match(/^"""(.+?)"""/);
4583
+ return docMatch ? docMatch[1] : void 0;
4584
+ }
4585
+ buildPythonRecord(testName, index, language, filePath, currentClass, lines) {
4586
+ const humanName = testName.replace(/^test_/, "").replace(/_/g, " ");
4587
+ const docstring = this.findPythonDocstring(lines, index);
4588
+ const patternKey = currentClass ? `${currentClass}.${testName}` : testName;
4589
+ return {
4590
+ id: `extracted:test-descriptions:${hash(filePath + ":" + patternKey)}`,
4591
+ extractor: "test-descriptions",
4592
+ language,
4593
+ filePath,
4594
+ line: index + 1,
4595
+ nodeType: "business_rule",
4596
+ name: docstring ?? humanName,
4597
+ content: patternKey,
4598
+ confidence: docstring ? 0.7 : 0.5,
4599
+ metadata: {
4600
+ suite: currentClass,
4601
+ framework: "pytest",
4602
+ functionName: testName
4603
+ }
4604
+ };
4605
+ }
4537
4606
  extractGo(_content, filePath, language, lines) {
4538
4607
  const records = [];
4539
4608
  let currentTest;
@@ -4582,89 +4651,107 @@ var TestDescriptionExtractor = class {
4582
4651
  extractRust(_content, filePath, language, lines) {
4583
4652
  const records = [];
4584
4653
  for (let i = 0; i < lines.length; i++) {
4585
- const line = lines[i];
4586
- if (line.trim() === "#[test]") {
4587
- for (let j = i + 1; j < lines.length && j <= i + 3; j++) {
4588
- const fnLine = lines[j];
4589
- const fnMatch = fnLine.match(/(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/);
4590
- if (fnMatch) {
4591
- const testName = fnMatch[1];
4592
- const humanName = testName.replace(/^test_/, "").replace(/_/g, " ");
4593
- let docComment;
4594
- if (i > 0) {
4595
- const prevLine = lines[i - 1].trim();
4596
- const docMatch = prevLine.match(/^\/\/\/\s*(.+)/);
4597
- if (docMatch) {
4598
- docComment = docMatch[1];
4599
- }
4600
- }
4601
- records.push({
4602
- id: `extracted:test-descriptions:${hash(filePath + ":" + testName)}`,
4603
- extractor: "test-descriptions",
4604
- language,
4605
- filePath,
4606
- line: j + 1,
4607
- nodeType: "business_rule",
4608
- name: docComment ?? humanName,
4609
- content: testName,
4610
- confidence: docComment ? 0.7 : 0.5,
4611
- metadata: { framework: "rust-test" }
4612
- });
4613
- break;
4614
- }
4615
- }
4616
- }
4654
+ if (lines[i].trim() !== "#[test]") continue;
4655
+ const record = this.buildRustRecord(lines, i, language, filePath);
4656
+ if (record) records.push(record);
4617
4657
  }
4618
4658
  return records;
4619
4659
  }
4660
+ buildRustRecord(lines, testIdx, language, filePath) {
4661
+ for (let j = testIdx + 1; j < lines.length && j <= testIdx + 3; j++) {
4662
+ const fnMatch = lines[j].match(RUST_FN_RE);
4663
+ if (!fnMatch) continue;
4664
+ const testName = fnMatch[1];
4665
+ const humanName = testName.replace(/^test_/, "").replace(/_/g, " ");
4666
+ const docComment = this.findRustDocComment(lines, testIdx);
4667
+ return {
4668
+ id: `extracted:test-descriptions:${hash(filePath + ":" + testName)}`,
4669
+ extractor: "test-descriptions",
4670
+ language,
4671
+ filePath,
4672
+ line: j + 1,
4673
+ nodeType: "business_rule",
4674
+ name: docComment ?? humanName,
4675
+ content: testName,
4676
+ confidence: docComment ? 0.7 : 0.5,
4677
+ metadata: { framework: "rust-test" }
4678
+ };
4679
+ }
4680
+ return void 0;
4681
+ }
4682
+ findRustDocComment(lines, testIdx) {
4683
+ if (testIdx <= 0) return void 0;
4684
+ const prevLine = lines[testIdx - 1].trim();
4685
+ const docMatch = prevLine.match(/^\/\/\/\s*(.+)/);
4686
+ return docMatch ? docMatch[1] : void 0;
4687
+ }
4620
4688
  extractJava(_content, filePath, language, lines) {
4621
4689
  const records = [];
4622
4690
  for (let i = 0; i < lines.length; i++) {
4623
- const line = lines[i];
4624
- const testMatch = line.match(/@Test\s*$/);
4625
- if (testMatch) {
4626
- let displayName;
4627
- for (let k = Math.max(0, i - 3); k < i; k++) {
4628
- const prevLine = lines[k];
4629
- const dm = prevLine.match(/@DisplayName\s*\(\s*"([^"]+)"\s*\)/);
4630
- if (dm) displayName = dm[1];
4631
- }
4632
- for (let j = i + 1; j < lines.length && j <= i + 5; j++) {
4633
- const scanLine = lines[j];
4634
- const adjacentDisplay = scanLine.match(/@DisplayName\s*\(\s*"([^"]+)"\s*\)/);
4635
- if (adjacentDisplay) {
4636
- displayName = adjacentDisplay[1];
4637
- continue;
4638
- }
4639
- const methodMatch = scanLine.match(
4640
- /^\s*(?:(?:public|private|protected)\s+)?(?:static\s+)?(?:void|[\w<>[\]]+)\s+(\w+)\s*\(/
4641
- );
4642
- if (methodMatch) {
4643
- const methodName = methodMatch[1];
4644
- const name = displayName ?? methodName;
4645
- const patternKey = displayName ?? methodName;
4646
- records.push({
4647
- id: `extracted:test-descriptions:${hash(filePath + ":" + patternKey)}`,
4648
- extractor: "test-descriptions",
4649
- language,
4650
- filePath,
4651
- line: j + 1,
4652
- nodeType: "business_rule",
4653
- name,
4654
- content: patternKey,
4655
- confidence: displayName ? 0.7 : 0.5,
4656
- metadata: { framework: "junit5" }
4657
- });
4658
- break;
4659
- }
4660
- }
4661
- }
4691
+ if (!/@Test\s*$/.test(lines[i])) continue;
4692
+ const record = this.buildJavaRecord(lines, i, language, filePath);
4693
+ if (record) records.push(record);
4662
4694
  }
4663
4695
  return records;
4664
4696
  }
4697
+ buildJavaRecord(lines, testIdx, language, filePath) {
4698
+ let displayName = this.findJavaDisplayNameBefore(lines, testIdx);
4699
+ for (let j = testIdx + 1; j < lines.length && j <= testIdx + 5; j++) {
4700
+ const scanLine = lines[j];
4701
+ const adjacentDisplay = scanLine.match(JAVA_DISPLAY_RE);
4702
+ if (adjacentDisplay) {
4703
+ displayName = adjacentDisplay[1];
4704
+ continue;
4705
+ }
4706
+ const methodMatch = scanLine.match(JAVA_METHOD_RE);
4707
+ if (methodMatch) {
4708
+ return this.buildJavaRecordFromMethod(methodMatch[1], displayName, j, language, filePath);
4709
+ }
4710
+ }
4711
+ return void 0;
4712
+ }
4713
+ findJavaDisplayNameBefore(lines, testIdx) {
4714
+ let displayName;
4715
+ for (let k = Math.max(0, testIdx - 3); k < testIdx; k++) {
4716
+ const dm = lines[k].match(JAVA_DISPLAY_RE);
4717
+ if (dm) displayName = dm[1];
4718
+ }
4719
+ return displayName;
4720
+ }
4721
+ buildJavaRecordFromMethod(methodName, displayName, index, language, filePath) {
4722
+ const name = displayName ?? methodName;
4723
+ const patternKey = displayName ?? methodName;
4724
+ return {
4725
+ id: `extracted:test-descriptions:${hash(filePath + ":" + patternKey)}`,
4726
+ extractor: "test-descriptions",
4727
+ language,
4728
+ filePath,
4729
+ line: index + 1,
4730
+ nodeType: "business_rule",
4731
+ name,
4732
+ content: patternKey,
4733
+ confidence: displayName ? 0.7 : 0.5,
4734
+ metadata: { framework: "junit5" }
4735
+ };
4736
+ }
4665
4737
  };
4666
4738
 
4667
4739
  // src/ingest/extractors/EnumConstantExtractor.ts
4740
+ var TS_ENUM_RE = /(?:export\s+)?enum\s+(\w+)/;
4741
+ var TS_AS_CONST_RE = /(?:export\s+)?const\s+(\w+)\s*=\s*\{/;
4742
+ var TS_UNION_RE = /(?:export\s+)?type\s+(\w+)\s*=\s*(['"`][\w]+['"`](?:\s*\|\s*['"`][\w]+['"`])+)/;
4743
+ var JS_FREEZE_RE = /(?:export\s+)?const\s+(\w+)\s*=\s*Object\.freeze\s*\(\s*\{/;
4744
+ var JS_CONST_OBJECT_RE = /(?:export\s+)?const\s+([A-Z][A-Z_\d]*)\s*=\s*\{/;
4745
+ var JS_UPPER_KEY_RE = /^[A-Z][A-Z_\d]*$/;
4746
+ var PY_ENUM_RE = /^class\s+(\w+)\s*\(\s*(?:str\s*,\s*)?(?:Enum|StrEnum|IntEnum|Flag|IntFlag)\s*\)/;
4747
+ var PY_LITERAL_RE = /(\w+)\s*=\s*Literal\s*\[([^\]]+)\]/;
4748
+ var GO_TYPE_RE = /^type\s+(\w+)\s+(?:int|string|uint|int32|int64|uint32|uint64)/;
4749
+ var GO_CONST_BLOCK_RE = /^const\s*\(/;
4750
+ var GO_IOTA_RE = /^(\w+)\s+(\w+)\s*=\s*iota/;
4751
+ var GO_TYPED_RE = /^(\w+)\s+(\w+)\s*=\s*"[^"]*"/;
4752
+ var GO_BARE_RE = /^(\w+)\s*$/;
4753
+ var RUST_ENUM_RE = /^(?:pub\s+)?enum\s+(\w+)/;
4754
+ var JAVA_ENUM_RE = /(?:public\s+|private\s+|protected\s+)?enum\s+(\w+)/;
4668
4755
  var EnumConstantExtractor = class {
4669
4756
  name = "enum-constants";
4670
4757
  supportedExtensions = [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs", ".java"];
@@ -4688,213 +4775,235 @@ var EnumConstantExtractor = class {
4688
4775
  const records = [];
4689
4776
  const lines = content.split("\n");
4690
4777
  for (let i = 0; i < lines.length; i++) {
4691
- const line = lines[i];
4692
- const enumMatch = line.match(/(?:export\s+)?enum\s+(\w+)/);
4693
- if (enumMatch) {
4694
- const enumName = enumMatch[1];
4695
- const members = this.collectEnumMembers(lines, i + 1);
4696
- records.push({
4697
- id: `extracted:enum-constants:${hash(filePath + ":" + enumName)}`,
4698
- extractor: "enum-constants",
4699
- language,
4700
- filePath,
4701
- line: i + 1,
4702
- nodeType: "business_term",
4703
- name: enumName,
4704
- content: `enum ${enumName} { ${members.join(", ")} }`,
4705
- confidence: 0.8,
4706
- metadata: { kind: "enum", members }
4707
- });
4708
- }
4709
- const constMatch = line.match(/(?:export\s+)?const\s+(\w+)\s*=\s*\{/);
4710
- if (constMatch && content.includes("as const")) {
4711
- const blockEnd = this.findClosingBrace(lines, i);
4712
- const block = lines.slice(i, blockEnd + 1).join("\n");
4713
- if (block.includes("as const")) {
4714
- const constName = constMatch[1];
4715
- const members = this.collectObjectKeys(lines, i + 1);
4716
- records.push({
4717
- id: `extracted:enum-constants:${hash(filePath + ":" + constName)}`,
4718
- extractor: "enum-constants",
4719
- language,
4720
- filePath,
4721
- line: i + 1,
4722
- nodeType: "business_term",
4723
- name: constName,
4724
- content: `const ${constName} = { ${members.join(", ")} } as const`,
4725
- confidence: 0.8,
4726
- metadata: { kind: "as-const", members }
4727
- });
4728
- }
4729
- }
4730
- const unionMatch = line.match(
4731
- /(?:export\s+)?type\s+(\w+)\s*=\s*(['"`][\w]+['"`](?:\s*\|\s*['"`][\w]+['"`])+)/
4732
- );
4733
- if (unionMatch) {
4734
- const typeName = unionMatch[1];
4735
- const values = unionMatch[2].split("|").map((v) => v.trim().replace(/['"`]/g, ""));
4736
- records.push({
4737
- id: `extracted:enum-constants:${hash(filePath + ":" + typeName)}`,
4738
- extractor: "enum-constants",
4739
- language,
4740
- filePath,
4741
- line: i + 1,
4742
- nodeType: "business_term",
4743
- name: typeName,
4744
- content: `type ${typeName} = ${values.map((v) => `'${v}'`).join(" | ")}`,
4745
- confidence: 0.7,
4746
- metadata: { kind: "union-type", members: values }
4747
- });
4748
- }
4778
+ const enumRecord = this.matchTsEnum(lines, i, filePath, language);
4779
+ if (enumRecord) records.push(enumRecord);
4780
+ const asConstRecord = this.matchTsAsConst(content, lines, i, filePath, language);
4781
+ if (asConstRecord) records.push(asConstRecord);
4782
+ const unionRecord = this.matchTsUnion(lines, i, filePath, language);
4783
+ if (unionRecord) records.push(unionRecord);
4749
4784
  }
4750
4785
  return records;
4751
4786
  }
4787
+ matchTsEnum(lines, i, filePath, language) {
4788
+ const enumMatch = lines[i].match(TS_ENUM_RE);
4789
+ if (!enumMatch) return void 0;
4790
+ const enumName = enumMatch[1];
4791
+ const members = this.collectEnumMembers(lines, i + 1);
4792
+ return {
4793
+ id: `extracted:enum-constants:${hash(filePath + ":" + enumName)}`,
4794
+ extractor: "enum-constants",
4795
+ language,
4796
+ filePath,
4797
+ line: i + 1,
4798
+ nodeType: "business_term",
4799
+ name: enumName,
4800
+ content: `enum ${enumName} { ${members.join(", ")} }`,
4801
+ confidence: 0.8,
4802
+ metadata: { kind: "enum", members }
4803
+ };
4804
+ }
4805
+ matchTsAsConst(content, lines, i, filePath, language) {
4806
+ const constMatch = lines[i].match(TS_AS_CONST_RE);
4807
+ if (!constMatch || !content.includes("as const")) return void 0;
4808
+ const blockEnd = this.findClosingBrace(lines, i);
4809
+ const block = lines.slice(i, blockEnd + 1).join("\n");
4810
+ if (!block.includes("as const")) return void 0;
4811
+ const constName = constMatch[1];
4812
+ const members = this.collectObjectKeys(lines, i + 1);
4813
+ return {
4814
+ id: `extracted:enum-constants:${hash(filePath + ":" + constName)}`,
4815
+ extractor: "enum-constants",
4816
+ language,
4817
+ filePath,
4818
+ line: i + 1,
4819
+ nodeType: "business_term",
4820
+ name: constName,
4821
+ content: `const ${constName} = { ${members.join(", ")} } as const`,
4822
+ confidence: 0.8,
4823
+ metadata: { kind: "as-const", members }
4824
+ };
4825
+ }
4826
+ matchTsUnion(lines, i, filePath, language) {
4827
+ const unionMatch = lines[i].match(TS_UNION_RE);
4828
+ if (!unionMatch) return void 0;
4829
+ const typeName = unionMatch[1];
4830
+ const values = unionMatch[2].split("|").map((v) => v.trim().replace(/['"`]/g, ""));
4831
+ return {
4832
+ id: `extracted:enum-constants:${hash(filePath + ":" + typeName)}`,
4833
+ extractor: "enum-constants",
4834
+ language,
4835
+ filePath,
4836
+ line: i + 1,
4837
+ nodeType: "business_term",
4838
+ name: typeName,
4839
+ content: `type ${typeName} = ${values.map((v) => `'${v}'`).join(" | ")}`,
4840
+ confidence: 0.7,
4841
+ metadata: { kind: "union-type", members: values }
4842
+ };
4843
+ }
4752
4844
  extractJavaScript(content, filePath, language) {
4753
4845
  const records = [];
4754
4846
  const lines = content.split("\n");
4755
4847
  for (let i = 0; i < lines.length; i++) {
4756
- const line = lines[i];
4757
- const freezeMatch = line.match(/(?:export\s+)?const\s+(\w+)\s*=\s*Object\.freeze\s*\(\s*\{/);
4848
+ const freezeMatch = lines[i].match(JS_FREEZE_RE);
4758
4849
  if (freezeMatch) {
4759
- const name = freezeMatch[1];
4760
- const members = this.collectObjectKeys(lines, i + 1);
4761
- records.push({
4762
- id: `extracted:enum-constants:${hash(filePath + ":" + name)}`,
4763
- extractor: "enum-constants",
4764
- language,
4765
- filePath,
4766
- line: i + 1,
4767
- nodeType: "business_term",
4768
- name,
4769
- content: `const ${name} = Object.freeze({ ${members.join(", ")} })`,
4770
- confidence: 0.8,
4771
- metadata: { kind: "frozen-object", members }
4772
- });
4850
+ records.push(this.buildJsFreezeRecord(freezeMatch[1], lines, i, filePath, language));
4773
4851
  }
4774
- const constMatch = line.match(/(?:export\s+)?const\s+([A-Z][A-Z_\d]*)\s*=\s*\{/);
4775
- if (constMatch && !freezeMatch) {
4776
- const name = constMatch[1];
4777
- const members = this.collectObjectKeys(lines, i + 1);
4778
- if (members.length > 0 && members.every((m) => /^[A-Z][A-Z_\d]*$/.test(m))) {
4779
- records.push({
4780
- id: `extracted:enum-constants:${hash(filePath + ":" + name)}`,
4781
- extractor: "enum-constants",
4782
- language,
4783
- filePath,
4784
- line: i + 1,
4785
- nodeType: "business_term",
4786
- name,
4787
- content: `const ${name} = { ${members.join(", ")} }`,
4788
- confidence: 0.6,
4789
- metadata: { kind: "const-object", members }
4790
- });
4791
- }
4852
+ if (!freezeMatch) {
4853
+ const constRecord = this.matchJsConstObject(lines, i, filePath, language);
4854
+ if (constRecord) records.push(constRecord);
4792
4855
  }
4793
4856
  }
4794
4857
  return records;
4795
4858
  }
4859
+ buildJsFreezeRecord(name, lines, i, filePath, language) {
4860
+ const members = this.collectObjectKeys(lines, i + 1);
4861
+ return {
4862
+ id: `extracted:enum-constants:${hash(filePath + ":" + name)}`,
4863
+ extractor: "enum-constants",
4864
+ language,
4865
+ filePath,
4866
+ line: i + 1,
4867
+ nodeType: "business_term",
4868
+ name,
4869
+ content: `const ${name} = Object.freeze({ ${members.join(", ")} })`,
4870
+ confidence: 0.8,
4871
+ metadata: { kind: "frozen-object", members }
4872
+ };
4873
+ }
4874
+ matchJsConstObject(lines, i, filePath, language) {
4875
+ const constMatch = lines[i].match(JS_CONST_OBJECT_RE);
4876
+ if (!constMatch) return void 0;
4877
+ const name = constMatch[1];
4878
+ const members = this.collectObjectKeys(lines, i + 1);
4879
+ if (members.length === 0 || !members.every((m) => JS_UPPER_KEY_RE.test(m))) return void 0;
4880
+ return {
4881
+ id: `extracted:enum-constants:${hash(filePath + ":" + name)}`,
4882
+ extractor: "enum-constants",
4883
+ language,
4884
+ filePath,
4885
+ line: i + 1,
4886
+ nodeType: "business_term",
4887
+ name,
4888
+ content: `const ${name} = { ${members.join(", ")} }`,
4889
+ confidence: 0.6,
4890
+ metadata: { kind: "const-object", members }
4891
+ };
4892
+ }
4796
4893
  extractPython(content, filePath, language) {
4797
4894
  const records = [];
4798
4895
  const lines = content.split("\n");
4799
4896
  for (let i = 0; i < lines.length; i++) {
4800
- const line = lines[i];
4801
- const enumMatch = line.match(
4802
- /^class\s+(\w+)\s*\(\s*(?:str\s*,\s*)?(?:Enum|StrEnum|IntEnum|Flag|IntFlag)\s*\)/
4803
- );
4804
- if (enumMatch) {
4805
- const enumName = enumMatch[1];
4806
- const members = this.collectPythonEnumMembers(lines, i + 1);
4807
- records.push({
4808
- id: `extracted:enum-constants:${hash(filePath + ":" + enumName)}`,
4809
- extractor: "enum-constants",
4810
- language,
4811
- filePath,
4812
- line: i + 1,
4813
- nodeType: "business_term",
4814
- name: enumName,
4815
- content: `class ${enumName}(Enum) { ${members.join(", ")} }`,
4816
- confidence: 0.8,
4817
- metadata: { kind: "enum", members }
4818
- });
4819
- }
4820
- const literalMatch = line.match(/(\w+)\s*=\s*Literal\s*\[([^\]]+)\]/);
4821
- if (literalMatch) {
4822
- const typeName = literalMatch[1];
4823
- const values = literalMatch[2].split(",").map((v) => v.trim().replace(/["']/g, ""));
4824
- records.push({
4825
- id: `extracted:enum-constants:${hash(filePath + ":" + typeName)}`,
4826
- extractor: "enum-constants",
4827
- language,
4828
- filePath,
4829
- line: i + 1,
4830
- nodeType: "business_term",
4831
- name: typeName,
4832
- content: `${typeName} = Literal[${values.map((v) => `"${v}"`).join(", ")}]`,
4833
- confidence: 0.7,
4834
- metadata: { kind: "literal-type", members: values }
4835
- });
4836
- }
4897
+ const enumRecord = this.matchPythonEnum(lines, i, filePath, language);
4898
+ if (enumRecord) records.push(enumRecord);
4899
+ const literalRecord = this.matchPythonLiteral(lines, i, filePath, language);
4900
+ if (literalRecord) records.push(literalRecord);
4837
4901
  }
4838
4902
  return records;
4839
4903
  }
4904
+ matchPythonEnum(lines, i, filePath, language) {
4905
+ const enumMatch = lines[i].match(PY_ENUM_RE);
4906
+ if (!enumMatch) return void 0;
4907
+ const enumName = enumMatch[1];
4908
+ const members = this.collectPythonEnumMembers(lines, i + 1);
4909
+ return {
4910
+ id: `extracted:enum-constants:${hash(filePath + ":" + enumName)}`,
4911
+ extractor: "enum-constants",
4912
+ language,
4913
+ filePath,
4914
+ line: i + 1,
4915
+ nodeType: "business_term",
4916
+ name: enumName,
4917
+ content: `class ${enumName}(Enum) { ${members.join(", ")} }`,
4918
+ confidence: 0.8,
4919
+ metadata: { kind: "enum", members }
4920
+ };
4921
+ }
4922
+ matchPythonLiteral(lines, i, filePath, language) {
4923
+ const literalMatch = lines[i].match(PY_LITERAL_RE);
4924
+ if (!literalMatch) return void 0;
4925
+ const typeName = literalMatch[1];
4926
+ const values = literalMatch[2].split(",").map((v) => v.trim().replace(/["']/g, ""));
4927
+ return {
4928
+ id: `extracted:enum-constants:${hash(filePath + ":" + typeName)}`,
4929
+ extractor: "enum-constants",
4930
+ language,
4931
+ filePath,
4932
+ line: i + 1,
4933
+ nodeType: "business_term",
4934
+ name: typeName,
4935
+ content: `${typeName} = Literal[${values.map((v) => `"${v}"`).join(", ")}]`,
4936
+ confidence: 0.7,
4937
+ metadata: { kind: "literal-type", members: values }
4938
+ };
4939
+ }
4840
4940
  extractGo(content, filePath, language) {
4841
4941
  const records = [];
4842
4942
  const lines = content.split("\n");
4843
4943
  for (let i = 0; i < lines.length; i++) {
4844
4944
  const line = lines[i];
4845
- const typeMatch = line.match(/^type\s+(\w+)\s+(?:int|string|uint|int32|int64|uint32|uint64)/);
4945
+ const typeMatch = line.match(GO_TYPE_RE);
4846
4946
  if (typeMatch) {
4847
4947
  }
4848
- const constBlockMatch = line.match(/^const\s*\(/);
4948
+ const constBlockMatch = line.match(GO_CONST_BLOCK_RE);
4849
4949
  if (constBlockMatch) {
4850
- const consts = [];
4851
- let typeName;
4852
- for (let j = i + 1; j < lines.length; j++) {
4853
- const constLine = lines[j].trim();
4854
- if (constLine === ")") break;
4855
- if (constLine === "" || constLine.startsWith("//")) continue;
4856
- const iotaMatch = constLine.match(/^(\w+)\s+(\w+)\s*=\s*iota/);
4857
- if (iotaMatch) {
4858
- typeName = iotaMatch[2];
4859
- consts.push(iotaMatch[1]);
4860
- continue;
4861
- }
4862
- const typedMatch = constLine.match(/^(\w+)\s+(\w+)\s*=\s*"[^"]*"/);
4863
- if (typedMatch) {
4864
- typeName = typeName ?? typedMatch[2];
4865
- consts.push(typedMatch[1]);
4866
- continue;
4867
- }
4868
- const bareMatch = constLine.match(/^(\w+)\s*$/);
4869
- if (bareMatch) {
4870
- consts.push(bareMatch[1]);
4871
- }
4872
- }
4873
- if (consts.length > 0) {
4874
- const name = typeName ?? consts[0];
4875
- records.push({
4876
- id: `extracted:enum-constants:${hash(filePath + ":" + name)}`,
4877
- extractor: "enum-constants",
4878
- language,
4879
- filePath,
4880
- line: i + 1,
4881
- nodeType: "business_term",
4882
- name,
4883
- content: `const ( ${consts.join(", ")} )`,
4884
- confidence: typeName ? 0.8 : 0.6,
4885
- metadata: { kind: typeName ? "typed-const" : "const-block", members: consts }
4886
- });
4887
- }
4950
+ const record = this.buildGoConstBlockRecord(lines, i, filePath, language);
4951
+ if (record) records.push(record);
4888
4952
  }
4889
4953
  }
4890
4954
  return records;
4891
4955
  }
4956
+ buildGoConstBlockRecord(lines, i, filePath, language) {
4957
+ const { consts, typeName } = this.collectGoConsts(lines, i + 1);
4958
+ if (consts.length === 0) return void 0;
4959
+ const name = typeName ?? consts[0];
4960
+ return {
4961
+ id: `extracted:enum-constants:${hash(filePath + ":" + name)}`,
4962
+ extractor: "enum-constants",
4963
+ language,
4964
+ filePath,
4965
+ line: i + 1,
4966
+ nodeType: "business_term",
4967
+ name,
4968
+ content: `const ( ${consts.join(", ")} )`,
4969
+ confidence: typeName ? 0.8 : 0.6,
4970
+ metadata: { kind: typeName ? "typed-const" : "const-block", members: consts }
4971
+ };
4972
+ }
4973
+ collectGoConsts(lines, startLine) {
4974
+ const consts = [];
4975
+ let typeName;
4976
+ for (let j = startLine; j < lines.length; j++) {
4977
+ const constLine = lines[j].trim();
4978
+ if (constLine === ")") break;
4979
+ if (constLine === "" || constLine.startsWith("//")) continue;
4980
+ const parsed = this.parseGoConstLine(constLine);
4981
+ if (!parsed) continue;
4982
+ typeName = this.resolveGoTypeName(typeName, parsed);
4983
+ consts.push(parsed.name);
4984
+ }
4985
+ return { consts, typeName };
4986
+ }
4987
+ parseGoConstLine(constLine) {
4988
+ const iotaMatch = constLine.match(GO_IOTA_RE);
4989
+ if (iotaMatch) return { name: iotaMatch[1], typeName: iotaMatch[2], overwriteType: true };
4990
+ const typedMatch = constLine.match(GO_TYPED_RE);
4991
+ if (typedMatch) return { name: typedMatch[1], typeName: typedMatch[2], overwriteType: false };
4992
+ const bareMatch = constLine.match(GO_BARE_RE);
4993
+ if (bareMatch) return { name: bareMatch[1] };
4994
+ return void 0;
4995
+ }
4996
+ resolveGoTypeName(current, parsed) {
4997
+ if (parsed.overwriteType) return parsed.typeName;
4998
+ if (parsed.typeName !== void 0 && current === void 0) return parsed.typeName;
4999
+ return current;
5000
+ }
4892
5001
  extractRust(content, filePath, language) {
4893
5002
  const records = [];
4894
5003
  const lines = content.split("\n");
4895
5004
  for (let i = 0; i < lines.length; i++) {
4896
5005
  const line = lines[i];
4897
- const enumMatch = line.match(/^(?:pub\s+)?enum\s+(\w+)/);
5006
+ const enumMatch = line.match(RUST_ENUM_RE);
4898
5007
  if (enumMatch) {
4899
5008
  const enumName = enumMatch[1];
4900
5009
  const members = this.collectRustEnumVariants(lines, i + 1);
@@ -4919,7 +5028,7 @@ var EnumConstantExtractor = class {
4919
5028
  const lines = content.split("\n");
4920
5029
  for (let i = 0; i < lines.length; i++) {
4921
5030
  const line = lines[i];
4922
- const enumMatch = line.match(/(?:public\s+|private\s+|protected\s+)?enum\s+(\w+)/);
5031
+ const enumMatch = line.match(JAVA_ENUM_RE);
4923
5032
  if (enumMatch) {
4924
5033
  const enumName = enumMatch[1];
4925
5034
  const members = this.collectJavaEnumConstants(lines, i + 1);
@@ -4967,10 +5076,7 @@ var EnumConstantExtractor = class {
4967
5076
  for (let i = startLine; i < lines.length; i++) {
4968
5077
  for (const ch of lines[i]) {
4969
5078
  if (ch === "{") depth++;
4970
- if (ch === "}") {
4971
- depth--;
4972
- if (depth === 0) return i;
4973
- }
5079
+ else if (ch === "}" && --depth === 0) return i;
4974
5080
  }
4975
5081
  }
4976
5082
  return lines.length - 1;
@@ -6331,34 +6437,42 @@ var KnowledgePipelineRunner = class {
6331
6437
  // src/ingest/connectors/ConnectorUtils.ts
6332
6438
  var CODE_NODE_TYPES5 = ["file", "function", "class", "method", "interface", "variable"];
6333
6439
  var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
6334
- function withRetry(client, options) {
6335
- const maxRetries = options?.maxRetries ?? 3;
6336
- const baseDelayMs = options?.baseDelayMs ?? 1e3;
6337
- const maxDelayMs = options?.maxDelayMs ?? 3e4;
6338
- return async (url, requestOptions) => {
6339
- let lastError;
6340
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
6341
- try {
6342
- const response = await client(url, requestOptions);
6343
- if (response.ok || !RETRYABLE_STATUSES.has(response.status ?? 0)) {
6344
- return response;
6345
- }
6346
- if (attempt === maxRetries) {
6347
- return response;
6348
- }
6349
- } catch (err) {
6350
- lastError = err instanceof Error ? err : new Error(String(err));
6351
- if (attempt === maxRetries) {
6352
- throw lastError;
6353
- }
6440
+ function computeBackoffDelay(config, attempt) {
6441
+ const exponentialDelay = config.baseDelayMs * 2 ** attempt;
6442
+ const cappedDelay = Math.min(exponentialDelay, config.maxDelayMs);
6443
+ return cappedDelay * (0.5 + Math.random() * 0.5);
6444
+ }
6445
+ function shouldReturnResponse(response, attempt, maxRetries) {
6446
+ if (response.ok || !RETRYABLE_STATUSES.has(response.status ?? 0)) {
6447
+ return true;
6448
+ }
6449
+ return attempt === maxRetries;
6450
+ }
6451
+ async function executeWithRetry(client, url, requestOptions, config) {
6452
+ let lastError;
6453
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
6454
+ try {
6455
+ const response = await client(url, requestOptions);
6456
+ if (shouldReturnResponse(response, attempt, config.maxRetries)) {
6457
+ return response;
6458
+ }
6459
+ } catch (err) {
6460
+ lastError = err instanceof Error ? err : new Error(String(err));
6461
+ if (attempt === config.maxRetries) {
6462
+ throw lastError;
6354
6463
  }
6355
- const exponentialDelay = baseDelayMs * 2 ** attempt;
6356
- const cappedDelay = Math.min(exponentialDelay, maxDelayMs);
6357
- const jitteredDelay = cappedDelay * (0.5 + Math.random() * 0.5);
6358
- await new Promise((resolve) => setTimeout(resolve, jitteredDelay));
6359
6464
  }
6360
- throw lastError ?? new Error("Request failed after retries");
6465
+ await new Promise((resolve) => setTimeout(resolve, computeBackoffDelay(config, attempt)));
6466
+ }
6467
+ throw lastError ?? new Error("Request failed after retries");
6468
+ }
6469
+ function withRetry(client, options) {
6470
+ const config = {
6471
+ maxRetries: options?.maxRetries ?? 3,
6472
+ baseDelayMs: options?.baseDelayMs ?? 1e3,
6473
+ maxDelayMs: options?.maxDelayMs ?? 3e4
6361
6474
  };
6475
+ return (url, requestOptions) => executeWithRetry(client, url, requestOptions, config);
6362
6476
  }
6363
6477
  var SANITIZE_RULES = [
6364
6478
  // Strip XML/HTML-like instruction tags that could be interpreted as system prompts