trace-mcp 1.6.1 → 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
@@ -3644,7 +3644,7 @@ function captureGraphSnapshots(store, cwd) {
3644
3644
 
3645
3645
  // src/db/schema.ts
3646
3646
  import Database from "better-sqlite3";
3647
- var SCHEMA_VERSION = 16;
3647
+ var SCHEMA_VERSION = 17;
3648
3648
  var DDL = `
3649
3649
  -- ============================================================
3650
3650
  -- UNIFIED ADDRESS SPACE
@@ -4525,6 +4525,25 @@ var MIGRATIONS = {
4525
4525
  AND (json_extract(metadata, '$.extends') IS NOT NULL
4526
4526
  OR json_extract(metadata, '$.implements') IS NOT NULL)
4527
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
+ `);
4528
4547
  }
4529
4548
  };
4530
4549
  function runMigrations(db, fromVersion) {
@@ -5892,16 +5911,18 @@ var EnvIndexer = class {
5892
5911
 
5893
5912
  // src/indexer/pipeline.ts
5894
5913
  var IndexingPipeline = class _IndexingPipeline {
5895
- constructor(store, registry, config, rootPath) {
5914
+ constructor(store, registry, config, rootPath, progress) {
5896
5915
  this.store = store;
5897
5916
  this.registry = registry;
5898
5917
  this.config = config;
5899
5918
  this.rootPath = rootPath;
5919
+ this.progress = progress;
5900
5920
  }
5901
5921
  store;
5902
5922
  registry;
5903
5923
  config;
5904
5924
  rootPath;
5925
+ progress;
5905
5926
  workspaces = [];
5906
5927
  _lock = Promise.resolve();
5907
5928
  _projectContext;
@@ -5986,6 +6007,13 @@ var IndexingPipeline = class _IndexingPipeline {
5986
6007
  errors: 0,
5987
6008
  durationMs: 0
5988
6009
  };
6010
+ this.progress?.update("indexing", {
6011
+ phase: "running",
6012
+ processed: 0,
6013
+ total: relPaths.length,
6014
+ startedAt: Date.now(),
6015
+ completedAt: 0
6016
+ });
5989
6017
  this._projectContext = void 0;
5990
6018
  this.registry.clearCaches();
5991
6019
  this._changedFileIds.clear();
@@ -6032,6 +6060,8 @@ var IndexingPipeline = class _IndexingPipeline {
6032
6060
  persister.persistBatch(extractions);
6033
6061
  result.indexed += extractions.length;
6034
6062
  }
6063
+ const processed = result.indexed + result.skipped + result.errors;
6064
+ this.progress?.update("indexing", { processed });
6035
6065
  }
6036
6066
  enableFts5Triggers(this.store.db);
6037
6067
  const edgeResolver = new EdgeResolver(this.getPipelineState());
@@ -6057,6 +6087,11 @@ var IndexingPipeline = class _IndexingPipeline {
6057
6087
  }
6058
6088
  result.durationMs = Date.now() - startMs;
6059
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
+ });
6060
6095
  logger.info(result, "Indexing pipeline completed");
6061
6096
  return result;
6062
6097
  }
@@ -7297,8 +7332,170 @@ var GLOBAL_CONFIG_PATH = path12.join(TRACE_MCP_HOME, ".config.json");
7297
7332
  var INDEX_DIR = path12.join(TRACE_MCP_HOME, "index");
7298
7333
  var REGISTRY_PATH = path12.join(TRACE_MCP_HOME, "registry.json");
