@neuralsea/workspace-indexer 0.3.4 → 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.
@@ -1719,6 +1719,17 @@ var RepoFileIndexer = class {
1719
1719
  if (cachedEmb) {
1720
1720
  embeddings[i] = cachedEmb;
1721
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
+ });
1722
1733
  continue;
1723
1734
  }
1724
1735
  embedTexts.push(
@@ -1730,7 +1741,7 @@ lines:${ch.startLine}-${ch.endLine}
1730
1741
  ---
1731
1742
  ${ch.text}`
1732
1743
  );
1733
- embedPlan.push({ chunkIdx: i, contentHash: ch.contentHash });
1744
+ embedPlan.push({ chunkIdx: i, contentHash: ch.contentHash, kind: ch.kind, startLine: ch.startLine, endLine: ch.endLine });
1734
1745
  }
1735
1746
  const batchSize = this.config.embed.batchSize;
1736
1747
  for (let start = 0; start < embedTexts.length; start += batchSize) {
@@ -1748,6 +1759,17 @@ ${ch.text}`
1748
1759
  const plan = embedPlan[start + j];
1749
1760
  embeddings[plan.chunkIdx] = vecs[j];
1750
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
+ });
1751
1773
  }
1752
1774
  }
1753
1775
  this.emit({
@@ -1795,6 +1817,9 @@ ${ch.text}`
1795
1817
  this.workspaceStore?.replaceChunksForFile(this.repoId, this.repoRoot, posixRelPath, rows);
1796
1818
  stage("store", storeStartedAt);
1797
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
+ }
1798
1823
  const symbolOut = await this.indexSymbolsIfEnabled(posixRelPath, language, redacted, fileHash);
1799
1824
  this.emit({
1800
1825
  type: "repo/index/file/symbols",
@@ -2449,18 +2474,32 @@ var RepoIndexer = class {
2449
2474
  const lang = languageFromPath(p);
2450
2475
  const edges = await provider.expandDocumentEdges({ repoRoot: this.repoRoot, path: p, language: lang, text, contentHash }, { signal: opts?.signal }).catch(() => []);
2451
2476
  const normalized = (Array.isArray(edges) ? edges : []).map((e) => ({
2452
- fromId: e.fromId,
2453
- toId: e.toId,
2454
- kind: e.kind,
2455
- toPath: String(e.toPath ?? "")
2477
+ fromId: String(e?.fromId ?? ""),
2478
+ toId: String(e?.toId ?? ""),
2479
+ kind: String(e?.kind ?? ""),
2480
+ toPath: String(e?.toPath ?? "")
2456
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
+ }
2457
2489
  this.workspaceStore.replaceSymbolEdgesFromFile(this.repoId, p, normalized);
2458
2490
  await this.graphStore?.replaceOutgoingSymbolEdgesFromFile?.({
2459
2491
  repoId: this.repoId,
2460
2492
  fromPath: p,
2461
2493
  edges: normalized.map((e) => ({ fromId: e.fromId, toId: e.toId, kind: e.kind, toPath: e.toPath }))
2462
2494
  }).catch(() => void 0);
2463
- this.emitProgress({ type: "repo/symbolGraph/expand/done", repoRoot: this.repoRoot, path: p, edges: normalized.length, ms: Date.now() - startedAt });
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
+ });
2464
2503
  }
2465
2504
  }
2466
2505
  async watch() {
@@ -3358,10 +3397,24 @@ var Neo4jGraphStore = class {
3358
3397
  this.driver = driver;
3359
3398
  this.cfg = cfg;
3360
3399
  this.labelPrefix = cfg.labelPrefix ?? "Petri";
3400
+ this.emitProgress = cfg.emit ?? null;
3361
3401
  }
3362
3402
  id = "neo4j";
3363
3403
  labelPrefix;
3364
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
+ }
3365
3418
  labels() {
3366
3419
  return {
3367
3420
  Repo: lp(this.labelPrefix, "Repo"),
@@ -3372,6 +3425,8 @@ var Neo4jGraphStore = class {
3372
3425
  };
3373
3426
  }
3374
3427
  async init() {
3428
+ const startedAt = Date.now();
3429
+ this.opStart("init");
3375
3430
  const { Schema } = this.labels();
3376
3431
  await runSession(this.driver, this.cfg.database, async (s) => {
3377
3432
  await s.run(
@@ -3382,6 +3437,7 @@ var Neo4jGraphStore = class {
3382
3437
  );
3383
3438
  });
3384
3439
  await this.runMigrations().catch(() => void 0);
3440
+ this.opDone("init", startedAt);
3385
3441
  }
3386
3442
  async getSchemaVersion() {
3387
3443
  const { Schema } = this.labels();
@@ -3405,6 +3461,8 @@ var Neo4jGraphStore = class {
3405
3461
  });
3406
3462
  }
3407
3463
  async runMigrations() {
3464
+ const startedAt = Date.now();
3465
+ this.opStart("migrations");
3408
3466
  const { Repo, File, Symbol, External } = this.labels();
3409
3467
  let v = await this.getSchemaVersion();
3410
3468
  if (v < 1) {
@@ -3445,8 +3503,11 @@ var Neo4jGraphStore = class {
3445
3503
  if (v !== this.schemaVersionLatest) {
3446
3504
  await this.setSchemaVersion(this.schemaVersionLatest);
3447
3505
  }
3506
+ this.opDone("migrations", startedAt);
3448
3507
  }
3449
3508
  async setRepoHead(args) {
3509
+ const startedAt = Date.now();
3510
+ this.opStart("setRepoHead", { repoId: args.repoId });
3450
3511
  const { Repo, File, Symbol, External } = this.labels();
3451
3512
  const { repoId, repoRoot, commit, branch } = args;
3452
3513
  await runSession(this.driver, this.cfg.database, async (s) => {
@@ -3477,15 +3538,21 @@ var Neo4jGraphStore = class {
3477
3538
  await s.run(`MATCH (r:${Repo} {repo_id:$repoId}) SET r.commit=$commit`, { repoId, commit });
3478
3539
  }
3479
3540
  });
3541
+ this.opDone("setRepoHead", startedAt, { repoId: args.repoId });
3480
3542
  }
3481
3543
  async deleteFile(args) {
3544
+ const startedAt = Date.now();
3545
+ this.opStart("deleteFile", { repoId: args.repoId, path: args.path });
3482
3546
  const { File, Symbol } = this.labels();
3483
3547
  await runSession(this.driver, this.cfg.database, async (s) => {
3484
3548
  await s.run(`MATCH (s:${Symbol} {repo_id:$repoId, path:$path}) DETACH DELETE s`, args);
3485
3549
  await s.run(`MATCH (f:${File} {repo_id:$repoId, path:$path}) DETACH DELETE f`, args);
3486
3550
  });
3551
+ this.opDone("deleteFile", startedAt, { repoId: args.repoId, path: args.path });
3487
3552
  }
3488
3553
  async replaceOutgoingSymbolEdgesFromFile(args) {
3554
+ const startedAt = Date.now();
3555
+ this.opStart("replaceOutgoingSymbolEdgesFromFile", { repoId: args.repoId, path: args.fromPath });
3489
3556
  const { Symbol } = this.labels();
3490
3557
  const kinds = args.edges.length > 0 ? Array.from(new Set(args.edges.map((e) => e.kind))) : ["definition", "reference", "implementation", "typeDefinition"];
3491
3558
  const keep = args.edges.map((e) => `${e.fromId}|${e.kind}|${e.toId}`);
@@ -3517,8 +3584,11 @@ var Neo4jGraphStore = class {
3517
3584
  { repoId: args.repoId, fromPath: args.fromPath, kinds, keep }
3518
3585
  ).catch(() => void 0);
3519
3586
  });
3587
+ this.opDone("replaceOutgoingSymbolEdgesFromFile", startedAt, { repoId: args.repoId, path: args.fromPath });
3520
3588
  }
3521
3589
  async replaceFileGraph(update) {
3590
+ const startedAt = Date.now();
3591
+ this.opStart("replaceFileGraph", { repoId: update.repoId, path: update.path });
3522
3592
  const { Repo, File, Symbol, External } = this.labels();
3523
3593
  const symbols = update.symbols.map((s) => ({
3524
3594
  id: s.id,
@@ -3630,8 +3700,11 @@ var Neo4jGraphStore = class {
3630
3700
  }
3631
3701
  }
3632
3702
  });
3703
+ this.opDone("replaceFileGraph", startedAt, { repoId: update.repoId, path: update.path });
3633
3704
  }
3634
3705
  async replaceRepoLinks(args) {
3706
+ const startedAt = Date.now();
3707
+ this.opStart("replaceRepoLinks");
3635
3708
  const { Repo } = this.labels();
3636
3709
  const byFrom = /* @__PURE__ */ new Map();
3637
3710
  for (const l of args.links) {
@@ -3662,8 +3735,11 @@ var Neo4jGraphStore = class {
3662
3735
  ).catch(() => void 0);
3663
3736
  });
3664
3737
  }
3738
+ this.opDone("replaceRepoLinks", startedAt);
3665
3739
  }
3666
3740
  async neighborFiles(args) {
3741
+ const startedAt = Date.now();
3742
+ this.opStart("neighborFiles");
3667
3743
  const { File, Symbol } = this.labels();
3668
3744
  const limit = args.limit ?? 20;
3669
3745
  const kinds = args.kinds && args.kinds.length > 0 ? args.kinds : null;
@@ -3687,9 +3763,12 @@ var Neo4jGraphStore = class {
3687
3763
  rows.push({ repoId, path: p, weight: Number.isFinite(w) ? w : 0 });
3688
3764
  }
3689
3765
  });
3766
+ this.opDone("neighborFiles", startedAt);
3690
3767
  return rows;
3691
3768
  }
3692
3769
  async shortestFilePath(args) {
3770
+ const startedAt = Date.now();
3771
+ this.opStart("shortestFilePath");
3693
3772
  const { File } = this.labels();
3694
3773
  const maxRels = args.maxRels ?? 16;
3695
3774
  let out = null;
@@ -3705,9 +3784,12 @@ var Neo4jGraphStore = class {
3705
3784
  if (!Array.isArray(files)) return;
3706
3785
  out = files.map((x) => ({ repoId: String(x.repoId), path: String(x.path) }));
3707
3786
  });
3787
+ this.opDone("shortestFilePath", startedAt);
3708
3788
  return out;
3709
3789
  }
3710
3790
  async extractFileSubgraph(args) {
3791
+ const startedAt = Date.now();
3792
+ this.opStart("extractFileSubgraph");
3711
3793
  const { File, Symbol } = this.labels();
3712
3794
  const limitEdges = args.limitEdges ?? 200;
3713
3795
  const nodes = /* @__PURE__ */ new Map();
@@ -3729,6 +3811,7 @@ var Neo4jGraphStore = class {
3729
3811
  edges.push({ from, to, kind });
3730
3812
  }
3731
3813
  });
3814
+ this.opDone("extractFileSubgraph", startedAt);
3732
3815
  return { nodes: Array.from(nodes.values()), edges };
3733
3816
  }
3734
3817
  async close() {
@@ -4089,7 +4172,8 @@ var WorkspaceIndexer = class {
4089
4172
  user: n.user,
4090
4173
  password: n.password,
4091
4174
  database: n.database,
4092
- labelPrefix: n.labelPrefix
4175
+ labelPrefix: n.labelPrefix,
4176
+ emit: (e) => this.emitProgress(e)
4093
4177
  });
4094
4178
  } catch (e) {
4095
4179
  this.emitProgress({
package/dist/cli.cjs CHANGED
@@ -1702,6 +1702,17 @@ var RepoFileIndexer = class {
1702
1702
  if (cachedEmb) {
1703
1703
  embeddings[i] = cachedEmb;
1704
1704
  cached2++;
1705
+ this.emit({
1706
+ type: "repo/index/file/embed/chunk",
1707
+ repoRoot: this.repoRoot,
1708
+ path: posixRelPath,
1709
+ chunkIndex: i,
1710
+ chunksTotal: combined.length,
1711
+ kind: ch.kind,
1712
+ startLine: ch.startLine,
1713
+ endLine: ch.endLine,
1714
+ cached: true
1715
+ });
1705
1716
  continue;
1706
1717
  }
1707
1718
  embedTexts.push(
@@ -1713,7 +1724,7 @@ lines:${ch.startLine}-${ch.endLine}
1713
1724
  ---
1714
1725
  ${ch.text}`
1715
1726
  );
1716
- embedPlan.push({ chunkIdx: i, contentHash: ch.contentHash });
1727
+ embedPlan.push({ chunkIdx: i, contentHash: ch.contentHash, kind: ch.kind, startLine: ch.startLine, endLine: ch.endLine });
1717
1728
  }
