@harness-engineering/graph 0.11.2 → 0.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.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.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
|
|
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
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
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
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
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
|
-
|
|
4106
|
-
|
|
4107
|
-
domainEntryCounts.set(dir.name, entryCount);
|
|
4153
|
+
totalEntries += mdFiles.length;
|
|
4154
|
+
domainEntryCounts.set(dir.name, mdFiles.length);
|
|
4108
4155
|
if (store) {
|
|
4109
|
-
|
|
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
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
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
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
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
|
-
|
|
4624
|
-
const
|
|
4625
|
-
if (
|
|
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
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
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
|
|
4801
|
-
|
|
4802
|
-
|
|
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(
|
|
4945
|
+
const typeMatch = line.match(GO_TYPE_RE);
|
|
4846
4946
|
if (typeMatch) {
|
|
4847
4947
|
}
|
|
4848
|
-
const constBlockMatch = line.match(
|
|
4948
|
+
const constBlockMatch = line.match(GO_CONST_BLOCK_RE);
|
|
4849
4949
|
if (constBlockMatch) {
|
|
4850
|
-
const
|
|
4851
|
-
|
|
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(
|
|
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(
|
|
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
|
|
6335
|
-
const
|
|
6336
|
-
const
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
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
|
-
|
|
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
|