7299
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
+ `;
7300
7494
  function ensureGlobalDirs() {
7301
7495
  fs11.mkdirSync(INDEX_DIR, { recursive: true });
7496
+ if (!fs11.existsSync(GLOBAL_CONFIG_PATH)) {
7497
+ fs11.writeFileSync(GLOBAL_CONFIG_PATH, DEFAULT_CONFIG_JSONC);
7498
+ }
7302
7499
  }
7303
7500
  function projectHash(absolutePath) {
7304
7501
  return crypto2.createHash("sha256").update(absolutePath).digest("hex").slice(0, 12);
@@ -8605,10 +8802,13 @@ function registerCoreTools(server, ctx) {
8605
8802
  const { store, registry, config, projectRoot, guardPath, j: j3, jh, journal } = ctx;
8606
8803
  server.tool(
8607
8804
  "get_index_health",
8608
- "Get index status, statistics, and health information",
8805
+ "Get index status, statistics, health information, and pipeline progress (indexing, summarization, embedding)",
8609
8806
  {},
8610
8807
  async () => {
8611
8808
  const result = getIndexHealth(store, config);
8809
+ if (ctx.progress) {
8810
+ result.progress = ctx.progress.snapshot();
8811
+ }
8612
8812
  return { content: [{ type: "text", text: j3(result) }] };
8613
8813
  }
8614
8814
  );
@@ -26606,6 +26806,94 @@ function benchmarkTaskContext(store, symbols, files, count, rand) {
26606
26806
  });
26607
26807
  return buildScenario("composite_task", "NL task \u2192 optimal code context (baseline: search + read 5-8 files + grep)", details);
26608
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
+ }
26609
26897
  function runBenchmark(store, opts = {}) {
26610
26898
  const n = opts.queries ?? 10;
26611
26899
  const rand = seededRandom(opts.seed ?? 42);
@@ -26615,8 +26903,13 @@ function runBenchmark(store, opts = {}) {
26615
26903
  benchmarkSymbolLookup(symbols, n, rand),
26616
26904
  benchmarkFileExploration(files, n, rand),
26617
26905
  benchmarkSearch(symbols, n, rand),
26906
+ benchmarkFindUsages(store, symbols, n, rand),
26907
+ benchmarkContextBundle(store, symbols, n, rand),
26908
+ benchmarkBatchOverhead(symbols, files, n, rand),
26618
26909
  benchmarkImpactAnalysis(store, symbols, n, rand),
26619
26910
  benchmarkCallGraph(store, symbols, n, rand),
26911
+ benchmarkTypeHierarchy(store, symbols, n, rand),
26912
+ benchmarkTestsFor(store, symbols, n, rand),
26620
26913
  benchmarkTaskContext(store, symbols, files, n, rand)
26621
26914
  ];
26622
26915
  const totalQueries = scenarios.reduce((s, sc) => s + sc.queries, 0);
@@ -27184,11 +27477,11 @@ function registerPrompts(server, ctx) {
27184
27477
  sections.push("");
27185
27478
  }
27186
27479
  }
27187
- const deadCode = safe2(() => getDeadCodeV2(store, { threshold: 0.5, limit: 10 }), { items: [] });
27188
- if (deadCode.items && deadCode.items.length > 0) {
27189
- 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})
27190
27483
  `);
27191
- for (const d of deadCode.items.slice(0, 5)) {
27484
+ for (const d of deadCode.dead_symbols.slice(0, 5)) {
27192
27485
  sections.push(`- ${d.name} in ${d.file} (confidence: ${d.confidence})`);
27193
27486
  }
27194
27487
  sections.push("");
@@ -27227,10 +27520,10 @@ Provide a thorough code review with risk assessment, suggested tests, and archit
27227
27520
  sections.push("```json");
27228
27521
  sections.push(JSON.stringify(map, null, 2));
27229
27522
  sections.push("```\n");
27230
- const health = safe2(() => getRepoHealth(store), {});
27523
+ const health = safe2(() => getRepoHealth(store), null);
27231
27524
  sections.push("## Architecture Health\n");
27232
- sections.push(`- Coupling metrics: ${health.coupling_summary?.total ?? "N/A"} files analyzed`);
27233
- 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}`);
27234
27527
  sections.push("");
27235
27528
  sections.push("## Key Entry Points\n");
27236
27529
  const context = safe2(() => getFeatureContext(store, projectRoot, "main entry point application startup", 4e3), { symbols: [] });
@@ -27313,7 +27606,7 @@ Analyze this bug. Identify the most likely failure point, suggest debugging step
27313
27606
  sections.push(`## Dependency Cycles: ${cycles.length}