1718
1729
  const batchSize = this.config.embed.batchSize;
1719
1730
  for (let start = 0; start < embedTexts.length; start += batchSize) {
@@ -1731,6 +1742,17 @@ ${ch.text}`
1731
1742
  const plan = embedPlan[start + j];
1732
1743
  embeddings[plan.chunkIdx] = vecs[j];
1733
1744
  this.embeddingCache.put(this.embedder.id, plan.contentHash, vecs[j]);
1745
+ this.emit({
1746
+ type: "repo/index/file/embed/chunk",
1747
+ repoRoot: this.repoRoot,
1748
+ path: posixRelPath,
1749
+ chunkIndex: plan.chunkIdx,
1750
+ chunksTotal: combined.length,
1751
+ kind: plan.kind,
1752
+ startLine: plan.startLine,
1753
+ endLine: plan.endLine,
1754
+ cached: false
1755
+ });
1734
1756
  }
1735
1757
  }
1736
1758
  this.emit({
@@ -1778,6 +1800,9 @@ ${ch.text}`
1778
1800
  this.workspaceStore?.replaceChunksForFile(this.repoId, this.repoRoot, posixRelPath, rows);
1779
1801
  stage("store", storeStartedAt);
1780
1802
  const symbolsStartedAt = Date.now();
1803
+ if (this.symbolGraphProvider && this.symbolGraphProvider.supports(language)) {
1804
+ this.emit({ type: "repo/index/file/symbols/start", repoRoot: this.repoRoot, path: posixRelPath, providerId: this.symbolGraphProvider.id });
1805
+ }
1781
1806
  const symbolOut = await this.indexSymbolsIfEnabled(posixRelPath, language, redacted, fileHash);
1782
1807
  this.emit({
1783
1808
  type: "repo/index/file/symbols",
@@ -2461,18 +2486,32 @@ var RepoIndexer = class {
2461
2486
  const lang = languageFromPath(p);
2462
2487
  const edges = await provider.expandDocumentEdges({ repoRoot: this.repoRoot, path: p, language: lang, text, contentHash }, { signal: opts?.signal }).catch(() => []);
2463
2488
  const normalized = (Array.isArray(edges) ? edges : []).map((e) => ({
2464
- fromId: e.fromId,
2465
- toId: e.toId,
2466
- kind: e.kind,
2467
- toPath: String(e.toPath ?? "")
2489
+ fromId: String(e?.fromId ?? ""),
2490
+ toId: String(e?.toId ?? ""),
2491
+ kind: String(e?.kind ?? ""),
2492
+ toPath: String(e?.toPath ?? "")
2468
2493
  }));
2494
+ const byKind = {};
2495
+ for (const e of normalized) {
2496
+ const kind = e.kind;
2497
+ if (kind === "definition" || kind === "reference" || kind === "implementation" || kind === "typeDefinition") {
2498
+ byKind[kind] = (byKind[kind] ?? 0) + 1;
2499
+ }
2500
+ }
2469
2501
  this.workspaceStore.replaceSymbolEdgesFromFile(this.repoId, p, normalized);
2470
2502
  await this.graphStore?.replaceOutgoingSymbolEdgesFromFile?.({
2471
2503
  repoId: this.repoId,
2472
2504
  fromPath: p,
2473
2505
  edges: normalized.map((e) => ({ fromId: e.fromId, toId: e.toId, kind: e.kind, toPath: e.toPath }))
2474
2506
  }).catch(() => void 0);
2475
- this.emitProgress({ type: "repo/symbolGraph/expand/done", repoRoot: this.repoRoot, path: p, edges: normalized.length, ms: Date.now() - startedAt });
2507
+ this.emitProgress({
2508
+ type: "repo/symbolGraph/expand/done",
2509
+ repoRoot: this.repoRoot,
2510
+ path: p,
2511
+ edges: normalized.length,
2512
+ byKind,
2513
+ ms: Date.now() - startedAt
2514
+ });
2476
2515
  }
2477
2516
  }
2478
2517
  async watch() {
@@ -3371,10 +3410,24 @@ var Neo4jGraphStore = class {
3371
3410
  this.driver = driver;
3372
3411
  this.cfg = cfg;
3373
3412
  this.labelPrefix = cfg.labelPrefix ?? "Petri";
3413
+ this.emitProgress = cfg.emit ?? null;
3374
3414
  }
3375
3415
  id = "neo4j";
3376
3416
  labelPrefix;
3377
3417
  schemaVersionLatest = 2;
3418
+ emitProgress;
3419
+ opStart(op, args) {
3420
+ try {
3421
+ this.emitProgress?.({ type: "graph/neo4j/op/start", op, repoId: args?.repoId, path: args?.path });
3422
+ } catch {
3423
+ }
3424
+ }
3425
+ opDone(op, startedAt, args) {
3426
+ try {
3427
+ this.emitProgress?.({ type: "graph/neo4j/op/done", op, repoId: args?.repoId, path: args?.path, ms: Date.now() - startedAt });
3428
+ } catch {
3429
+ }
3430
+ }
3378
3431
  labels() {
3379
3432
  return {
3380
3433
  Repo: lp(this.labelPrefix, "Repo"),
@@ -3385,6 +3438,8 @@ var Neo4jGraphStore = class {
3385
3438
  };
3386
3439
  }
3387
3440
  async init() {
3441
+ const startedAt = Date.now();
3442
+ this.opStart("init");
3388
3443
  const { Schema } = this.labels();
3389
3444
  await runSession(this.driver, this.cfg.database, async (s) => {
3390
3445
  await s.run(
@@ -3395,6 +3450,7 @@ var Neo4jGraphStore = class {
3395
3450
  );
3396
3451
  });
3397
3452
  await this.runMigrations().catch(() => void 0);
3453
+ this.opDone("init", startedAt);
3398
3454
  }
3399
3455
  async getSchemaVersion() {
3400
3456
  const { Schema } = this.labels();
@@ -3418,6 +3474,8 @@ var Neo4jGraphStore = class {
3418
3474
  });
3419
3475
  }
3420
3476
  async runMigrations() {
3477
+ const startedAt = Date.now();
3478
+ this.opStart("migrations");
3421
3479
  const { Repo, File, Symbol: Symbol2, External } = this.labels();
3422
3480
  let v = await this.getSchemaVersion();
3423
3481
  if (v < 1) {
@@ -3458,8 +3516,11 @@ var Neo4jGraphStore = class {
3458
3516
  if (v !== this.schemaVersionLatest) {
3459
3517
  await this.setSchemaVersion(this.schemaVersionLatest);
3460
3518
  }
3519
+ this.opDone("migrations", startedAt);
3461
3520
  }
3462
3521
  async setRepoHead(args) {
3522
+ const startedAt = Date.now();
3523
+ this.opStart("setRepoHead", { repoId: args.repoId });
3463
3524
  const { Repo, File, Symbol: Symbol2, External } = this.labels();
3464
3525
  const { repoId, repoRoot, commit, branch } = args;
3465
3526
  await runSession(this.driver, this.cfg.database, async (s) => {
@@ -3490,15 +3551,21 @@ var Neo4jGraphStore = class {
3490
3551
  await s.run(`MATCH (r:${Repo} {repo_id:$repoId}) SET r.commit=$commit`, { repoId, commit });
3491
3552
  }
3492
3553
  });
3554
+ this.opDone("setRepoHead", startedAt, { repoId: args.repoId });
3493
3555
  }
3494
3556
  async deleteFile(args) {
3557
+ const startedAt = Date.now();
3558
+ this.opStart("deleteFile", { repoId: args.repoId, path: args.path });
3495
3559
  const { File, Symbol: Symbol2 } = this.labels();
3496
3560
  await runSession(this.driver, this.cfg.database, async (s) => {
3497
3561
  await s.run(`MATCH (s:${Symbol2} {repo_id:$repoId, path:$path}) DETACH DELETE s`, args);
3498
3562
  await s.run(`MATCH (f:${File} {repo_id:$repoId, path:$path}) DETACH DELETE f`, args);
3499
3563
  });
3564
+ this.opDone("deleteFile", startedAt, { repoId: args.repoId, path: args.path });
3500
3565
  }
3501
3566
  async replaceOutgoingSymbolEdgesFromFile(args) {
3567
+ const startedAt = Date.now();
3568
+ this.opStart("replaceOutgoingSymbolEdgesFromFile", { repoId: args.repoId, path: args.fromPath });
3502
3569
  const { Symbol: Symbol2 } = this.labels();
3503
3570
  const kinds = args.edges.length > 0 ? Array.from(new Set(args.edges.map((e) => e.kind))) : ["definition", "reference", "implementation", "typeDefinition"];
3504
3571
  const keep = args.edges.map((e) => `${e.fromId}|${e.kind}|${e.toId}`);
@@ -3530,8 +3597,11 @@ var Neo4jGraphStore = class {
3530
3597
  { repoId: args.repoId, fromPath: args.fromPath, kinds, keep }
3531
3598
  ).catch(() => void 0);
3532
3599
  });
3600
+ this.opDone("replaceOutgoingSymbolEdgesFromFile", startedAt, { repoId: args.repoId, path: args.fromPath });
3533
3601
  }
3534
3602
  async replaceFileGraph(update) {
3603
+ const startedAt = Date.now();
3604
+ this.opStart("replaceFileGraph", { repoId: update.repoId, path: update.path });
3535
3605
  const { Repo, File, Symbol: Symbol2, External } = this.labels();
3536
3606
  const symbols = update.symbols.map((s) => ({
3537
3607
  id: s.id,
@@ -3643,8 +3713,11 @@ var Neo4jGraphStore = class {
3643
3713
  }
3644
3714
  }
3645
3715
  });
3716
+ this.opDone("replaceFileGraph", startedAt, { repoId: update.repoId, path: update.path });
3646
3717
  }
3647
3718
  async replaceRepoLinks(args) {
3719
+ const startedAt = Date.now();
3720
+ this.opStart("replaceRepoLinks");
3648
3721
  const { Repo } = this.labels();
3649
3722
  const byFrom = /* @__PURE__ */ new Map();
3650
3723
  for (const l of args.links) {
@@ -3675,8 +3748,11 @@ var Neo4jGraphStore = class {
3675
3748
  ).catch(() => void 0);
3676
3749
  });
3677
3750
  }
3751
+ this.opDone("replaceRepoLinks", startedAt);
3678
3752
  }
3679
3753
  async neighborFiles(args) {
3754
+ const startedAt = Date.now();
3755
+ this.opStart("neighborFiles");
3680
3756
  const { File, Symbol: Symbol2 } = this.labels();
3681
3757
  const limit = args.limit ?? 20;
3682
3758
  const kinds = args.kinds && args.kinds.length > 0 ? args.kinds : null;
@@ -3700,9 +3776,12 @@ var Neo4jGraphStore = class {
3700
3776
  rows.push({ repoId, path: p, weight: Number.isFinite(w) ? w : 0 });
3701
3777
  }
3702
3778
  });
3779
+ this.opDone("neighborFiles", startedAt);
3703
3780
  return rows;
3704
3781
  }
