trace-mcp 1.6.0 → 1.7.0

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.ts CHANGED
@@ -194,7 +194,7 @@ interface LanguagePlugin {
194
194
  manifest: PluginManifest;
195
195
  supportedExtensions: string[];
196
196
  supportedVersions?: string[];
197
- extractSymbols(filePath: string, content: Buffer): TraceMcpResult<FileParseResult>;
197
+ extractSymbols(filePath: string, content: Buffer): TraceMcpResult<FileParseResult> | Promise<TraceMcpResult<FileParseResult>>;
198
198
  }
199
199
  interface FrameworkPlugin {
200
200
  manifest: PluginManifest;
@@ -437,6 +437,8 @@ declare class SymbolRepository {
437
437
  getSymbolsByIds(ids: number[]): Map<number, SymbolRow>;
438
438
  findSymbolByRole(name: string, frameworkRole?: string): SymbolRow | undefined;
439
439
  updateSymbolSummary(symbolId: number, summary: string): void;
440
+ countUnsummarizedSymbols(kinds: string[]): number;
441
+ countUnembeddedSymbols(): number;
440
442
  getUnsummarizedSymbols(kinds: string[], limit: number): {
441
443
  id: number;
442
444
  name: string;
@@ -631,6 +633,8 @@ declare class Store {
631
633
  getSymbolsByIds(ids: number[]): Map<number, SymbolRow>;
632
634
  findSymbolByRole(name: string, frameworkRole?: string): SymbolRow | undefined;
633
635
  updateSymbolSummary(symbolId: number, summary: string): void;
636
+ countUnsummarizedSymbols(kinds: string[]): number;
637
+ countUnembeddedSymbols(): number;
634
638
  getUnsummarizedSymbols(kinds: string[], limit: number): {
635
639
  id: number;
636
640
  name: string;
@@ -1904,7 +1908,44 @@ type TraceMcpConfig = z.infer<typeof TraceMcpConfigSchema>;
1904
1908
  */
1905
1909
  declare function loadConfig(searchFrom?: string): Promise<TraceMcpResult<TraceMcpConfig>>;
1906
1910
 
1907
- declare function createServer(store: Store, registry: PluginRegistry, config: TraceMcpConfig, rootPath?: string): McpServer;
1911
+ /**
1912
+ * Indexing progress tracking.
1913
+ * Shared mutable state object — pipelines write, MCP tools + CLI read.
1914
+ * Progress is also persisted to SQLite for cross-process access (CLI `status` command).
1915
+ */
1916
+
1917
+ type PipelinePhase = 'idle' | 'running' | 'completed' | 'error';
1918
+ type PipelineName = 'indexing' | 'summarization' | 'embedding';
1919
+ interface PipelineProgress {
1920
+ phase: PipelinePhase;
1921
+ processed: number;
1922
+ total: number;
1923
+ startedAt: number;
1924
+ completedAt: number;
1925
+ error?: string;
1926
+ }
1927
+ interface PipelineProgressSnapshot extends PipelineProgress {
1928
+ percentage: number | null;
1929
+ elapsedMs: number;
1930
+ }
1931
+ interface ProgressSnapshot {
1932
+ indexing: PipelineProgressSnapshot;
1933
+ summarization: PipelineProgressSnapshot;
1934
+ embedding: PipelineProgressSnapshot;
1935
+ }
1936
+ declare class ProgressState {
1937
+ indexing: PipelineProgress;
1938
+ summarization: PipelineProgress;
1939
+ embedding: PipelineProgress;
1940
+ private db;
1941
+ constructor(db?: Database.Database);
1942
+ update(name: PipelineName, partial: Partial<PipelineProgress>): void;
1943
+ snapshot(): ProgressSnapshot;
1944
+ private persist;
1945
+ private loadFromDb;
1946
+ }
1947
+
1948
+ declare function createServer(store: Store, registry: PluginRegistry, config: TraceMcpConfig, rootPath?: string, progress?: ProgressState): McpServer;
1908
1949
 
1909
1950
  declare function initializeDatabase(dbPath: string): Database.Database;
1910
1951
 
package/dist/index.js CHANGED
@@ -2501,6 +2501,10 @@ function detectWorkspaces(rootPath) {
2501
2501
  function buildMultiRootWorkspaces(parentDir, childRoots) {
2502
2502
  const workspaces = [];
2503
2503
  for (const childRoot of childRoots) {
2504
+ if (!fs2.existsSync(childRoot)) {
2505
+ logger.warn({ childRoot }, "Skipping missing multi-root child directory");
2506
+ continue;
2507
+ }
2504
2508
  const relPath = path2.relative(parentDir, childRoot).replace(/\\/g, "/");
2505
2509
  const childName = path2.basename(childRoot);
2506
2510
  workspaces.push({ name: childName, path: relPath });
@@ -3640,7 +3644,7 @@ function captureGraphSnapshots(store, cwd) {
3640
3644
 
3641
3645
  // src/db/schema.ts
3642
3646
  import Database from "better-sqlite3";
3643
- var SCHEMA_VERSION = 16;
3647
+ var SCHEMA_VERSION = 17;
3644
3648
  var DDL = `
3645
3649
  -- ============================================================
3646
3650
  -- UNIFIED ADDRESS SPACE
@@ -4521,6 +4525,25 @@ var MIGRATIONS = {
4521
4525
  AND (json_extract(metadata, '$.extends') IS NOT NULL
4522
4526
  OR json_extract(metadata, '$.implements') IS NOT NULL)
4523
4527
  `);
4528
+ },
4529
+ 17: (db) => {
4530
+ db.exec(`
4531
+ CREATE TABLE IF NOT EXISTS indexing_progress (
4532
+ pipeline TEXT PRIMARY KEY,
4533
+ phase TEXT NOT NULL DEFAULT 'idle',
4534
+ processed INTEGER NOT NULL DEFAULT 0,
4535
+ total INTEGER NOT NULL DEFAULT 0,
4536
+ started_at INTEGER NOT NULL DEFAULT 0,
4537
+ completed_at INTEGER NOT NULL DEFAULT 0,
4538
+ error TEXT,
4539
+ updated_at INTEGER NOT NULL DEFAULT 0
4540
+ )
4541
+ `);
4542
+ db.exec(`
4543
+ INSERT OR IGNORE INTO indexing_progress (pipeline) VALUES ('indexing');
4544
+ INSERT OR IGNORE INTO indexing_progress (pipeline) VALUES ('summarization');
4545
+ INSERT OR IGNORE INTO indexing_progress (pipeline) VALUES ('embedding');
4546
+ `);
4524
4547
  }
4525
4548
  };
4526
4549
  function runMigrations(db, fromVersion) {
@@ -4795,12 +4818,15 @@ var FilePersister = class {
4795
4818
  }
4796
4819
  if (ext.symbols.length > 0) {
4797
4820
  const insertedIds = store.insertSymbols(fileId, ext.symbols);
4798
- const trigramBatch = ext.symbols.map((sym, i) => ({
4799
- id: insertedIds[i],
4800
- name: sym.name,
4801
- fqn: sym.fqn ?? null
4802
- }));
4803
- indexTrigramsBatch(store.db, trigramBatch);
4821
+ const trigramBySymbolId = /* @__PURE__ */ new Map();
4822
+ for (let i = 0; i < ext.symbols.length; i++) {
4823
+ trigramBySymbolId.set(ext.symbols[i].symbolId, {
4824
+ id: insertedIds[i],
4825
+ name: ext.symbols[i].name,
4826
+ fqn: ext.symbols[i].fqn ?? null
4827
+ });
4828
+ }
4829
+ indexTrigramsBatch(store.db, [...trigramBySymbolId.values()]);
4804
4830
  }
4805
4831
  if (ext.otherEdges.length > 0) this.storeRawEdges(ext.otherEdges);
4806
4832
  if (ext.importEdges.length > 0) {
@@ -4816,11 +4842,15 @@ var FilePersister = class {
4816
4842
  for (const fwResult of ext.frameworkExtracts) {
4817
4843
  if (fwResult.symbols.length > 0) {
4818
4844
  const fwIds = store.insertSymbols(fileId, fwResult.symbols);
4819
- indexTrigramsBatch(store.db, fwResult.symbols.map((sym, i) => ({
4820
- id: fwIds[i],
4821
- name: sym.name,
4822
- fqn: sym.fqn ?? null
4823
- })));
4845
+ const fwTrigramBySymbolId = /* @__PURE__ */ new Map();
4846
+ for (let i = 0; i < fwResult.symbols.length; i++) {
4847
+ fwTrigramBySymbolId.set(fwResult.symbols[i].symbolId, {
4848
+ id: fwIds[i],
4849
+ name: fwResult.symbols[i].name,
4850
+ fqn: fwResult.symbols[i].fqn ?? null
4851
+ });
4852
+ }
4853
+ indexTrigramsBatch(store.db, [...fwTrigramBySymbolId.values()]);
4824
4854
  }
4825
4855
  if (fwResult.edges?.length) {
4826
4856
  this.storeRawEdges(fwResult.edges);
@@ -5881,16 +5911,18 @@ var EnvIndexer = class {
5881
5911
 
5882
5912
  // src/indexer/pipeline.ts
5883
5913
  var IndexingPipeline = class _IndexingPipeline {
5884
- constructor(store, registry, config, rootPath) {
5914
+ constructor(store, registry, config, rootPath, progress) {
5885
5915
  this.store = store;
5886
5916
  this.registry = registry;
5887
5917
  this.config = config;
5888
5918
  this.rootPath = rootPath;
5919
+ this.progress = progress;
5889
5920
  }
5890
5921
  store;
5891
5922
  registry;
5892
5923
  config;
5893
5924
  rootPath;
5925
+ progress;
5894
5926
  workspaces = [];
5895
5927
  _lock = Promise.resolve();
5896
5928
  _projectContext;
@@ -5975,6 +6007,13 @@ var IndexingPipeline = class _IndexingPipeline {
5975
6007
  errors: 0,
5976
6008
  durationMs: 0
5977
6009
  };
6010
+ this.progress?.update("indexing", {
6011
+ phase: "running",
6012
+ processed: 0,
6013
+ total: relPaths.length,
6014
+ startedAt: Date.now(),
6015
+ completedAt: 0
6016
+ });
5978
6017
  this._projectContext = void 0;
5979
6018
  this.registry.clearCaches();
5980
6019
  this._changedFileIds.clear();
@@ -6021,6 +6060,8 @@ var IndexingPipeline = class _IndexingPipeline {
6021
6060
  persister.persistBatch(extractions);
6022
6061
  result.indexed += extractions.length;
6023
6062
  }
6063
+ const processed = result.indexed + result.skipped + result.errors;
6064
+ this.progress?.update("indexing", { processed });
6024
6065
  }
6025
6066
  enableFts5Triggers(this.store.db);
6026
6067
  const edgeResolver = new EdgeResolver(this.getPipelineState());
@@ -6046,6 +6087,11 @@ var IndexingPipeline = class _IndexingPipeline {
6046
6087
  }
6047
6088
  result.durationMs = Date.now() - startMs;
6048
6089
  result.incremental = this._isIncremental;
6090
+ this.progress?.update("indexing", {
6091
+ phase: "completed",
6092
+ processed: result.indexed + result.skipped + result.errors,
6093
+ completedAt: Date.now()
6094
+ });
6049
6095
  logger.info(result, "Indexing pipeline completed");
6050
6096
  return result;
6051
6097
  }
@@ -7286,8 +7332,170 @@ var GLOBAL_CONFIG_PATH = path12.join(TRACE_MCP_HOME, ".config.json");
7286
7332
  var INDEX_DIR = path12.join(TRACE_MCP_HOME, "index");
7287
7333
  var REGISTRY_PATH = path12.join(TRACE_MCP_HOME, "registry.json");
7288
7334
  var TOPOLOGY_DB_PATH = path12.join(TRACE_MCP_HOME, "topology.db");
7335
+ function stripJsonComments(text) {
7336
+ let result = "";
7337
+ let i = 0;
7338
+ while (i < text.length) {
7339
+ if (text[i] === '"') {
7340
+ const start = i;
7341
+ i++;
7342
+ while (i < text.length && text[i] !== '"') {
7343
+ if (text[i] === "\\") i++;
7344
+ i++;
7345
+ }
7346
+ i++;
7347
+ result += text.slice(start, i);
7348
+ continue;
7349
+ }
7350
+ if (text[i] === "/" && text[i + 1] === "/") {
7351
+ while (i < text.length && text[i] !== "\n") i++;
7352
+ continue;
7353
+ }
7354
+ result += text[i];
7355
+ i++;
7356
+ }
7357
+ return result.replace(/,(\s*[}\]])/g, "$1");
7358
+ }
7359
+ var DEFAULT_CONFIG_JSONC = `{
7360
+ // \u2500\u2500 AI / Embeddings \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7361
+ "ai": {
7362
+ "enabled": false,
7363
+ "provider": "ollama", // "ollama" | "openai"
7364
+ // "base_url": "http://localhost:11434", // custom endpoint
7365
+ // "api_key": "", // required for openai; or set OPENAI_API_KEY env
7366
+ // "inference_model": "", // ollama: "gemma4-e4b", openai: "gpt-4o-mini"
7367
+ // "fast_model": "", // ollama: "gemma4-e4b", openai: "gpt-4o-mini"
7368
+ // "embedding_model": "", // ollama: "qwen3-embedding:0.6b", openai: "text-embedding-3-small"
7369
+ // "embedding_dimensions": 1536, // provider-specific
7370
+ "summarize_on_index": true,
7371
+ "summarize_batch_size": 20,
7372
+ "summarize_kinds": ["class", "function", "method", "interface", "trait", "enum", "type"],
7373
+ "concurrency": 1 // match OLLAMA_NUM_PARALLEL for ollama
7374
+ // "reranker_model": "" // optional: e.g. "bge-reranker-v2-m3"
7375
+ },
7376
+
7377
+ // \u2500\u2500 Security \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7378
+ "security": {
7379
+ // "secret_patterns": [], // extra regex patterns to detect secrets
7380
+ // "max_file_size_bytes": 1048576, // skip files larger than this (1 MB)
7381
+ // "max_files": 10000 // max files per project
7382
+ },
7383
+
7384
+ // \u2500\u2500 Predictive analysis \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7385
+ "predictive": {
7386
+ "enabled": true,
7387
+ "weights": {
7388
+ "bug": { "churn": 0.20, "fix_ratio": 0.20, "complexity": 0.20, "coupling": 0.15, "pagerank": 0.10, "authors": 0.15 },
7389
+ "tech_debt": { "complexity": 0.30, "coupling": 0.25, "test_gap": 0.25, "churn": 0.20 },
7390
+ "change_risk": { "blast_radius": 0.25, "complexity": 0.20, "churn": 0.20, "test_gap": 0.20, "coupling": 0.15 }
7391
+ },
7392
+ "cache_ttl_minutes": 60,
7393
+ "git_since_days": 180,
7394
+ "module_depth": 2
7395
+ },
7396
+
7397
+ // \u2500\u2500 Intent / domain classification \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7398
+ "intent": {
7399
+ "enabled": false,
7400
+ // "domain_hints": {}, // { "domain_name": ["path/pattern/**"] }
7401
+ // "custom_domains": [], // [{ "name": "...", "path_patterns": ["..."] }]
7402
+ "auto_classify_on_index": true,
7403
+ "classify_batch_size": 100
7404
+ },
7405
+
7406
+ // \u2500\u2500 Runtime tracing (OpenTelemetry) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7407
+ "runtime": {
7408
+ "enabled": false,
7409
+ "otlp": {
7410
+ "port": 4318,
7411
+ "host": "127.0.0.1",
7412
+ "max_body_bytes": 4194304
7413
+ },
7414
+ "retention": {
7415
+ "max_span_age_days": 7,
7416
+ "max_aggregate_age_days": 90,
7417
+ "prune_interval": 100
7418
+ },
7419
+ "mapping": {
7420
+ "fqn_attributes": ["code.function", "code.namespace", "code.filepath"],
7421
+ "route_patterns": ["^(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\\\\s+(.+)$"]
7422
+ }
7423
+ },
7424
+
7425
+ // \u2500\u2500 Cross-repo topology \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7426
+ "topology": {
7427
+ "enabled": true,
7428
+ // "repos": [], // extra repo paths to federate
7429
+ "auto_detect": true,
7430
+ "auto_federation": true
7431
+ // "contract_globs": [] // globs for API contract files
7432
+ },
7433
+
7434
+ // \u2500\u2500 Quality gates \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7435
+ "quality_gates": {
7436
+ "enabled": true,
7437
+ "fail_on": "error", // "error" | "warning" | "none"
7438
+ "rules": {
7439
+ // "max_cyclomatic_complexity": { "threshold": 20, "severity": "error" },
7440
+ // "max_coupling_instability": { "threshold": 0.8, "severity": "warning" },
7441
+ // "max_circular_import_chains": { "threshold": 0, "severity": "error" },
7442
+ // "max_dead_exports_percent": { "threshold": 10, "severity": "warning" },
7443
+ // "max_tech_debt_grade": { "threshold": "C", "severity": "warning" },
7444
+ // "max_security_critical_findings": { "threshold": 0, "severity": "error" },
7445
+ // "max_antipattern_count": { "threshold": 5, "severity": "warning" },
7446
+ // "max_code_smell_count": { "threshold": 10, "severity": "warning" }
7447
+ }
7448
+ },
7449
+
7450
+ // \u2500\u2500 Tool exposure \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7451
+ "tools": {
7452
+ "preset": "full", // "full" | "minimal" | custom preset name
7453
+ // "include": [], // whitelist specific tools
7454
+ // "exclude": [], // blacklist specific tools
7455
+ // "descriptions": {}, // override tool descriptions
7456
+ "description_verbosity": "full", // "full" | "minimal" | "none"
7457
+ "instructions_verbosity": "full", // "full" | "minimal" | "none"
7458
+ "meta_fields": true // true | false | ["_hints", "_budget_warning", ...]
7459
+ },
7460
+
7461
+ // \u2500\u2500 Indexing ignore rules \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7462
+ "ignore": {
7463
+ "directories": [], // extra directory names to skip
7464
+ "patterns": [] // extra gitignore-style patterns
7465
+ },
7466
+
7467
+ // \u2500\u2500 Framework-specific \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7468
+ "frameworks": {
7469
+ "laravel": {
7470
+ "artisan": { "enabled": true, "timeout": 10000 },
7471
+ "graceful_degradation": true
7472
+ }
7473
+ },
7474
+
7475
+ // \u2500\u2500 File watcher \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7476
+ "watch": {
7477
+ "enabled": true,
7478
+ "debounceMs": 2000
7479
+ },
7480
+
7481
+ // \u2500\u2500 Per-project overrides \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7482
+ // Keys are absolute paths; values override any top-level setting for that project.
7483
+ // Example:
7484
+ // "projects": {
7485
+ // "/path/to/project": {
7486
+ // "ai": { "enabled": true, "concurrency": 4 },
7487
+ // "include": ["src/**/*.ts"],
7488
+ // "exclude": ["dist/**"]
7489
+ // }
7490
+ // }
7491
+ "projects": {}
7492
+ }
7493
+ `;
7289
7494
  function ensureGlobalDirs() {
7290
7495
  fs11.mkdirSync(INDEX_DIR, { recursive: true });
7496
+ if (!fs11.existsSync(GLOBAL_CONFIG_PATH)) {
7497
+ fs11.writeFileSync(GLOBAL_CONFIG_PATH, DEFAULT_CONFIG_JSONC);
7498
+ }
7291
7499
  }
7292
7500
  function projectHash(absolutePath) {
7293
7501
  return crypto2.createHash("sha256").update(absolutePath).digest("hex").slice(0, 12);
@@ -8594,10 +8802,13 @@ function registerCoreTools(server, ctx) {
8594
8802
  const { store, registry, config, projectRoot, guardPath, j: j3, jh, journal } = ctx;
8595
8803
  server.tool(
8596
8804
  "get_index_health",
8597
- "Get index status, statistics, and health information",
8805
+ "Get index status, statistics, health information, and pipeline progress (indexing, summarization, embedding)",
8598
8806
  {},
8599
8807
  async () => {
8600
8808
  const result = getIndexHealth(store, config);
8809
+ if (ctx.progress) {
8810
+ result.progress = ctx.progress.snapshot();
8811
+ }
8601
8812
  return { content: [{ type: "text", text: j3(result) }] };
8602
8813
  }
8603
8814
  );
@@ -26595,6 +26806,94 @@ function benchmarkTaskContext(store, symbols, files, count, rand) {
26595
26806
  });
26596
26807
  return buildScenario("composite_task", "NL task \u2192 optimal code context (baseline: search + read 5-8 files + grep)", details);
26597
26808
  }
26809
+ function benchmarkFindUsages(store, symbols, count, rand) {
26810
+ const sampled = sample(symbols.filter((s) => s.kind === "function" || s.kind === "method"), count, rand);
26811
+ const details = sampled.map((s) => {
26812
+ const grepMatches = 10;
26813
+ const grepContextLines = 5;
26814
+ const grepChars = grepMatches * grepContextLines * 80;
26815
+ const bl = estimateTokens3(grepChars);
26816
+ const tm = estimateTokens3(grepMatches * 60);
26817
+ return { query: s.name, file: s.file_path, baseline_tokens: bl, trace_mcp_tokens: tm, reduction_pct: reductionPct(bl, tm) };
26818
+ });
26819
+ return buildScenario("find_usages", "All usages of a symbol (baseline: grep with context lines)", details);
26820
+ }
26821
+ function benchmarkContextBundle(store, symbols, count, rand) {
26822
+ const funcs = symbols.filter((s) => s.kind === "function" || s.kind === "method");
26823
+ const batchSize = 3;
26824
+ const batches = Math.min(count, Math.floor(funcs.length / batchSize));
26825
+ const sampled = sample(funcs, batches * batchSize, rand);
26826
+ const details = [];
26827
+ for (let i = 0; i < batches; i++) {
26828
+ const group = sampled.slice(i * batchSize, (i + 1) * batchSize);
26829
+ const totalSourceBytes = group.reduce((s, sym) => s + (sym.source_bytes || Math.round(sym.file_byte_length * 0.08)), 0);
26830
+ const importOverhead = group.length * 200;
26831
+ const bl = estimateTokens3(totalSourceBytes + importOverhead);
26832
+ const tm = estimateTokens3(Math.round((totalSourceBytes + importOverhead) * 0.6));
26833
+ const names = group.map((g) => g.name).join(", ");
26834
+ details.push({ query: names, file: group[0].file_path, baseline_tokens: bl, trace_mcp_tokens: tm, reduction_pct: reductionPct(bl, tm) });
26835
+ }
26836
+ return buildScenario("context_bundle", "Batch symbol+imports lookup (baseline: N \xD7 get_symbol + Read imports)", details);
26837
+ }
26838
+ function benchmarkBatchOverhead(symbols, files, count, rand) {
26839
+ const batchSize = 3;
26840
+ const batches = Math.min(count, Math.floor(symbols.length / batchSize));
26841
+ const sampledSymbols = sample(symbols, batches * batchSize, rand);
26842
+ const details = [];
26843
+ for (let i = 0; i < batches; i++) {
26844
+ const group = sampledSymbols.slice(i * batchSize, (i + 1) * batchSize);
26845
+ const perCallOverhead = 150;
26846
+ const contentTokens = group.reduce((s, sym) => s + estimateTokens3(sym.source_bytes || 200), 0);
26847
+ const bl = contentTokens + batchSize * perCallOverhead;
26848
+ const tm = contentTokens + perCallOverhead;
26849
+ const names = group.map((g) => g.name).join(", ");
26850
+ details.push({ query: names, file: group[0].file_path, baseline_tokens: bl, trace_mcp_tokens: tm, reduction_pct: reductionPct(bl, tm) });
26851
+ }
26852
+ return buildScenario("batch_overhead", "N independent queries batched (baseline: N separate MCP round-trips)", details);
26853
+ }
26854
+ function benchmarkTypeHierarchy(store, symbols, count, rand) {
26855
+ const types = symbols.filter((s) => s.kind === "interface" || s.kind === "class");
26856
+ const sampled = sample(types, count, rand);
26857
+ const details = sampled.map((s) => {
26858
+ const nodeRow = store.db.prepare(
26859
+ "SELECT n.id FROM nodes n JOIN symbols sym ON n.ref_id = sym.id AND n.node_type = ? WHERE sym.symbol_id = ?"
26860
+ ).get("symbol", s.symbol_id);
26861
+ let implFileBytes = 0;
26862
+ let implCount = 0;
26863
+ if (nodeRow) {
26864
+ const impls = store.db.prepare(`
26865
+ SELECT DISTINCT f.byte_length FROM edges e
26866
+ JOIN edge_types et ON e.edge_type_id = et.id
26867
+ JOIN nodes n2 ON e.source_node_id = n2.id AND n2.node_type = 'symbol'
26868
+ JOIN symbols s2 ON n2.ref_id = s2.id
26869
+ JOIN files f ON s2.file_id = f.id
26870
+ WHERE e.target_node_id = ?
26871
+ AND et.name IN ('implements', 'extends')
26872
+ LIMIT 10
26873
+ `).all(nodeRow.id);
26874
+ implFileBytes = impls.reduce((sum, d) => sum + (d.byte_length || 0), 0);
26875
+ implCount = impls.length;
26876
+ }
26877
+ const grepChars = Math.max(implCount, 3) * 5 * 80;
26878
+ const readChars = implFileBytes || s.file_byte_length * 3;
26879
+ const bl = estimateTokens3(grepChars + readChars);
26880
+ const tm = estimateTokens3(Math.max(implCount, 3) * 120);
26881
+ return { query: s.name, file: s.file_path, baseline_tokens: bl, trace_mcp_tokens: tm, reduction_pct: reductionPct(bl, tm) };
26882
+ });
26883
+ return buildScenario("type_hierarchy", "Find all implementations of interface/class (baseline: grep + read files)", details);
26884
+ }
26885
+ function benchmarkTestsFor(store, symbols, count, rand) {
26886
+ const sampled = sample(symbols.filter((s) => s.kind === "function" || s.kind === "method"), count, rand);
26887
+ const details = sampled.map((s) => {
26888
+ const globTokens = 200;
26889
+ const grepTokens = 3 * 5 * 80 / 3.5;
26890
+ const testFileReadTokens = 2 * estimateTokens3(3e3);
26891
+ const bl = Math.round(globTokens + grepTokens + testFileReadTokens);
26892
+ const tm = estimateTokens3(400);
26893
+ return { query: s.name, file: s.file_path, baseline_tokens: bl, trace_mcp_tokens: tm, reduction_pct: reductionPct(bl, tm) };
26894
+ });
26895
+ return buildScenario("tests_for", "Find tests for a symbol (baseline: glob + grep + read test files)", details);
26896
+ }
26598
26897
  function runBenchmark(store, opts = {}) {
26599
26898
  const n = opts.queries ?? 10;
26600
26899
  const rand = seededRandom(opts.seed ?? 42);
@@ -26604,8 +26903,13 @@ function runBenchmark(store, opts = {}) {
26604
26903
  benchmarkSymbolLookup(symbols, n, rand),
26605
26904
  benchmarkFileExploration(files, n, rand),
26606
26905
  benchmarkSearch(symbols, n, rand),
26906
+ benchmarkFindUsages(store, symbols, n, rand),
26907
+ benchmarkContextBundle(store, symbols, n, rand),
26908
+ benchmarkBatchOverhead(symbols, files, n, rand),
26607
26909
  benchmarkImpactAnalysis(store, symbols, n, rand),
26608
26910
  benchmarkCallGraph(store, symbols, n, rand),
26911
+ benchmarkTypeHierarchy(store, symbols, n, rand),
26912
+ benchmarkTestsFor(store, symbols, n, rand),
26609
26913
  benchmarkTaskContext(store, symbols, files, n, rand)
26610
26914
  ];
26611
26915
  const totalQueries = scenarios.reduce((s, sc) => s + sc.queries, 0);
@@ -27173,11 +27477,11 @@ function registerPrompts(server, ctx) {
27173
27477
  sections.push("");
27174
27478
  }
27175
27479
  }
27176
- const deadCode = safe2(() => getDeadCodeV2(store, { threshold: 0.5, limit: 10 }), { items: [] });
27177
- if (deadCode.items && deadCode.items.length > 0) {
27178
- sections.push(`## Dead Code Candidates (${deadCode.items.length})
27480
+ const deadCode = safe2(() => getDeadCodeV2(store, { threshold: 0.5, limit: 10 }), { dead_symbols: [], file_pattern: null, total_exports: 0, total_dead: 0, threshold: 0.5 });
27481
+ if (deadCode.dead_symbols && deadCode.dead_symbols.length > 0) {
27482
+ sections.push(`## Dead Code Candidates (${deadCode.dead_symbols.length})
27179
27483
  `);
27180
- for (const d of deadCode.items.slice(0, 5)) {
27484
+ for (const d of deadCode.dead_symbols.slice(0, 5)) {
27181
27485
  sections.push(`- ${d.name} in ${d.file} (confidence: ${d.confidence})`);
27182
27486
  }
27183
27487
  sections.push("");
@@ -27216,10 +27520,10 @@ Provide a thorough code review with risk assessment, suggested tests, and archit
27216
27520
  sections.push("```json");
27217
27521
  sections.push(JSON.stringify(map, null, 2));
27218
27522
  sections.push("```\n");
27219
- const health = safe2(() => getRepoHealth(store), {});
27523
+ const health = safe2(() => getRepoHealth(store), null);
27220
27524
  sections.push("## Architecture Health\n");
27221
- sections.push(`- Coupling metrics: ${health.coupling_summary?.total ?? "N/A"} files analyzed`);
27222
- sections.push(`- Dependency cycles: ${health.cycles?.length ?? 0}`);
27525
+ sections.push(`- Files in graph: ${health?.summary?.files_in_graph ?? "N/A"}`);
27526
+ sections.push(`- Dependency cycles: ${health?.cycles?.length ?? 0}`);
27223
27527
  sections.push("");
27224
27528
  sections.push("## Key Entry Points\n");
27225
27529
  const context = safe2(() => getFeatureContext(store, projectRoot, "main entry point application startup", 4e3), { symbols: [] });
@@ -27302,7 +27606,7 @@ Analyze this bug. Identify the most likely failure point, suggest debugging step
27302
27606
  sections.push(`## Dependency Cycles: ${cycles.length}
27303
27607
  `);
27304
27608
  for (const c of cycles.slice(0, 5)) {
27305
- sections.push(`- ${c.join(" \u2192 ")}`);
27609
+ sections.push(`- ${c.files.join(" \u2192 ")}`);
27306
27610
  }
27307
27611
  sections.push("");
27308
27612
  const debt = safe2(() => getTechDebt(store, projectRoot, {
@@ -27383,11 +27687,11 @@ Analyze this project's architecture health. Identify the most critical issues an
27383
27687
  sections.push(...riskFiles);
27384
27688
  sections.push("");
27385
27689
  }
27386
- const dead = safe2(() => getDeadCodeV2(store, { threshold: 0.6, limit: 10 }), { items: [] });
27387
- if (dead.items && dead.items.length > 0) {
27388
- sections.push(`## Potential Dead Code: ${dead.items.length}
27690
+ const dead = safe2(() => getDeadCodeV2(store, { threshold: 0.6, limit: 10 }), { dead_symbols: [], file_pattern: null, total_exports: 0, total_dead: 0, threshold: 0.6 });
27691
+ if (dead.dead_symbols && dead.dead_symbols.length > 0) {
27692
+ sections.push(`## Potential Dead Code: ${dead.dead_symbols.length}
27389
27693
  `);
27390
- for (const d of dead.items.slice(0, 5)) {
27694
+ for (const d of dead.dead_symbols.slice(0, 5)) {
27391
27695
  sections.push(`- ${d.name} (${d.file})`);
27392
27696
  }
27393
27697
  sections.push("");
@@ -27728,7 +28032,7 @@ function registerSessionTools(server, ctx) {
27728
28032
  }
27729
28033
 
27730
28034
  // src/server/server.ts
27731
- var PKG_VERSION = true ? "1.6.0" : "0.0.0-dev";
28035
+ var PKG_VERSION = true ? "1.7.0" : "0.0.0-dev";
27732
28036
  function j2(value) {
27733
28037
  return JSON.stringify(value, (_key, val) => val === null || val === void 0 ? void 0 : val);
27734
28038
  }
@@ -27832,7 +28136,7 @@ function extractCompactResult(toolName, response) {
27832
28136
  return void 0;
27833
28137
  }
27834
28138
  }
27835
- function createServer2(store, registry, config, rootPath) {
28139
+ function createServer2(store, registry, config, rootPath, progress) {
27836
28140
  const projectRoot = rootPath ?? process.cwd();
27837
28141
  const frameworkNames = new Set(
27838
28142
  registry.getAllFrameworkPlugins().map((p) => p.manifest.name)
@@ -27979,7 +28283,8 @@ function createServer2(store, registry, config, rootPath) {
27979
28283
  guardPath,
27980
28284
  j: j2,
27981
28285
  jh,
27982
- markExplored: explored.markExplored
28286
+ markExplored: explored.markExplored,
28287
+ progress: progress ?? null
27983
28288
  };
27984
28289
  const metaCtx = {
27985
28290
  ...ctx,
@@ -28261,6 +28566,24 @@ var SymbolRepository = class {
28261
28566
  updateSymbolSummary(symbolId, summary) {
28262
28567
  this.db.prepare("UPDATE symbols SET summary = ? WHERE id = ?").run(summary, symbolId);
28263
28568
  }
28569
+ countUnsummarizedSymbols(kinds) {
28570
+ if (kinds.length === 0) return 0;
28571
+ const placeholders = kinds.map(() => "?").join(",");
28572
+ const row = this.db.prepare(`
28573
+ SELECT COUNT(*) as cnt FROM symbols s
28574
+ JOIN files f ON s.file_id = f.id
28575
+ WHERE s.summary IS NULL AND s.kind IN (${placeholders}) AND f.gitignored = 0
28576
+ `).get(...kinds);
28577
+ return row.cnt;
28578
+ }
28579
+ countUnembeddedSymbols() {
28580
+ const row = this.db.prepare(`
28581
+ SELECT COUNT(*) as cnt FROM symbols s
28582
+ LEFT JOIN symbol_embeddings se ON se.symbol_id = s.id
28583
+ WHERE se.symbol_id IS NULL
28584
+ `).get();
28585
+ return row.cnt;
28586
+ }
28264
28587
  getUnsummarizedSymbols(kinds, limit) {
28265
28588
  if (kinds.length === 0) return [];
28266
28589
  const placeholders = kinds.map(() => "?").join(",");
@@ -28940,6 +29263,12 @@ var Store = class {
28940
29263
  updateSymbolSummary(symbolId, summary) {
28941
29264
  this.symbols.updateSymbolSummary(symbolId, summary);
28942
29265
  }
29266
+ countUnsummarizedSymbols(kinds) {
29267
+ return this.symbols.countUnsummarizedSymbols(kinds);
29268
+ }
29269
+ countUnembeddedSymbols() {
29270
+ return this.symbols.countUnembeddedSymbols();
29271
+ }
28943
29272
  getUnsummarizedSymbols(kinds, limit) {
28944
29273
  return this.symbols.getUnsummarizedSymbols(kinds, limit);
28945
29274
  }
@@ -29399,7 +29728,7 @@ var TraceMcpConfigSchema = z13.object({
29399
29728
  function loadGlobalConfigRaw() {
29400
29729
  if (!fs37.existsSync(GLOBAL_CONFIG_PATH)) return {};
29401
29730
  try {
29402
- return JSON.parse(fs37.readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
29731
+ return JSON.parse(stripJsonComments(fs37.readFileSync(GLOBAL_CONFIG_PATH, "utf-8")));
29403
29732
  } catch {
29404
29733
  return {};
29405
29734
  }