@openez-graph/cli 0.11.0 → 0.11.1
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/CHANGELOG.md +9 -0
- package/dist/cli.cjs +120 -24
- package/package.json +1 -1
package/dist/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ All notable changes to OpenEZ Graph are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.11.1] - 2026-08-06
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Writing phase performance bottleneck: symbol, import, and wikilink node upserts batched per file instead of N individual queries
|
|
13
|
+
- Embedding provider and model now logged at indexing startup for visibility
|
|
14
|
+
- Progress logs added for post-writing phases (embedding batches, FTS rebuild, call edge resolution)
|
|
15
|
+
|
|
8
16
|
## [0.11.0] - 2026-08-06
|
|
9
17
|
|
|
10
18
|
### Added
|
|
@@ -146,6 +154,7 @@ Remediation release — index/graph correctness, data protection, and web flow f
|
|
|
146
154
|
- Error handling and validation for import path extraction
|
|
147
155
|
- CLI npm packaging
|
|
148
156
|
|
|
157
|
+
[0.11.1]: https://github.com/asta-nguyen/openez-graph/compare/v0.11.0...v0.11.1
|
|
149
158
|
[0.11.0]: https://github.com/asta-nguyen/openez-graph/compare/v0.10.0...v0.11.0
|
|
150
159
|
[0.10.0]: https://github.com/asta-nguyen/openez-graph/compare/fbcad4f...HEAD
|
|
151
160
|
[0.9.1]: https://github.com/asta-nguyen/openez-graph/compare/fbcad4f...HEAD
|
package/dist/cli.cjs
CHANGED
|
@@ -12594,6 +12594,36 @@ function createWorkspaceRepository(rootPath) {
|
|
|
12594
12594
|
}
|
|
12595
12595
|
return ids;
|
|
12596
12596
|
},
|
|
12597
|
+
async upsertGraphNodesBatch(inputs) {
|
|
12598
|
+
if (inputs.length === 0) return [];
|
|
12599
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12600
|
+
const BATCH = 500;
|
|
12601
|
+
const results = [];
|
|
12602
|
+
for (let i = 0; i < inputs.length; i += BATCH) {
|
|
12603
|
+
const batch = inputs.slice(i, i + BATCH);
|
|
12604
|
+
const placeholders = batch.map(() => "(?, ?, ?, ?, ?, ?, ?)").join(",");
|
|
12605
|
+
const params = [];
|
|
12606
|
+
for (const item of batch) {
|
|
12607
|
+
const id = import_node_crypto2.default.randomUUID();
|
|
12608
|
+
params.push(
|
|
12609
|
+
id,
|
|
12610
|
+
item.type,
|
|
12611
|
+
item.label,
|
|
12612
|
+
item.refId ?? null,
|
|
12613
|
+
item.metadata ?? "{}",
|
|
12614
|
+
now,
|
|
12615
|
+
now
|
|
12616
|
+
);
|
|
12617
|
+
}
|
|
12618
|
+
const rows = native.prepare(
|
|
12619
|
+
`INSERT INTO graph_nodes (id, type, label, ref_id, metadata, created_at, updated_at) VALUES ${placeholders}
|
|
12620
|
+
ON CONFLICT(type, label) WHERE type != 'symbol' DO UPDATE SET ref_id = COALESCE(excluded.ref_id, graph_nodes.ref_id), metadata = excluded.metadata, updated_at = excluded.updated_at
|
|
12621
|
+
RETURNING id, label`
|
|
12622
|
+
).all(...params);
|
|
12623
|
+
results.push(...rows.map((r) => ({ label: r.label, id: String(r.id) })));
|
|
12624
|
+
}
|
|
12625
|
+
return results;
|
|
12626
|
+
},
|
|
12597
12627
|
async getGraphNode(id) {
|
|
12598
12628
|
const row = native.prepare("SELECT * FROM graph_nodes WHERE id = ?").get(id);
|
|
12599
12629
|
return row ? mapNodeRow(row) : null;
|
|
@@ -30293,13 +30323,7 @@ function walkTree(root, config2, lines) {
|
|
|
30293
30323
|
endLine: endRow,
|
|
30294
30324
|
...receiver ? { receiver } : {}
|
|
30295
30325
|
});
|
|
30296
|
-
extractCallsInNode(
|
|
30297
|
-
node,
|
|
30298
|
-
config2,
|
|
30299
|
-
fullName,
|
|
30300
|
-
calledIdentifiers,
|
|
30301
|
-
callExpressions
|
|
30302
|
-
);
|
|
30326
|
+
extractCallsInNode(node, config2, fullName, calledIdentifiers, callExpressions);
|
|
30303
30327
|
const isContextNode = symbolRule.establishesContext || config2.contextNodeTypes.has(node.type);
|
|
30304
30328
|
if (isContextNode) {
|
|
30305
30329
|
const contextName = symbolRule.extractContextName ? symbolRule.extractContextName(node) ?? fullName : fullName;
|
|
@@ -30324,9 +30348,7 @@ function walkTree(root, config2, lines) {
|
|
|
30324
30348
|
}
|
|
30325
30349
|
function extractCallsInNode(symbolNode, config2, callerName, calledIdentifiers, callExpressions) {
|
|
30326
30350
|
const nestedSymbolTypes = config2.symbolRules.map((r) => r.nodeType);
|
|
30327
|
-
const nestedSymbols = symbolNode.descendantsOfType(nestedSymbolTypes).filter(
|
|
30328
|
-
(n) => !(n.startIndex === symbolNode.startIndex && n.endIndex === symbolNode.endIndex)
|
|
30329
|
-
);
|
|
30351
|
+
const nestedSymbols = symbolNode.descendantsOfType(nestedSymbolTypes).filter((n) => !(n.startIndex === symbolNode.startIndex && n.endIndex === symbolNode.endIndex));
|
|
30330
30352
|
const callNodes = symbolNode.descendantsOfType(config2.callRule.nodeType);
|
|
30331
30353
|
for (const callNode of callNodes) {
|
|
30332
30354
|
const insideNested = nestedSymbols.some(
|
|
@@ -281073,7 +281095,12 @@ async function writeEmbeddingsToRepo(repo, chunkRows, provider) {
|
|
|
281073
281095
|
const BATCH_SIZE = 50;
|
|
281074
281096
|
let totalWritten = 0;
|
|
281075
281097
|
let failedBatches = 0;
|
|
281098
|
+
const totalBatches = Math.ceil(toEmbed.length / BATCH_SIZE);
|
|
281076
281099
|
for (let i = 0; i < toEmbed.length; i += BATCH_SIZE) {
|
|
281100
|
+
const batchNum = Math.floor(i / BATCH_SIZE) + 1;
|
|
281101
|
+
if (batchNum % 10 === 0 || batchNum === totalBatches) {
|
|
281102
|
+
process.stdout.write(`\r[embedding] batch ${batchNum}/${totalBatches} (${totalWritten} written)`);
|
|
281103
|
+
}
|
|
281077
281104
|
const batch = toEmbed.slice(i, i + BATCH_SIZE);
|
|
281078
281105
|
try {
|
|
281079
281106
|
const vectors = await provider.embed(
|
|
@@ -281118,6 +281145,9 @@ async function writeEmbeddingsToRepo(repo, chunkRows, provider) {
|
|
|
281118
281145
|
);
|
|
281119
281146
|
}
|
|
281120
281147
|
}
|
|
281148
|
+
if (totalBatches > 0) {
|
|
281149
|
+
process.stdout.write("\n");
|
|
281150
|
+
}
|
|
281121
281151
|
return { written: totalWritten + reusedWritten, failedBatches };
|
|
281122
281152
|
}
|
|
281123
281153
|
async function parseInline(tasks, onProgress) {
|
|
@@ -281161,6 +281191,15 @@ async function indexWorkspace(input) {
|
|
|
281161
281191
|
const excludeGlobs = workspace.excludeGlobs || configuredWorkspace?.exclude.join("\n") || "";
|
|
281162
281192
|
const embeddingProvider = await getEmbeddingProvider();
|
|
281163
281193
|
const runMode = input.mode ?? "incremental";
|
|
281194
|
+
if (embeddingProvider) {
|
|
281195
|
+
process.stdout.write(
|
|
281196
|
+
`[embedding] provider=${embeddingProvider.provider} model=${embeddingProvider.model}
|
|
281197
|
+
`
|
|
281198
|
+
);
|
|
281199
|
+
} else {
|
|
281200
|
+
process.stdout.write(`[embedding] disabled (no provider configured)
|
|
281201
|
+
`);
|
|
281202
|
+
}
|
|
281164
281203
|
const reportProgress = async (message, progress) => {
|
|
281165
281204
|
await input.onProgress?.({ message, progress });
|
|
281166
281205
|
};
|
|
@@ -281358,6 +281397,8 @@ async function indexWorkspace(input) {
|
|
|
281358
281397
|
const chunkNodeIds = await repo.insertGraphNodesBatch(chunkNodeInputs);
|
|
281359
281398
|
const edges = [];
|
|
281360
281399
|
const reusedSymbolIds = /* @__PURE__ */ new Set();
|
|
281400
|
+
const newSymbolInputs = [];
|
|
281401
|
+
const pendingSymbolEdges = [];
|
|
281361
281402
|
for (const [ci, chunkId] of chunkIds.entries()) {
|
|
281362
281403
|
const chunkNodeId = chunkNodeIds[ci];
|
|
281363
281404
|
edges.push({ fromNodeId: fileNodeId, toNodeId: chunkNodeId, type: "contains" });
|
|
@@ -281366,7 +281407,7 @@ async function indexWorkspace(input) {
|
|
|
281366
281407
|
if (symbolName) {
|
|
281367
281408
|
const fileSymbolKey = `${file.relativePath}\0${symbolName}`;
|
|
281368
281409
|
let symbolNodeId = symbolNodeIdsByFileAndName.get(fileSymbolKey);
|
|
281369
|
-
if (
|
|
281410
|
+
if (symbolNodeIdsByFileAndName.has(fileSymbolKey) === false) {
|
|
281370
281411
|
const existingSymbolId = fileExistingSymbols.get(symbolName);
|
|
281371
281412
|
if (existingSymbolId) {
|
|
281372
281413
|
repo.updateSymbolNode(
|
|
@@ -281381,7 +281422,7 @@ async function indexWorkspace(input) {
|
|
|
281381
281422
|
);
|
|
281382
281423
|
symbolNodeId = existingSymbolId;
|
|
281383
281424
|
} else {
|
|
281384
|
-
|
|
281425
|
+
newSymbolInputs.push({
|
|
281385
281426
|
type: "symbol",
|
|
281386
281427
|
label: symbolName,
|
|
281387
281428
|
refId: chunkId,
|
|
@@ -281390,14 +281431,43 @@ async function indexWorkspace(input) {
|
|
|
281390
281431
|
filePath: file.relativePath,
|
|
281391
281432
|
language: indexed.language,
|
|
281392
281433
|
parser: indexed.parser
|
|
281393
|
-
})
|
|
281434
|
+
}),
|
|
281435
|
+
_chunkIndex: ci
|
|
281394
281436
|
});
|
|
281437
|
+
symbolNodeId = "";
|
|
281395
281438
|
}
|
|
281396
281439
|
symbolNodeIdsByFileAndName.set(fileSymbolKey, symbolNodeId);
|
|
281440
|
+
} else {
|
|
281441
|
+
symbolNodeId = symbolNodeIdsByFileAndName.get(fileSymbolKey) ?? "";
|
|
281397
281442
|
}
|
|
281398
281443
|
reusedSymbolIds.add(symbolNodeId);
|
|
281444
|
+
const definesEdgeIdx = edges.length;
|
|
281399
281445
|
edges.push({ fromNodeId: fileNodeId, toNodeId: symbolNodeId, type: "defines" });
|
|
281446
|
+
const representedByEdgeIdx = edges.length;
|
|
281400
281447
|
edges.push({ fromNodeId: symbolNodeId, toNodeId: chunkNodeId, type: "represented_by" });
|
|
281448
|
+
if (symbolNodeId === "") {
|
|
281449
|
+
pendingSymbolEdges.push({ label: symbolName, definesEdgeIdx, representedByEdgeIdx });
|
|
281450
|
+
}
|
|
281451
|
+
}
|
|
281452
|
+
}
|
|
281453
|
+
if (newSymbolInputs.length > 0) {
|
|
281454
|
+
const symbolIds = await repo.insertGraphNodesBatch(
|
|
281455
|
+
newSymbolInputs.map(({ _chunkIndex, ...rest }) => rest)
|
|
281456
|
+
);
|
|
281457
|
+
for (let si = 0; si < newSymbolInputs.length; si++) {
|
|
281458
|
+
const input2 = newSymbolInputs[si];
|
|
281459
|
+
const fileSymbolKey = `${file.relativePath}\0${input2.label}`;
|
|
281460
|
+
const newId = symbolIds[si];
|
|
281461
|
+
symbolNodeIdsByFileAndName.set(fileSymbolKey, newId);
|
|
281462
|
+
reusedSymbolIds.add(newId);
|
|
281463
|
+
}
|
|
281464
|
+
for (const pending of pendingSymbolEdges) {
|
|
281465
|
+
const newId = symbolNodeIdsByFileAndName.get(
|
|
281466
|
+
`${file.relativePath}\0${pending.label}`
|
|
281467
|
+
);
|
|
281468
|
+
if (!newId) continue;
|
|
281469
|
+
edges[pending.definesEdgeIdx].toNodeId = newId;
|
|
281470
|
+
edges[pending.representedByEdgeIdx].fromNodeId = newId;
|
|
281401
281471
|
}
|
|
281402
281472
|
}
|
|
281403
281473
|
const staleSymbolIds = [];
|
|
@@ -281409,6 +281479,8 @@ async function indexWorkspace(input) {
|
|
|
281409
281479
|
if (staleSymbolIds.length > 0) {
|
|
281410
281480
|
repo.deleteGraphNodesByIds(staleSymbolIds);
|
|
281411
281481
|
}
|
|
281482
|
+
const batchNodeInputs = [];
|
|
281483
|
+
const importEdgeInputs = [];
|
|
281412
281484
|
for (const importPath of indexed.importPaths) {
|
|
281413
281485
|
if (typeof importPath !== "string" || importPath.length === 0) continue;
|
|
281414
281486
|
const resolvedImportPath = workspaceFileResolver?.resolveImport(
|
|
@@ -281417,25 +281489,43 @@ async function indexWorkspace(input) {
|
|
|
281417
281489
|
indexed.language ?? void 0
|
|
281418
281490
|
);
|
|
281419
281491
|
if (!resolvedImportPath) continue;
|
|
281420
|
-
|
|
281492
|
+
batchNodeInputs.push({
|
|
281421
281493
|
type: "file",
|
|
281422
281494
|
label: resolvedImportPath,
|
|
281423
281495
|
metadata: JSON.stringify({ path: resolvedImportPath })
|
|
281424
281496
|
});
|
|
281425
|
-
|
|
281426
|
-
fromNodeId: fileNodeId,
|
|
281427
|
-
toNodeId: targetNodeId,
|
|
281428
|
-
type: "imports",
|
|
281429
|
-
metadata: JSON.stringify({ importPath })
|
|
281430
|
-
});
|
|
281497
|
+
importEdgeInputs.push({ importPath, resolvedImportPath });
|
|
281431
281498
|
}
|
|
281432
281499
|
for (const link of indexed.wikilinks) {
|
|
281433
|
-
|
|
281500
|
+
batchNodeInputs.push({
|
|
281434
281501
|
type: "entity",
|
|
281435
281502
|
label: link,
|
|
281436
281503
|
metadata: "{}"
|
|
281437
281504
|
});
|
|
281438
|
-
|
|
281505
|
+
}
|
|
281506
|
+
if (batchNodeInputs.length > 0) {
|
|
281507
|
+
const upsertedNodes = await repo.upsertGraphNodesBatch(batchNodeInputs);
|
|
281508
|
+
const nodeByLabel = /* @__PURE__ */ new Map();
|
|
281509
|
+
for (const node of upsertedNodes) {
|
|
281510
|
+
nodeByLabel.set(node.label, node.id);
|
|
281511
|
+
}
|
|
281512
|
+
for (const { importPath, resolvedImportPath } of importEdgeInputs) {
|
|
281513
|
+
const targetNodeId = nodeByLabel.get(resolvedImportPath);
|
|
281514
|
+
if (targetNodeId) {
|
|
281515
|
+
edges.push({
|
|
281516
|
+
fromNodeId: fileNodeId,
|
|
281517
|
+
toNodeId: targetNodeId,
|
|
281518
|
+
type: "imports",
|
|
281519
|
+
metadata: JSON.stringify({ importPath })
|
|
281520
|
+
});
|
|
281521
|
+
}
|
|
281522
|
+
}
|
|
281523
|
+
for (const link of indexed.wikilinks) {
|
|
281524
|
+
const entityNodeId = nodeByLabel.get(link);
|
|
281525
|
+
if (entityNodeId) {
|
|
281526
|
+
edges.push({ fromNodeId: fileNodeId, toNodeId: entityNodeId, type: "mentions" });
|
|
281527
|
+
}
|
|
281528
|
+
}
|
|
281439
281529
|
}
|
|
281440
281530
|
const edgeSet = /* @__PURE__ */ new Set();
|
|
281441
281531
|
const dedupedEdges = edges.filter((e) => {
|
|
@@ -281463,7 +281553,11 @@ async function indexWorkspace(input) {
|
|
|
281463
281553
|
filesUpdated += 1;
|
|
281464
281554
|
}
|
|
281465
281555
|
});
|
|
281466
|
-
if (allChunkRowsForEmbeddings.length > 0) {
|
|
281556
|
+
if (allChunkRowsForEmbeddings.length > 0 && embeddingProvider) {
|
|
281557
|
+
await reportProgress(
|
|
281558
|
+
`Embedding ${allChunkRowsForEmbeddings.length} chunks via ${embeddingProvider.provider}/${embeddingProvider.model}...`,
|
|
281559
|
+
90
|
|
281560
|
+
);
|
|
281467
281561
|
const embeddingResult = await writeEmbeddingsToRepo(
|
|
281468
281562
|
repo,
|
|
281469
281563
|
allChunkRowsForEmbeddings,
|
|
@@ -281473,10 +281567,12 @@ async function indexWorkspace(input) {
|
|
|
281473
281567
|
embeddingFailures += embeddingResult.failedBatches;
|
|
281474
281568
|
}
|
|
281475
281569
|
if (bulkWriteMode) {
|
|
281570
|
+
await reportProgress("Rebuilding FTS index...", 93);
|
|
281476
281571
|
repo.restoreFtsTriggers();
|
|
281477
281572
|
repo.setOptimizedWriteMode(false);
|
|
281478
281573
|
bulkWriteMode = false;
|
|
281479
281574
|
}
|
|
281575
|
+
await reportProgress("Resolving call edges...", 95);
|
|
281480
281576
|
const globalSymbolNodes = await repo.loadAllSymbolNodes();
|
|
281481
281577
|
const insertedCallEdges = /* @__PURE__ */ new Set();
|
|
281482
281578
|
const callEdges = [];
|
|
@@ -296995,7 +297091,7 @@ __export(mcp_bridge_exports, {
|
|
|
296995
297091
|
startMcpServer: () => startMcpServer
|
|
296996
297092
|
});
|
|
296997
297093
|
async function startMcpServer(defaultPath, version4) {
|
|
296998
|
-
await createAndStartMcpServer({ defaultPath, version: version4, build: "
|
|
297094
|
+
await createAndStartMcpServer({ defaultPath, version: version4, build: "ebc5064-dirty" });
|
|
296999
297095
|
}
|
|
297000
297096
|
var init_mcp_bridge = __esm({
|
|
297001
297097
|
"src/mcp-bridge.ts"() {
|
package/package.json
CHANGED