3705
3782
  async shortestFilePath(args) {
3783
+ const startedAt = Date.now();
3784
+ this.opStart("shortestFilePath");
3706
3785
  const { File } = this.labels();
3707
3786
  const maxRels = args.maxRels ?? 16;
3708
3787
  let out = null;
@@ -3718,9 +3797,12 @@ var Neo4jGraphStore = class {
3718
3797
  if (!Array.isArray(files)) return;
3719
3798
  out = files.map((x) => ({ repoId: String(x.repoId), path: String(x.path) }));
3720
3799
  });
3800
+ this.opDone("shortestFilePath", startedAt);
3721
3801
  return out;
3722
3802
  }
3723
3803
  async extractFileSubgraph(args) {
3804
+ const startedAt = Date.now();
3805
+ this.opStart("extractFileSubgraph");
3724
3806
  const { File, Symbol: Symbol2 } = this.labels();
3725
3807
  const limitEdges = args.limitEdges ?? 200;
3726
3808
  const nodes = /* @__PURE__ */ new Map();
@@ -3742,6 +3824,7 @@ var Neo4jGraphStore = class {
3742
3824
  edges.push({ from, to, kind });
3743
3825
  }
3744
3826
  });
3827
+ this.opDone("extractFileSubgraph", startedAt);
3745
3828
  return { nodes: Array.from(nodes.values()), edges };
