@neuralsea/workspace-indexer 0.3.3 → 0.3.5

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.
@@ -1621,6 +1621,16 @@ var RepoFileIndexer = class {
1621
1621
  return;
1622
1622
  }
1623
1623
  this.emit({ type: "repo/index/file/start", repoRoot: this.repoRoot, path: posixRelPath });
1624
+ const stage = (stageName, stageStartedAt) => {
1625
+ this.emit({
1626
+ type: "repo/index/file/stage",
1627
+ repoRoot: this.repoRoot,
1628
+ path: posixRelPath,
1629
+ stage: stageName,
1630
+ ms: Date.now() - stageStartedAt
1631
+ });
1632
+ };
1633
+ const readStartedAt = Date.now();
1624
1634
  const abs = path9.join(this.repoRoot, fromPosixPath(posixRelPath));
1625
1635
  let stat;
1626
1636
  try {
@@ -1644,16 +1654,20 @@ var RepoFileIndexer = class {
1644
1654
  }
1645
1655
  const raw = buf.toString("utf8");
1646
1656
  const redacted = this.applyRedactions(raw);
1657
+ stage("read", readStartedAt);
1647
1658
  const fileHash = sha256Hex(redacted);
1648
1659
  const prev = this.store.getFileHash(posixRelPath);
1649
1660
  if (prev === fileHash) {
1650
1661
  this.emit({ type: "repo/index/file/skip", repoRoot: this.repoRoot, path: posixRelPath, reason: "unchanged" });
1651
1662
  return;
1652
1663
  }
1664
+ const chunkStartedAt = Date.now();
1653
1665
  const { language, chunks } = chunkSource(posixRelPath, redacted, this.config.chunk);
1666
+ stage("chunk", chunkStartedAt);
1654
1667
  let imports = [];
1655
1668
  let exports = [];
1656
1669
  if (language === "typescript" || language === "javascript") {
1670
+ const relationsStartedAt = Date.now();
1657
1671
  const rel = extractTsRelations(posixRelPath, redacted);
1658
1672
  imports = rel.imports;
1659
1673
  exports = rel.exports;
@@ -1661,12 +1675,14 @@ var RepoFileIndexer = class {
1661
1675
  this.store.setEdges(posixRelPath, "export", rel.exports);
1662
1676
  this.workspaceStore?.setEdges(this.repoId, posixRelPath, "import", rel.imports);
1663
1677
  this.workspaceStore?.setEdges(this.repoId, posixRelPath, "export", rel.exports);
1678
+ stage("relations", relationsStartedAt);
1664
1679
  } else {
1665
1680
  this.store.setEdges(posixRelPath, "import", []);
1666
1681
  this.store.setEdges(posixRelPath, "export", []);
1667
1682
  this.workspaceStore?.setEdges(this.repoId, posixRelPath, "import", []);
1668
1683
  this.workspaceStore?.setEdges(this.repoId, posixRelPath, "export", []);
1669
1684
  }
1685
+ const synopsisStartedAt = Date.now();
1670
1686
  const synopsisText = buildSynopsis(posixRelPath, language, redacted);
1671
1687
  const synopsis = synopsisText.trim() ? [
1672
1688
  {
@@ -1691,14 +1707,29 @@ var RepoFileIndexer = class {
1691
1707
  }
1692
1708
  ] : [];
1693
1709
  const combined = [...synopsis, ...headerChunk, ...chunks.map((c) => ({ ...c, kind: "chunk" }))];
1710
+ stage("synopsis", synopsisStartedAt);
1711
+ const embedStartedAt = Date.now();
1694
1712
  const embedTexts = [];
1695
1713
  const embedPlan = [];
1696
1714
  const embeddings = combined.map(() => null);
1715
+ let cached2 = 0;
1697
1716
  for (let i = 0; i < combined.length; i++) {
1698
1717
  const ch = combined[i];
1699
- const cached2 = this.embeddingCache.get(this.embedder.id, ch.contentHash);
1700
- if (cached2) {
1701
- embeddings[i] = cached2;
1718
+ const cachedEmb = this.embeddingCache.get(this.embedder.id, ch.contentHash);
1719
+ if (cachedEmb) {
1720
+ embeddings[i] = cachedEmb;
1721
+ cached2++;
1722
+ this.emit({
1723
+ type: "repo/index/file/embed/chunk",
1724
+ repoRoot: this.repoRoot,
1725
+ path: posixRelPath,
1726
+ chunkIndex: i,
1727
+ chunksTotal: combined.length,
1728
+ kind: ch.kind,
1729
+ startLine: ch.startLine,
1730
+ endLine: ch.endLine,
1731
+ cached: true
1732
+ });
1702
1733
  continue;
1703
1734
  }
1704
1735
  embedTexts.push(
@@ -1710,7 +1741,7 @@ lines:${ch.startLine}-${ch.endLine}
1710
1741
  ---
1711
1742
  ${ch.text}`
1712
1743
  );
1713
- embedPlan.push({ chunkIdx: i, contentHash: ch.contentHash });
1744
+ embedPlan.push({ chunkIdx: i, contentHash: ch.contentHash, kind: ch.kind, startLine: ch.startLine, endLine: ch.endLine });
1714
1745
  }
1715
1746
  const batchSize = this.config.embed.batchSize;
1716
1747
  for (let start = 0; start < embedTexts.length; start += batchSize) {
@@ -1728,13 +1759,35 @@ ${ch.text}`
1728
1759
  const plan = embedPlan[start + j];
1729
1760
  embeddings[plan.chunkIdx] = vecs[j];
1730
1761
  this.embeddingCache.put(this.embedder.id, plan.contentHash, vecs[j]);
1762
+ this.emit({
1763
+ type: "repo/index/file/embed/chunk",
1764
+ repoRoot: this.repoRoot,
1765
+ path: posixRelPath,
1766
+ chunkIndex: plan.chunkIdx,
1767
+ chunksTotal: combined.length,
1768
+ kind: plan.kind,
1769
+ startLine: plan.startLine,
1770
+ endLine: plan.endLine,
1771
+ cached: false
1772
+ });
1731
1773
  }
1732
1774
  }
1775
+ this.emit({
1776
+ type: "repo/index/file/embed/done",
1777
+ repoRoot: this.repoRoot,
1778
+ path: posixRelPath,
1779
+ chunks: combined.length,
1780
+ cached: cached2,
1781
+ computed: embedTexts.length,
1782
+ ms: Date.now() - embedStartedAt
1783
+ });
1784
+ stage("embed", embedStartedAt);
1733
1785
  const fileMtime = stat.mtimeMs;
1734
1786
  const ftsMode = this.config.storage.ftsMode;
1735
1787
  const storeText = this.config.storage.storeText;
1736
1788
  const oldChunkIds = this.store.listChunksForFile(posixRelPath).map((r) => r.id);
1737
1789
  const points = [];
1790
+ const storeStartedAt = Date.now();
1738
1791
  const rows = combined.map((ch, i) => {
1739
1792
  const id = sha256Hex(
1740
1793
  `${this.repoId}:${posixRelPath}:${ch.kind}:${ch.startLine}:${ch.endLine}:${ch.contentHash}`
@@ -1762,7 +1815,22 @@ ${ch.text}`
1762
1815
  this.store.replaceChunksForFile(posixRelPath, rows);
1763
1816
  this.workspaceStore?.upsertFile(this.repoId, posixRelPath, fileHash, fileMtime, language, stat.size);
1764
1817
  this.workspaceStore?.replaceChunksForFile(this.repoId, this.repoRoot, posixRelPath, rows);
1818
+ stage("store", storeStartedAt);
1819
+ const symbolsStartedAt = Date.now();
1820
+ if (this.symbolGraphProvider && this.symbolGraphProvider.supports(language)) {
1821
+ this.emit({ type: "repo/index/file/symbols/start", repoRoot: this.repoRoot, path: posixRelPath, providerId: this.symbolGraphProvider.id });
1822
+ }
1765
1823
  const symbolOut = await this.indexSymbolsIfEnabled(posixRelPath, language, redacted, fileHash);
1824
+ this.emit({
1825
+ type: "repo/index/file/symbols",
1826
+ repoRoot: this.repoRoot,
1827
+ path: posixRelPath,
1828
+ symbols: symbolOut?.symbols?.length ?? 0,
1829
+ edges: symbolOut?.edges?.length ?? 0,
1830
+ ms: Date.now() - symbolsStartedAt
1831
+ });
1832
+ stage("symbols", symbolsStartedAt);
1833
+ const graphStartedAt = Date.now();
1766
1834
  const head = this.getHead();
1767
1835
  if (this.graphStore && head) {
1768
1836
  await this.graphStore.replaceFileGraph({
@@ -1778,7 +1846,10 @@ ${ch.text}`
1778
1846
  symbolEdges: (symbolOut?.edges ?? []).map((e) => ({ fromId: e.fromId, toId: e.toId, kind: e.kind }))
1779
1847
  }).catch(() => void 0);
1780
1848
  }
1849
+ stage("graph", graphStartedAt);
1850
+ const vectorStartedAt = Date.now();
1781
1851
  await this.ensureVectorUpToDate(points, oldChunkIds);
1852
+ stage("vector", vectorStartedAt);
1782
1853
  this.emit({
1783
1854
  type: "repo/index/file/done",
1784
1855
  repoRoot: this.repoRoot,
@@ -2389,28 +2460,46 @@ var RepoIndexer = class {
2389
2460
  const uniq3 = Array.from(new Set(posixPaths)).slice(0, maxFiles);
2390
2461
  for (const p of uniq3) {
2391
2462
  if (opts?.signal?.aborted) return;
2463
+ const startedAt = Date.now();
2464
+ this.emitProgress({ type: "repo/symbolGraph/expand/start", repoRoot: this.repoRoot, path: p });
2392
2465
  const abs = path12.join(this.repoRoot, p.split("/").join(path12.sep));
2393
2466
  let text = "";
2394
2467
  try {
2395
2468
  text = fs9.readFileSync(abs, "utf8");
2396
2469
  } catch {
2470
+ this.emitProgress({ type: "repo/symbolGraph/expand/done", repoRoot: this.repoRoot, path: p, edges: 0, ms: Date.now() - startedAt });
2397
2471
  continue;
2398
2472
  }
2399
2473
  const contentHash = this.store.getFileHash(p) ?? void 0;
2400
2474
  const lang = languageFromPath(p);
2401
2475
  const edges = await provider.expandDocumentEdges({ repoRoot: this.repoRoot, path: p, language: lang, text, contentHash }, { signal: opts?.signal }).catch(() => []);
2402
2476
  const normalized = (Array.isArray(edges) ? edges : []).map((e) => ({
2403
- fromId: e.fromId,
2404
- toId: e.toId,
2405
- kind: e.kind,
2406
- toPath: String(e.toPath ?? "")
2477
+ fromId: String(e?.fromId ?? ""),
2478
+ toId: String(e?.toId ?? ""),
2479
+ kind: String(e?.kind ?? ""),
2480
+ toPath: String(e?.toPath ?? "")
2407
2481
  }));
2482
+ const byKind = {};
2483
+ for (const e of normalized) {
2484
+ const kind = e.kind;
2485
+ if (kind === "definition" || kind === "reference" || kind === "implementation" || kind === "typeDefinition") {
2486
+ byKind[kind] = (byKind[kind] ?? 0) + 1;
2487
+ }
2488
+ }
2408
2489
  this.workspaceStore.replaceSymbolEdgesFromFile(this.repoId, p, normalized);
2409
2490
  await this.graphStore?.replaceOutgoingSymbolEdgesFromFile?.({
2410
2491
  repoId: this.repoId,
2411
2492
  fromPath: p,
2412
2493
  edges: normalized.map((e) => ({ fromId: e.fromId, toId: e.toId, kind: e.kind, toPath: e.toPath }))
2413
2494
  }).catch(() => void 0);
2495
+ this.emitProgress({
2496
+ type: "repo/symbolGraph/expand/done",
2497
+ repoRoot: this.repoRoot,
2498
+ path: p,
2499
+ edges: normalized.length,
2500
+ byKind,
2501
+ ms: Date.now() - startedAt
2502
+ });
2414
2503
  }
2415
2504
  }
2416
2505
  async watch() {
@@ -3308,10 +3397,24 @@ var Neo4jGraphStore = class {
3308
3397
  this.driver = driver;
3309
3398
  this.cfg = cfg;
3310
3399
  this.labelPrefix = cfg.labelPrefix ?? "Petri";
3400
+ this.emitProgress = cfg.emit ?? null;
3311
3401
  }
3312
3402
  id = "neo4j";
3313
3403
  labelPrefix;
3314
3404
  schemaVersionLatest = 2;
3405
+ emitProgress;
3406
+ opStart(op, args) {
3407
+ try {
3408
+ this.emitProgress?.({ type: "graph/neo4j/op/start", op, repoId: args?.repoId, path: args?.path });
3409
+ } catch {
3410
+ }
3411
+ }
3412
+ opDone(op, startedAt, args) {
3413
+ try {
3414
+ this.emitProgress?.({ type: "graph/neo4j/op/done", op, repoId: args?.repoId, path: args?.path, ms: Date.now() - startedAt });
3415
+ } catch {
3416
+ }
3417
+ }
3315
3418
  labels() {
3316
3419
  return {
3317
3420
  Repo: lp(this.labelPrefix, "Repo"),
@@ -3322,6 +3425,8 @@ var Neo4jGraphStore = class {
3322
3425
  };
3323
3426
  }
3324
3427
  async init() {
3428
+ const startedAt = Date.now();
3429
+ this.opStart("init");
3325
3430
  const { Schema } = this.labels();
3326
3431
  await runSession(this.driver, this.cfg.database, async (s) => {
3327
3432
  await s.run(
@@ -3332,6 +3437,7 @@ var Neo4jGraphStore = class {
3332
3437
  );
3333
3438
  });
3334
3439
  await this.runMigrations().catch(() => void 0);
3440
+ this.opDone("init", startedAt);
3335
3441
  }
3336
3442
  async getSchemaVersion() {
3337
3443
  const { Schema } = this.labels();
@@ -3355,6 +3461,8 @@ var Neo4jGraphStore = class {
3355
3461
  });
3356
3462
  }
3357
3463
  async runMigrations() {
3464
+ const startedAt = Date.now();
3465
+ this.opStart("migrations");
3358
3466
  const { Repo, File, Symbol, External } = this.labels();
3359
3467
  let v = await this.getSchemaVersion();
3360
3468
  if (v < 1) {
@@ -3395,8 +3503,11 @@ var Neo4jGraphStore = class {
3395
3503
  if (v !== this.schemaVersionLatest) {
3396
3504
  await this.setSchemaVersion(this.schemaVersionLatest);
3397
3505
  }
3506
+ this.opDone("migrations", startedAt);
3398
3507
  }
3399
3508
  async setRepoHead(args) {
3509
+ const startedAt = Date.now();
3510
+ this.opStart("setRepoHead", { repoId: args.repoId });
3400
3511
  const { Repo, File, Symbol, External } = this.labels();
3401
3512
  const { repoId, repoRoot, commit, branch } = args;
3402
3513
  await runSession(this.driver, this.cfg.database, async (s) => {
@@ -3427,15 +3538,21 @@ var Neo4jGraphStore = class {
3427
3538
  await s.run(`MATCH (r:${Repo} {repo_id:$repoId}) SET r.commit=$commit`, { repoId, commit });
3428
3539
  }
3429
3540
  });
3541
+ this.opDone("setRepoHead", startedAt, { repoId: args.repoId });
3430
3542
  }
3431
3543
  async deleteFile(args) {
3544
+ const startedAt = Date.now();
3545
+ this.opStart("deleteFile", { repoId: args.repoId, path: args.path });
3432
3546
  const { File, Symbol } = this.labels();
3433
3547
  await runSession(this.driver, this.cfg.database, async (s) => {
3434
3548
  await s.run(`MATCH (s:${Symbol} {repo_id:$repoId, path:$path}) DETACH DELETE s`, args);
3435
3549
  await s.run(`MATCH (f:${File} {repo_id:$repoId, path:$path}) DETACH DELETE f`, args);
3436
3550
  });
3551
+ this.opDone("deleteFile", startedAt, { repoId: args.repoId, path: args.path });
3437
3552
  }
3438
3553
  async replaceOutgoingSymbolEdgesFromFile(args) {
3554
+ const startedAt = Date.now();
3555
+ this.opStart("replaceOutgoingSymbolEdgesFromFile", { repoId: args.repoId, path: args.fromPath });
3439
3556
  const { Symbol } = this.labels();
3440
3557
  const kinds = args.edges.length > 0 ? Array.from(new Set(args.edges.map((e) => e.kind))) : ["definition", "reference", "implementation", "typeDefinition"];
3441
3558
  const keep = args.edges.map((e) => `${e.fromId}|${e.kind}|${e.toId}`);
@@ -3467,8 +3584,11 @@ var Neo4jGraphStore = class {
3467
3584
  { repoId: args.repoId, fromPath: args.fromPath, kinds, keep }
3468
3585
  ).catch(() => void 0);
3469
3586
  });
3587
+ this.opDone("replaceOutgoingSymbolEdgesFromFile", startedAt, { repoId: args.repoId, path: args.fromPath });
3470
3588
  }
3471
3589
  async replaceFileGraph(update) {
3590
+ const startedAt = Date.now();
3591
+ this.opStart("replaceFileGraph", { repoId: update.repoId, path: update.path });
3472
3592
  const { Repo, File, Symbol, External } = this.labels();
3473
3593
  const symbols = update.symbols.map((s) => ({
3474
3594
  id: s.id,
@@ -3580,8 +3700,11 @@ var Neo4jGraphStore = class {
3580
3700
  }
3581
3701
  }
3582
3702
  });
3703
+ this.opDone("replaceFileGraph", startedAt, { repoId: update.repoId, path: update.path });
3583
3704
  }
3584
3705
  async replaceRepoLinks(args) {
3706
+ const startedAt = Date.now();
3707
+ this.opStart("replaceRepoLinks");
3585
3708
  const { Repo } = this.labels();
3586
3709
  const byFrom = /* @__PURE__ */ new Map();
3587
3710
  for (const l of args.links) {
@@ -3612,8 +3735,11 @@ var Neo4jGraphStore = class {
3612
3735
  ).catch(() => void 0);
3613
3736
  });
3614
3737
  }
3738
+ this.opDone("replaceRepoLinks", startedAt);
3615
3739
  }
3616
3740
  async neighborFiles(args) {
3741
+ const startedAt = Date.now();
3742
+ this.opStart("neighborFiles");
3617
3743
  const { File, Symbol } = this.labels();
3618
3744
  const limit = args.limit ?? 20;
3619
3745
  const kinds = args.kinds && args.kinds.length > 0 ? args.kinds : null;
@@ -3637,9 +3763,12 @@ var Neo4jGraphStore = class {
3637
3763
  rows.push({ repoId, path: p, weight: Number.isFinite(w) ? w : 0 });
3638
3764
  }
3639
3765
  });
3766
+ this.opDone("neighborFiles", startedAt);
3640
3767
  return rows;
3641
3768
  }
3642
3769
  async shortestFilePath(args) {
3770
+ const startedAt = Date.now();
3771
+ this.opStart("shortestFilePath");
3643
3772
  const { File } = this.labels();
3644
3773
  const maxRels = args.maxRels ?? 16;
3645
3774
  let out = null;
@@ -3655,9 +3784,12 @@ var Neo4jGraphStore = class {
3655
3784
  if (!Array.isArray(files)) return;
3656
3785
  out = files.map((x) => ({ repoId: String(x.repoId), path: String(x.path) }));
3657
3786
  });
3787
+ this.opDone("shortestFilePath", startedAt);
3658
3788
  return out;
3659
3789
  }
3660
3790
  async extractFileSubgraph(args) {
3791
+ const startedAt = Date.now();
3792
+ this.opStart("extractFileSubgraph");
3661
3793
  const { File, Symbol } = this.labels();
3662
3794
  const limitEdges = args.limitEdges ?? 200;
3663
3795
  const nodes = /* @__PURE__ */ new Map();
@@ -3679,6 +3811,7 @@ var Neo4jGraphStore = class {
3679
3811
  edges.push({ from, to, kind });
3680
3812
  }
3681
3813
  });
3814
+ this.opDone("extractFileSubgraph", startedAt);
3682
3815
  return { nodes: Array.from(nodes.values()), edges };
3683
3816
  }
3684
3817
  async close() {
@@ -4039,7 +4172,8 @@ var WorkspaceIndexer = class {
4039
4172
  user: n.user,
4040
4173
  password: n.password,
4041
4174
  database: n.database,
4042
- labelPrefix: n.labelPrefix
4175
+ labelPrefix: n.labelPrefix,
4176
+ emit: (e) => this.emitProgress(e)
4043
4177
  });
4044
4178
  } catch (e) {
4045
4179
  this.emitProgress({
@@ -4236,11 +4370,14 @@ var WorkspaceIndexer = class {
4236
4370
  if (seeds.length >= 4) break;
4237
4371
  }
4238
4372
  if (seeds.length > 0) {
4373
+ const gs = Date.now();
4374
+ this.emitProgress({ type: "workspace/retrieve/graph/start", workspaceRoot: this.workspaceRoot, seeds: seeds.length });
4239
4375
  graphNeighborFiles = await this.graphStore.neighborFiles({
4240
4376
  seeds,
4241
4377
  limit: profile.name === "architecture" ? 16 : 10,
4242
4378
  kinds: ["definition", "reference", "implementation", "typeDefinition"]
4243
4379
  });
4380
+ this.emitProgress({ type: "workspace/retrieve/graph/done", workspaceRoot: this.workspaceRoot, neighbors: graphNeighborFiles.length, ms: Date.now() - gs });
4244
4381
  }
4245
4382
  }
4246
4383
  } catch {