27314
27607
  `);
27315
27608
  for (const c of cycles.slice(0, 5)) {
27316
- sections.push(`- ${c.join(" \u2192 ")}`);
27609
+ sections.push(`- ${c.files.join(" \u2192 ")}`);
27317
27610
  }
27318
27611
  sections.push("");
27319
27612
  const debt = safe2(() => getTechDebt(store, projectRoot, {
@@ -27394,11 +27687,11 @@ Analyze this project's architecture health. Identify the most critical issues an
27394
27687
  sections.push(...riskFiles);
27395
27688
  sections.push("");
27396
27689
  }
27397
- const dead = safe2(() => getDeadCodeV2(store, { threshold: 0.6, limit: 10 }), { items: [] });
27398
- if (dead.items && dead.items.length > 0) {
27399
- 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}
27400
27693
  `);
27401
- for (const d of dead.items.slice(0, 5)) {
27694
+ for (const d of dead.dead_symbols.slice(0, 5)) {
27402
27695
  sections.push(`- ${d.name} (${d.file})`);
27403
27696
  }
27404
27697
  sections.push("");
@@ -27739,7 +28032,7 @@ function registerSessionTools(server, ctx) {
27739
28032
  }
27740
28033
 
27741
28034
  // src/server/server.ts
27742
- var PKG_VERSION = true ? "1.6.1" : "0.0.0-dev";
28035
+ var PKG_VERSION = true ? "1.7.0" : "0.0.0-dev";
27743
28036
  function j2(value) {
27744
28037
  return JSON.stringify(value, (_key, val) => val === null || val === void 0 ? void 0 : val);
27745
28038
  }
@@ -27843,7 +28136,7 @@ function extractCompactResult(toolName, response) {
27843
28136
  return void 0;
27844
28137
  }
27845
28138
  }
27846
- function createServer2(store, registry, config, rootPath) {
28139
+ function createServer2(store, registry, config, rootPath, progress) {
27847
28140
  const projectRoot = rootPath ?? process.cwd();
27848
28141
  const frameworkNames = new Set(
27849
28142
  registry.getAllFrameworkPlugins().map((p) => p.manifest.name)
@@ -27990,7 +28283,8 @@ function createServer2(store, registry, config, rootPath) {
27990
28283
  guardPath,
27991
28284
  j: j2,
27992
28285
  jh,
27993
- markExplored: explored.markExplored
28286
+ markExplored: explored.markExplored,
28287
+ progress: progress ?? null
27994
28288
  };
27995
28289
  const metaCtx = {
27996
28290
  ...ctx,
@@ -28272,6 +28566,24 @@ var SymbolRepository = class {
28272
28566
  updateSymbolSummary(symbolId, summary) {
28273
28567
  this.db.prepare("UPDATE symbols SET summary = ? WHERE id = ?").run(summary, symbolId);
28274
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
+ }
28275
28587
  getUnsummarizedSymbols(kinds, limit) {
28276
28588
  if (kinds.length === 0) return [];
28277
28589
  const placeholders = kinds.map(() => "?").join(",");
@@ -28951,6 +29263,12 @@ var Store = class {
28951
29263
  updateSymbolSummary(symbolId, summary) {
28952
29264
  this.symbols.updateSymbolSummary(symbolId, summary);
28953
29265
  }
29266
+ countUnsummarizedSymbols(kinds) {
29267
+ return this.symbols.countUnsummarizedSymbols(kinds);
29268
+ }
29269
+ countUnembeddedSymbols() {
29270
+ return this.symbols.countUnembeddedSymbols();
29271
+ }
28954
29272
  getUnsummarizedSymbols(kinds, limit) {
28955
29273
  return this.symbols.getUnsummarizedSymbols(kinds, limit);
28956
29274
  }
@@ -29410,7 +29728,7 @@ var TraceMcpConfigSchema = z13.object({
29410
29728
  function loadGlobalConfigRaw() {
29411
29729
  if (!fs37.existsSync(GLOBAL_CONFIG_PATH)) return {};
29412
29730
  try {
29413
- return JSON.parse(fs37.readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
29731
+ return JSON.parse(stripJsonComments(fs37.readFileSync(GLOBAL_CONFIG_PATH, "utf-8")));
29414
29732
  } catch {
29415
29733
  return {};
29416
29734
  }