3746
3829
  }
3747
3830
  async close() {
@@ -4101,7 +4184,8 @@ var WorkspaceIndexer = class {
4101
4184
  user: n.user,
4102
4185
  password: n.password,
4103
4186
  database: n.database,
4104
- labelPrefix: n.labelPrefix
4187
+ labelPrefix: n.labelPrefix,
4188
+ emit: (e) => this.emitProgress(e)
4105
4189
  });
4106
4190
  } catch (e) {
4107
4191
  this.emitProgress({
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  OpenAIEmbeddingsProvider,
6
6
  WorkspaceIndexer,
7
7
  loadConfigFile
8
- } from "./chunk-ENM3P2PS.js";
8
+ } from "./chunk-FUUQXFJQ.js";
9
9
 
10
10
  // src/cli.ts
11
11
  import yargs from "yargs";
package/dist/index.cjs CHANGED
@@ -2271,10 +2271,24 @@ var Neo4jGraphStore = class {
2271
2271
  this.driver = driver;
2272
2272
  this.cfg = cfg;
2273
2273
  this.labelPrefix = cfg.labelPrefix ?? "Petri";
2274
+ this.emitProgress = cfg.emit ?? null;
2274
2275
  }
2275
2276
  id = "neo4j";
2276
2277
  labelPrefix;
2277
2278
  schemaVersionLatest = 2;
2279
+ emitProgress;
2280
+ opStart(op, args) {
2281
+ try {
2282
+ this.emitProgress?.({ type: "graph/neo4j/op/start", op, repoId: args?.repoId, path: args?.path });
2283
+ } catch {
2284
+ }
2285
+ }
2286
+ opDone(op, startedAt, args) {
2287
+ try {
2288
+ this.emitProgress?.({ type: "graph/neo4j/op/done", op, repoId: args?.repoId, path: args?.path, ms: Date.now() - startedAt });
2289
+ } catch {
2290
+ }
2291
+ }
2278
2292
  labels() {
2279
2293
  return {
2280
2294
  Repo: lp(this.labelPrefix, "Repo"),
@@ -2285,6 +2299,8 @@ var Neo4jGraphStore = class {
2285
2299
  };
2286
2300
  }
2287
2301
  async init() {
2302
+ const startedAt = Date.now();
2303
+ this.opStart("init");
2288
2304
  const { Schema } = this.labels();
2289
2305
  await runSession(this.driver, this.cfg.database, async (s) => {
2290
2306
  await s.run(
@@ -2295,6 +2311,7 @@ var Neo4jGraphStore = class {
2295
2311
  );
2296
2312
  });
2297
2313
  await this.runMigrations().catch(() => void 0);
2314
+ this.opDone("init", startedAt);
2298
2315
  }
2299
2316
  async getSchemaVersion() {
2300
2317
  const { Schema } = this.labels();
@@ -2318,6 +2335,8 @@ var Neo4jGraphStore = class {
2318
2335
  });
2319
2336
  }
2320
2337
  async runMigrations() {
2338
+ const startedAt = Date.now();
2339
+ this.opStart("migrations");
2321
2340
  const { Repo, File, Symbol: Symbol2, External } = this.labels();
2322
2341
  let v = await this.getSchemaVersion();
2323
2342
  if (v < 1) {
@@ -2358,8 +2377,11 @@ var Neo4jGraphStore = class {
2358
2377
  if (v !== this.schemaVersionLatest) {
2359
2378
  await this.setSchemaVersion(this.schemaVersionLatest);
2360
2379
  }
2380
+ this.opDone("migrations", startedAt);
2361
2381
  }
2362
2382
  async setRepoHead(args) {
2383
+ const startedAt = Date.now();
2384
+ this.opStart("setRepoHead", { repoId: args.repoId });
2363
2385
  const { Repo, File, Symbol: Symbol2, External } = this.labels();
2364
2386
  const { repoId, repoRoot, commit, branch } = args;
2365
2387
  await runSession(this.driver, this.cfg.database, async (s) => {
@@ -2390,15 +2412,21 @@ var Neo4jGraphStore = class {
2390
2412
  await s.run(`MATCH (r:${Repo} {repo_id:$repoId}) SET r.commit=$commit`, { repoId, commit });
2391
2413
  }
2392
2414
  });
2415
+ this.opDone("setRepoHead", startedAt, { repoId: args.repoId });
2393
2416
  }
2394
2417
  async deleteFile(args) {
2418
+ const startedAt = Date.now();
2419
+ this.opStart("deleteFile", { repoId: args.repoId, path: args.path });
2395
2420
  const { File, Symbol: Symbol2 } = this.labels();
2396
2421
  await runSession(this.driver, this.cfg.database, async (s) => {
2397
2422
  await s.run(`MATCH (s:${Symbol2} {repo_id:$repoId, path:$path}) DETACH DELETE s`, args);
2398
2423
  await s.run(`MATCH (f:${File} {repo_id:$repoId, path:$path}) DETACH DELETE f`, args);
2399
2424
  });
2425
+ this.opDone("deleteFile", startedAt, { repoId: args.repoId, path: args.path });
2400
2426
  }
2401
2427
  async replaceOutgoingSymbolEdgesFromFile(args) {
2428
+ const startedAt = Date.now();
2429
+ this.opStart("replaceOutgoingSymbolEdgesFromFile", { repoId: args.repoId, path: args.fromPath });
2402
2430
  const { Symbol: Symbol2 } = this.labels();
2403
2431
  const kinds = args.edges.length > 0 ? Array.from(new Set(args.edges.map((e) => e.kind))) : ["definition", "reference", "implementation", "typeDefinition"];
2404
2432
  const keep = args.edges.map((e) => `${e.fromId}|${e.kind}|${e.toId}`);
@@ -2430,8 +2458,11 @@ var Neo4jGraphStore = class {
2430
2458
  { repoId: args.repoId, fromPath: args.fromPath, kinds, keep }
2431
2459
  ).catch(() => void 0);
2432
2460
  });
2461
+ this.opDone("replaceOutgoingSymbolEdgesFromFile", startedAt, { repoId: args.repoId, path: args.fromPath });
2433
2462
  }
2434
2463
  async replaceFileGraph(update) {
2464
+ const startedAt = Date.now();
2465
+ this.opStart("replaceFileGraph", { repoId: update.repoId, path: update.path });
2435
2466
  const { Repo, File, Symbol: Symbol2, External } = this.labels();
2436
2467
  const symbols = update.symbols.map((s) => ({
2437
2468
  id: s.id,
@@ -2543,8 +2574,11 @@ var Neo4jGraphStore = class {
2543
2574
  }
2544
2575
  }
2545
2576
  });
2577
+ this.opDone("replaceFileGraph", startedAt, { repoId: update.repoId, path: update.path });
2546
2578
  }
2547
2579
  async replaceRepoLinks(args) {
2580
+ const startedAt = Date.now();
2581
+ this.opStart("replaceRepoLinks");
2548
2582
  const { Repo } = this.labels();
2549
2583
  const byFrom = /* @__PURE__ */ new Map();
2550
2584
  for (const l of args.links) {
@@ -2575,8 +2609,11 @@ var Neo4jGraphStore = class {
2575
2609
  ).catch(() => void 0);
2576
2610
  });
2577
2611
  }
2612
+ this.opDone("replaceRepoLinks", startedAt);
2578
2613
  }
