@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.d.mts +46 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +534 -420
- package/dist/index.mjs +534 -420
- package/package.json +2 -2
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
|
|
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
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
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
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
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
|
-
|
|
3991
|
-
|
|
3992
|
-
domainEntryCounts.set(dir.name, entryCount);
|
|
4038
|
+
totalEntries += mdFiles.length;
|
|
4039
|
+
domainEntryCounts.set(dir.name, mdFiles.length);
|
|
3993
4040
|
if (store) {
|
|
3994
|
-
|
|
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
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
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
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
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
|
-
|
|
4509
|
-
const
|
|
4510
|
-
if (
|
|
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
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
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
|
|
4686
|
-
|
|
4687
|
-
|
|
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(
|
|
4830
|
+
const typeMatch = line.match(GO_TYPE_RE);
|
|
4731
4831
|
if (typeMatch) {
|
|
4732
4832
|
}
|
|
4733
|
-
const constBlockMatch = line.match(
|
|
4833
|
+
const constBlockMatch = line.match(GO_CONST_BLOCK_RE);
|
|
4734
4834
|
if (constBlockMatch) {
|
|
4735
|
-
const
|
|
4736
|
-
|
|
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(
|
|
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(
|
|
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
|
|
6220
|
-
const
|
|
6221
|
-
const
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
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
|
-
|
|
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
|