2579
2614
  async neighborFiles(args) {
2615
+ const startedAt = Date.now();
2616
+ this.opStart("neighborFiles");
2580
2617
  const { File, Symbol: Symbol2 } = this.labels();
2581
2618
  const limit = args.limit ?? 20;
2582
2619
  const kinds = args.kinds && args.kinds.length > 0 ? args.kinds : null;
@@ -2600,9 +2637,12 @@ var Neo4jGraphStore = class {
2600
2637
  rows.push({ repoId, path: p, weight: Number.isFinite(w) ? w : 0 });
2601
2638
  }
2602
2639
  });
2640
+ this.opDone("neighborFiles", startedAt);
2603
2641
  return rows;
2604
2642
  }
2605
2643
  async shortestFilePath(args) {
2644
+ const startedAt = Date.now();
2645
+ this.opStart("shortestFilePath");
2606
2646
  const { File } = this.labels();
2607
2647
  const maxRels = args.maxRels ?? 16;
2608
2648
  let out = null;
@@ -2618,9 +2658,12 @@ var Neo4jGraphStore = class {
2618
2658
  if (!Array.isArray(files)) return;
2619
2659
  out = files.map((x) => ({ repoId: String(x.repoId), path: String(x.path) }));
2620
2660
  });
2661
+ this.opDone("shortestFilePath", startedAt);
2621
2662
  return out;
2622
2663
  }
2623
2664
  async extractFileSubgraph(args) {
2665
+ const startedAt = Date.now();
2666
+ this.opStart("extractFileSubgraph");
2624
2667
  const { File, Symbol: Symbol2 } = this.labels();
2625
2668
  const limitEdges = args.limitEdges ?? 200;
2626
2669
  const nodes = /* @__PURE__ */ new Map();
@@ -2642,6 +2685,7 @@ var Neo4jGraphStore = class {
2642
2685
  edges.push({ from, to, kind });
2643
2686
  }
2644
2687
  });
2688
+ this.opDone("extractFileSubgraph", startedAt);
2645
2689
  return { nodes: Array.from(nodes.values()), edges };
2646
2690
  }
2647
2691
  async close() {
@@ -3506,6 +3550,17 @@ var RepoFileIndexer = class {
3506
3550
  if (cachedEmb) {
3507
3551
  embeddings[i] = cachedEmb;
3508
3552
  cached2++;
3553
+ this.emit({
3554
+ type: "repo/index/file/embed/chunk",
3555
+ repoRoot: this.repoRoot,
3556
+ path: posixRelPath,
3557
+ chunkIndex: i,
3558
+ chunksTotal: combined.length,
3559
+ kind: ch.kind,
3560
+ startLine: ch.startLine,
3561
+ endLine: ch.endLine,
3562
+ cached: true
3563
+ });
3509
3564
  continue;
3510
3565
  }
3511
3566
  embedTexts.push(
@@ -3517,7 +3572,7 @@ lines:${ch.startLine}-${ch.endLine}
3517
3572
  ---
3518
3573
  ${ch.text}`
3519
3574
  );
3520
- embedPlan.push({ chunkIdx: i, contentHash: ch.contentHash });
3575
+ embedPlan.push({ chunkIdx: i, contentHash: ch.contentHash, kind: ch.kind, startLine: ch.startLine, endLine: ch.endLine });
3521
3576
  }
3522
3577
  const batchSize = this.config.embed.batchSize;
3523
3578
  for (let start = 0; start < embedTexts.length; start += batchSize) {
@@ -3535,6 +3590,17 @@ ${ch.text}`
3535
3590
  const plan = embedPlan[start + j];
3536
3591
  embeddings[plan.chunkIdx] = vecs[j];
3537
3592
  this.embeddingCache.put(this.embedder.id, plan.contentHash, vecs[j]);
3593
+ this.emit({
3594
+ type: "repo/index/file/embed/chunk",
3595
+ repoRoot: this.repoRoot,
3596
+ path: posixRelPath,
3597
+ chunkIndex: plan.chunkIdx,
3598
+ chunksTotal: combined.length,
3599
+ kind: plan.kind,
3600
+ startLine: plan.startLine,
3601
+ endLine: plan.endLine,
3602
+ cached: false
3603
+ });
3538
3604
  }
3539
3605
  }
3540
3606
  this.emit({
@@ -3582,6 +3648,9 @@ ${ch.text}`
3582
3648
  this.workspaceStore?.replaceChunksForFile(this.repoId, this.repoRoot, posixRelPath, rows);
3583
3649
  stage("store", storeStartedAt);
3584
3650
  const symbolsStartedAt = Date.now();
3651
+ if (this.symbolGraphProvider && this.symbolGraphProvider.supports(language)) {
3652
+ this.emit({ type: "repo/index/file/symbols/start", repoRoot: this.repoRoot, path: posixRelPath, providerId: this.symbolGraphProvider.id });
3653
+ }
3585
3654
  const symbolOut = await this.indexSymbolsIfEnabled(posixRelPath, language, redacted, fileHash);
3586
3655
  this.emit({
3587
3656
  type: "repo/index/file/symbols",
@@ -4236,18 +4305,32 @@ var RepoIndexer = class {
4236
4305
  const lang = languageFromPath(p);
4237
4306
  const edges = await provider.expandDocumentEdges({ repoRoot: this.repoRoot, path: p, language: lang, text, contentHash }, { signal: opts?.signal }).catch(() => []);
4238
4307
  const normalized = (Array.isArray(edges) ? edges : []).map((e) => ({
4239
- fromId: e.fromId,
4240
- toId: e.toId,
4241
- kind: e.kind,
4242
- toPath: String(e.toPath ?? "")
4308
+ fromId: String(e?.fromId ?? ""),
4309
+ toId: String(e?.toId ?? ""),
4310
+ kind: String(e?.kind ?? ""),
4311
+ toPath: String(e?.toPath ?? "")
4243
4312
  }));
4313
+ const byKind = {};
4314
+ for (const e of normalized) {
4315
+ const kind = e.kind;
4316
+ if (kind === "definition" || kind === "reference" || kind === "implementation" || kind === "typeDefinition") {
4317
+ byKind[kind] = (byKind[kind] ?? 0) + 1;
4318
+ }
4319
+ }
4244
4320
  this.workspaceStore.replaceSymbolEdgesFromFile(this.repoId, p, normalized);
4245
4321
  await this.graphStore?.replaceOutgoingSymbolEdgesFromFile?.({
4246
4322
  repoId: this.repoId,
4247
4323
  fromPath: p,
4248
4324
  edges: normalized.map((e) => ({ fromId: e.fromId, toId: e.toId, kind: e.kind, toPath: e.toPath }))
4249
4325
  }).catch(() => void 0);
4250
- this.emitProgress({ type: "repo/symbolGraph/expand/done", repoRoot: this.repoRoot, path: p, edges: normalized.length, ms: Date.now() - startedAt });
4326
+ this.emitProgress({
4327
+ type: "repo/symbolGraph/expand/done",
4328
+ repoRoot: this.repoRoot,
4329
+ path: p,
4330
+ edges: normalized.length,
4331
+ byKind,
4332
+ ms: Date.now() - startedAt
4333
+ });
4251
4334
  }
4252
4335
  }
4253
4336
  async watch() {
@@ -4637,7 +4720,8 @@ var WorkspaceIndexer = class {
4637
4720
  user: n.user,
4638
4721
  password: n.password,
4639
4722
  database: n.database,
4640
- labelPrefix: n.labelPrefix
4723
+ labelPrefix: n.labelPrefix,
4724
+ emit: (e) => this.emitProgress(e)
4641
4725
  });
4642
4726
  } catch (e) {
4643
4727
  this.emitProgress({
package/dist/index.d.cts CHANGED
@@ -287,6 +287,16 @@ type IndexerProgressEvent = {
287
287
  repoRoot: string;
288
288
  path: string;
289
289
  reason: string;
290
+ } | {
291
+ type: "repo/index/file/embed/chunk";
292
+ repoRoot: string;
293
+ path: string;
294
+ chunkIndex: number;
295
+ chunksTotal: number;
296
+ kind: "chunk" | "synopsis";
297
+ startLine: number;
298
+ endLine: number;
299
+ cached: boolean;
290
300
  } | {
291
301
  type: "repo/index/file/embed/done";
292
302
  repoRoot: string;
@@ -295,6 +305,11 @@ type IndexerProgressEvent = {
295
305
  cached: number;
296
306
  computed: number;
297
307
  ms: number;
308
+ } | {
309
+ type: "repo/index/file/symbols/start";
310
+ repoRoot: string;
311
+ path: string;
312
+ providerId: string;
298
313
  } | {
299
314
  type: "repo/index/file/symbols";
300
315
  repoRoot: string;
@@ -345,6 +360,7 @@ type IndexerProgressEvent = {
345
360
  repoRoot: string;
346
361
  path: string;
347
362
  edges: number;
363
+ byKind?: Partial<Record<"definition" | "reference" | "implementation" | "typeDefinition", number>>;
348
364
  ms: number;
349
365
  } | {
350
366
  type: "repo/watch/start";
@@ -359,6 +375,17 @@ type IndexerProgressEvent = {
359
375
  repoRoot: string;
360
376
  fileCount: number;
361
377
  ms: number;
378
+ } | {
379
+ type: "graph/neo4j/op/start";
380
+ op: string;
381
+ repoId?: string;
382
+ path?: string;
383
+ } | {
384
+ type: "graph/neo4j/op/done";
385
+ op: string;
386
+ repoId?: string;
387
+ path?: string;
388
+ ms: number;
362
389
  } | {
363
390
  type: "error";
364
391
  scope: "workspace" | "repo";
@@ -1024,6 +1051,7 @@ type Neo4jConfig = {
1024
1051
  password: string;
1025
1052
  database?: string;
1026
1053
  labelPrefix?: string;
1054
+ emit?: (e: IndexerProgressEvent) => void;
1027
1055
  };
1028
1056
  declare class Neo4jGraphStore implements GraphStore {
1029
1057
  private driver;
@@ -1031,7 +1059,10 @@ declare class Neo4jGraphStore implements GraphStore {
1031
1059
  id: string;
1032
1060
  private labelPrefix;
1033
1061
  private schemaVersionLatest;
1062
+ private emitProgress;
1034
1063
  constructor(driver: Neo4jLikeDriver, cfg: Neo4jConfig);
1064
+ private opStart;
1065
+ private opDone;
1035
1066
  private labels;
1036
1067
  init(): Promise<void>;
1037
1068
  private getSchemaVersion;
package/dist/index.d.ts CHANGED
@@ -287,6 +287,16 @@ type IndexerProgressEvent = {
287
287
  repoRoot: string;
288
288
  path: string;
289
289
  reason: string;
290
+ } | {
291
+ type: "repo/index/file/embed/chunk";
292
+ repoRoot: string;
293
+ path: string;
294
+ chunkIndex: number;
295
+ chunksTotal: number;
296
+ kind: "chunk" | "synopsis";
297
+ startLine: number;
298
+ endLine: number;
299
+ cached: boolean;
290
300
  } | {
291
301
  type: "repo/index/file/embed/done";
292
302
  repoRoot: string;
@@ -295,6 +305,11 @@ type IndexerProgressEvent = {
295
305
  cached: number;
296
306
  computed: number;
297
307
  ms: number;
308
+ } | {
309
+ type: "repo/index/file/symbols/start";
310
+ repoRoot: string;
311
+ path: string;
312
+ providerId: string;
298
313
  } | {
299
314
  type: "repo/index/file/symbols";
300
315
  repoRoot: string;
@@ -345,6 +360,7 @@ type IndexerProgressEvent = {
345
360
  repoRoot: string;
346
361
  path: string;
347
362
  edges: number;
363
+ byKind?: Partial<Record<"definition" | "reference" | "implementation" | "typeDefinition", number>>;
348
364
  ms: number;
349
365
  } | {
350
366
  type: "repo/watch/start";
@@ -359,6 +375,17 @@ type IndexerProgressEvent = {
359
375
  repoRoot: string;
360
376
  fileCount: number;
361
377
  ms: number;
378
+ } | {
379
+ type: "graph/neo4j/op/start";
380
+ op: string;
381
+ repoId?: string;
382
+ path?: string;
383
+ } | {
384
+ type: "graph/neo4j/op/done";
385
+ op: string;
386
+ repoId?: string;
387
+ path?: string;
388
+ ms: number;
362
389
  } | {
363
390
  type: "error";
364
391
  scope: "workspace" | "repo";
@@ -1024,6 +1051,7 @@ type Neo4jConfig = {
1024
1051
  password: string;
1025
1052
  database?: string;
1026
1053
  labelPrefix?: string;
1054
+ emit?: (e: IndexerProgressEvent) => void;
1027
1055
  };
1028
1056
  declare class Neo4jGraphStore implements GraphStore {
1029
1057
  private driver;
@@ -1031,7 +1059,10 @@ declare class Neo4jGraphStore implements GraphStore {
1031
1059
  id: string;
1032
1060
  private labelPrefix;
1033
1061
  private schemaVersionLatest;
1062
+ private emitProgress;
1034
1063
  constructor(driver: Neo4jLikeDriver, cfg: Neo4jConfig);
1064
+ private opStart;
1065
+ private opDone;
1035
1066
  private labels;
1036
1067
  init(): Promise<void>;
1037
1068
  private getSchemaVersion;
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  mergeIndexerConfig,
28
28
  pickRepoOverride,
29
29
  stableSymbolId
30
- } from "./chunk-ENM3P2PS.js";
30
+ } from "./chunk-FUUQXFJQ.js";
31
31
 
32
32
  // src/symbolGraph/vscodeProvider.ts
33
33
  import path2 from "path";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neuralsea/workspace-indexer",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Local-first multi-repo workspace indexer (semantic embeddings + git-aware incremental updates + hybrid retrieval profiles) for AI agents.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",