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/cli.js CHANGED
@@ -530,8 +530,8 @@ var require_utils = __commonJS({
530
530
  }
531
531
  return output;
532
532
  };
533
- exports.basename = (path107, { windows } = {}) => {
534
- const segs = path107.split(windows ? /[\\/]/ : "/");
533
+ exports.basename = (path108, { windows } = {}) => {
534
+ const segs = path108.split(windows ? /[\\/]/ : "/");
535
535
  const last = segs[segs.length - 1];
536
536
  if (last === "") {
537
537
  return segs[segs.length - 2];
@@ -1960,7 +1960,7 @@ var require_picomatch = __commonJS({
1960
1960
  };
1961
1961
  picomatch3.isMatch = (str2, patterns, options) => picomatch3(patterns, options)(str2);
1962
1962
  picomatch3.parse = (pattern, options) => {
1963
- if (Array.isArray(pattern)) return pattern.map((p4) => picomatch3.parse(p4, options));
1963
+ if (Array.isArray(pattern)) return pattern.map((p5) => picomatch3.parse(p5, options));
1964
1964
  return parse(pattern, { ...options, fastpaths: false });
1965
1965
  };
1966
1966
  picomatch3.scan = (input, options) => scan(input, options);
@@ -2083,7 +2083,7 @@ function detectLayerPreset(store) {
2083
2083
  let matchCount = 0;
2084
2084
  for (const layer of layers) {
2085
2085
  const hasMatch = layer.path_prefixes.some(
2086
- (prefix) => paths.some((p4) => p4.startsWith(prefix))
2086
+ (prefix) => paths.some((p5) => p5.startsWith(prefix))
2087
2087
  );
2088
2088
  if (hasMatch) matchCount++;
2089
2089
  }
@@ -2115,9 +2115,9 @@ var init_layer_violations = __esm({
2115
2115
  });
2116
2116
 
2117
2117
  // src/cli.ts
2118
- import { Command as Command10 } from "commander";
2119
- import path106 from "path";
2120
- import fs94 from "fs";
2118
+ import { Command as Command12 } from "commander";
2119
+ import path107 from "path";
2120
+ import fs96 from "fs";
2121
2121
  import { randomUUID } from "crypto";
2122
2122
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2123
2123
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
@@ -2134,7 +2134,7 @@ var logger = pino({
2134
2134
  }, process.stderr);
2135
2135
 
2136
2136
  // src/db/schema.ts
2137
- var SCHEMA_VERSION = 16;
2137
+ var SCHEMA_VERSION = 17;
2138
2138
  var DDL = `
2139
2139
  -- ============================================================
2140
2140
  -- UNIFIED ADDRESS SPACE
@@ -3015,6 +3015,25 @@ var MIGRATIONS = {
3015
3015
  AND (json_extract(metadata, '$.extends') IS NOT NULL
3016
3016
  OR json_extract(metadata, '$.implements') IS NOT NULL)
3017
3017
  `);
3018
+ },
3019
+ 17: (db) => {
3020
+ db.exec(`
3021
+ CREATE TABLE IF NOT EXISTS indexing_progress (
3022
+ pipeline TEXT PRIMARY KEY,
3023
+ phase TEXT NOT NULL DEFAULT 'idle',
3024
+ processed INTEGER NOT NULL DEFAULT 0,
3025
+ total INTEGER NOT NULL DEFAULT 0,
3026
+ started_at INTEGER NOT NULL DEFAULT 0,
3027
+ completed_at INTEGER NOT NULL DEFAULT 0,
3028
+ error TEXT,
3029
+ updated_at INTEGER NOT NULL DEFAULT 0
3030
+ )
3031
+ `);
3032
+ db.exec(`
3033
+ INSERT OR IGNORE INTO indexing_progress (pipeline) VALUES ('indexing');
3034
+ INSERT OR IGNORE INTO indexing_progress (pipeline) VALUES ('summarization');
3035
+ INSERT OR IGNORE INTO indexing_progress (pipeline) VALUES ('embedding');
3036
+ `);
3018
3037
  }
3019
3038
  };
3020
3039
  function runMigrations(db, fromVersion) {
@@ -3129,14 +3148,14 @@ var FileRepository = class {
3129
3148
  }
3130
3149
  db;
3131
3150
  _stmts;
3132
- insertFile(path107, language, contentHash, byteLength, workspace, mtimeMs, createNode) {
3133
- const result = this._stmts.insertFile.run(path107, language, contentHash, byteLength, workspace, mtimeMs);
3151
+ insertFile(path108, language, contentHash, byteLength, workspace, mtimeMs, createNode) {
3152
+ const result = this._stmts.insertFile.run(path108, language, contentHash, byteLength, workspace, mtimeMs);
3134
3153
  const fileId = Number(result.lastInsertRowid);
3135
3154
  createNode("file", fileId);
3136
3155
  return fileId;
3137
3156
  }
3138
- getFile(path107) {
3139
- return this._stmts.getFile.get(path107);
3157
+ getFile(path108) {
3158
+ return this._stmts.getFile.get(path108);
3140
3159
  }
3141
3160
  getFileById(id) {
3142
3161
  return this._stmts.getFileById.get(id);
@@ -3366,6 +3385,24 @@ var SymbolRepository = class {
3366
3385
  updateSymbolSummary(symbolId, summary) {
3367
3386
  this.db.prepare("UPDATE symbols SET summary = ? WHERE id = ?").run(summary, symbolId);
3368
3387
  }
3388
+ countUnsummarizedSymbols(kinds) {
3389
+ if (kinds.length === 0) return 0;
3390
+ const placeholders = kinds.map(() => "?").join(",");
3391
+ const row = this.db.prepare(`
3392
+ SELECT COUNT(*) as cnt FROM symbols s
3393
+ JOIN files f ON s.file_id = f.id
3394
+ WHERE s.summary IS NULL AND s.kind IN (${placeholders}) AND f.gitignored = 0
3395
+ `).get(...kinds);
3396
+ return row.cnt;
3397
+ }
3398
+ countUnembeddedSymbols() {
3399
+ const row = this.db.prepare(`
3400
+ SELECT COUNT(*) as cnt FROM symbols s
3401
+ LEFT JOIN symbol_embeddings se ON se.symbol_id = s.id
3402
+ WHERE se.symbol_id IS NULL
3403
+ `).get();
3404
+ return row.cnt;
3405
+ }
3369
3406
  getUnsummarizedSymbols(kinds, limit) {
3370
3407
  if (kinds.length === 0) return [];
3371
3408
  const placeholders = kinds.map(() => "?").join(",");
@@ -3980,9 +4017,9 @@ var Store = class {
3980
4017
  domain;
3981
4018
  analytics;
3982
4019
  // --- Files (delegates to FileRepository) ---
3983
- insertFile(path107, language, contentHash, byteLength, workspace, mtimeMs) {
4020
+ insertFile(path108, language, contentHash, byteLength, workspace, mtimeMs) {
3984
4021
  return this.files.insertFile(
3985
- path107,
4022
+ path108,
3986
4023
  language,
3987
4024
  contentHash,
3988
4025
  byteLength,
@@ -3991,8 +4028,8 @@ var Store = class {
3991
4028
  (nodeType, refId) => this.graph.createNode(nodeType, refId)
3992
4029
  );
3993
4030
  }
3994
- getFile(path107) {
3995
- return this.files.getFile(path107);
4031
+ getFile(path108) {
4032
+ return this.files.getFile(path108);
3996
4033
  }
3997
4034
  getFileById(id) {
3998
4035
  return this.files.getFileById(id);
@@ -4080,6 +4117,12 @@ var Store = class {
4080
4117
  updateSymbolSummary(symbolId, summary) {
4081
4118
  this.symbols.updateSymbolSummary(symbolId, summary);
4082
4119
  }
4120
+ countUnsummarizedSymbols(kinds) {
4121
+ return this.symbols.countUnsummarizedSymbols(kinds);
4122
+ }
4123
+ countUnembeddedSymbols() {
4124
+ return this.symbols.countUnembeddedSymbols();
4125
+ }
4083
4126
  getUnsummarizedSymbols(kinds, limit) {
4084
4127
  return this.symbols.getUnsummarizedSymbols(kinds, limit);
4085
4128
  }
@@ -4264,7 +4307,7 @@ var PluginRegistry = class {
4264
4307
  }
4265
4308
  getActiveFrameworkPlugins(ctx) {
4266
4309
  if (this._activeFrameworkCache) return this._activeFrameworkCache;
4267
- const active = this.frameworkPlugins.filter((p4) => p4.detect(ctx));
4310
+ const active = this.frameworkPlugins.filter((p5) => p5.detect(ctx));
4268
4311
  this._activeFrameworkCache = this.topologicalSort(active);
4269
4312
  return this._activeFrameworkCache;
4270
4313
  }
@@ -4295,8 +4338,8 @@ var PluginRegistry = class {
4295
4338
  }
4296
4339
  topologicalSort(plugins) {
4297
4340
  const nameMap = /* @__PURE__ */ new Map();
4298
- for (const p4 of plugins) {
4299
- nameMap.set(p4.manifest.name, p4);
4341
+ for (const p5 of plugins) {
4342
+ nameMap.set(p5.manifest.name, p5);
4300
4343
  }
4301
4344
  const sorted = [];
4302
4345
  const visited = /* @__PURE__ */ new Set();
@@ -4346,8 +4389,170 @@ var GLOBAL_CONFIG_PATH = path.join(TRACE_MCP_HOME, ".config.json");
4346
4389
  var INDEX_DIR = path.join(TRACE_MCP_HOME, "index");
4347
4390
  var REGISTRY_PATH = path.join(TRACE_MCP_HOME, "registry.json");
4348
4391
  var TOPOLOGY_DB_PATH = path.join(TRACE_MCP_HOME, "topology.db");
4392
+ function stripJsonComments(text) {
4393
+ let result = "";
4394
+ let i = 0;
4395
+ while (i < text.length) {
4396
+ if (text[i] === '"') {
4397
+ const start = i;
4398
+ i++;
4399
+ while (i < text.length && text[i] !== '"') {
4400
+ if (text[i] === "\\") i++;
4401
+ i++;
4402
+ }
4403
+ i++;
4404
+ result += text.slice(start, i);
4405
+ continue;
4406
+ }
4407
+ if (text[i] === "/" && text[i + 1] === "/") {
4408
+ while (i < text.length && text[i] !== "\n") i++;
4409
+ continue;
4410
+ }
4411
+ result += text[i];
4412
+ i++;
4413
+ }
4414
+ return result.replace(/,(\s*[}\]])/g, "$1");
4415
+ }
4416
+ var DEFAULT_CONFIG_JSONC = `{
4417
+ // \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
4418
+ "ai": {
4419
+ "enabled": false,
4420
+ "provider": "ollama", // "ollama" | "openai"
4421
+ // "base_url": "http://localhost:11434", // custom endpoint
4422
+ // "api_key": "", // required for openai; or set OPENAI_API_KEY env
4423
+ // "inference_model": "", // ollama: "gemma4-e4b", openai: "gpt-4o-mini"
4424
+ // "fast_model": "", // ollama: "gemma4-e4b", openai: "gpt-4o-mini"
4425
+ // "embedding_model": "", // ollama: "qwen3-embedding:0.6b", openai: "text-embedding-3-small"
4426
+ // "embedding_dimensions": 1536, // provider-specific
4427
+ "summarize_on_index": true,
4428
+ "summarize_batch_size": 20,
4429
+ "summarize_kinds": ["class", "function", "method", "interface", "trait", "enum", "type"],
4430
+ "concurrency": 1 // match OLLAMA_NUM_PARALLEL for ollama
4431
+ // "reranker_model": "" // optional: e.g. "bge-reranker-v2-m3"
4432
+ },
4433
+
4434
+ // \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
4435
+ "security": {
4436
+ // "secret_patterns": [], // extra regex patterns to detect secrets
4437
+ // "max_file_size_bytes": 1048576, // skip files larger than this (1 MB)
4438
+ // "max_files": 10000 // max files per project
4439
+ },
4440
+
4441
+ // \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
4442
+ "predictive": {
4443
+ "enabled": true,
4444
+ "weights": {
4445
+ "bug": { "churn": 0.20, "fix_ratio": 0.20, "complexity": 0.20, "coupling": 0.15, "pagerank": 0.10, "authors": 0.15 },
4446
+ "tech_debt": { "complexity": 0.30, "coupling": 0.25, "test_gap": 0.25, "churn": 0.20 },
4447
+ "change_risk": { "blast_radius": 0.25, "complexity": 0.20, "churn": 0.20, "test_gap": 0.20, "coupling": 0.15 }
4448
+ },
4449
+ "cache_ttl_minutes": 60,
4450
+ "git_since_days": 180,
4451
+ "module_depth": 2
4452
+ },
4453
+
4454
+ // \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
4455
+ "intent": {
4456
+ "enabled": false,
4457
+ // "domain_hints": {}, // { "domain_name": ["path/pattern/**"] }
4458
+ // "custom_domains": [], // [{ "name": "...", "path_patterns": ["..."] }]
4459
+ "auto_classify_on_index": true,
4460
+ "classify_batch_size": 100
4461
+ },
4462
+
4463
+ // \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
4464
+ "runtime": {
4465
+ "enabled": false,
4466
+ "otlp": {
4467
+ "port": 4318,
4468
+ "host": "127.0.0.1",
4469
+ "max_body_bytes": 4194304
4470
+ },
4471
+ "retention": {
4472
+ "max_span_age_days": 7,
4473
+ "max_aggregate_age_days": 90,
4474
+ "prune_interval": 100
4475
+ },
4476
+ "mapping": {
4477
+ "fqn_attributes": ["code.function", "code.namespace", "code.filepath"],
4478
+ "route_patterns": ["^(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\\\\s+(.+)$"]
4479
+ }
4480
+ },
4481
+
4482
+ // \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
4483
+ "topology": {
4484
+ "enabled": true,
4485
+ // "repos": [], // extra repo paths to federate
4486
+ "auto_detect": true,
4487
+ "auto_federation": true
4488
+ // "contract_globs": [] // globs for API contract files
4489
+ },
4490
+
4491
+ // \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
4492
+ "quality_gates": {
4493
+ "enabled": true,
4494
+ "fail_on": "error", // "error" | "warning" | "none"
4495
+ "rules": {
4496
+ // "max_cyclomatic_complexity": { "threshold": 20, "severity": "error" },
4497
+ // "max_coupling_instability": { "threshold": 0.8, "severity": "warning" },
4498
+ // "max_circular_import_chains": { "threshold": 0, "severity": "error" },
4499
+ // "max_dead_exports_percent": { "threshold": 10, "severity": "warning" },
4500
+ // "max_tech_debt_grade": { "threshold": "C", "severity": "warning" },
4501
+ // "max_security_critical_findings": { "threshold": 0, "severity": "error" },
4502
+ // "max_antipattern_count": { "threshold": 5, "severity": "warning" },
4503
+ // "max_code_smell_count": { "threshold": 10, "severity": "warning" }
4504
+ }
4505
+ },
4506
+
4507
+ // \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
4508
+ "tools": {
4509
+ "preset": "full", // "full" | "minimal" | custom preset name
4510
+ // "include": [], // whitelist specific tools
4511
+ // "exclude": [], // blacklist specific tools
4512
+ // "descriptions": {}, // override tool descriptions
4513
+ "description_verbosity": "full", // "full" | "minimal" | "none"
4514
+ "instructions_verbosity": "full", // "full" | "minimal" | "none"
4515
+ "meta_fields": true // true | false | ["_hints", "_budget_warning", ...]
4516
+ },
4517
+
4518
+ // \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
4519
+ "ignore": {
4520
+ "directories": [], // extra directory names to skip
4521
+ "patterns": [] // extra gitignore-style patterns
4522
+ },
4523
+
4524
+ // \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
4525
+ "frameworks": {
4526
+ "laravel": {
4527
+ "artisan": { "enabled": true, "timeout": 10000 },
4528
+ "graceful_degradation": true
4529
+ }
4530
+ },
4531
+
4532
+ // \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
4533
+ "watch": {
4534
+ "enabled": true,
4535
+ "debounceMs": 2000
4536
+ },
4537
+
4538
+ // \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
4539
+ // Keys are absolute paths; values override any top-level setting for that project.
4540
+ // Example:
4541
+ // "projects": {
4542
+ // "/path/to/project": {
4543
+ // "ai": { "enabled": true, "concurrency": 4 },
4544
+ // "include": ["src/**/*.ts"],
4545
+ // "exclude": ["dist/**"]
4546
+ // }
4547
+ // }
4548
+ "projects": {}
4549
+ }
4550
+ `;
4349
4551
  function ensureGlobalDirs() {
4350
4552
  fs.mkdirSync(INDEX_DIR, { recursive: true });
4553
+ if (!fs.existsSync(GLOBAL_CONFIG_PATH)) {
4554
+ fs.writeFileSync(GLOBAL_CONFIG_PATH, DEFAULT_CONFIG_JSONC);
4555
+ }
4351
4556
  }
4352
4557
  function projectHash(absolutePath) {
4353
4558
  return crypto.createHash("sha256").update(absolutePath).digest("hex").slice(0, 12);
@@ -4569,7 +4774,7 @@ var TraceMcpConfigSchema = z.object({
4569
4774
  function loadGlobalConfigRaw() {
4570
4775
  if (!fs2.existsSync(GLOBAL_CONFIG_PATH)) return {};
4571
4776
  try {
4572
- return JSON.parse(fs2.readFileSync(GLOBAL_CONFIG_PATH, "utf-8"));
4777
+ return JSON.parse(stripJsonComments(fs2.readFileSync(GLOBAL_CONFIG_PATH, "utf-8")));
4573
4778
  } catch {
4574
4779
  return {};
4575
4780
  }
@@ -5947,7 +6152,7 @@ function extractSymbolSource(content, symbolName, symbolKind) {
5947
6152
  }
5948
6153
  let startLine = -1;
5949
6154
  for (let i = 0; i < lines.length; i++) {
5950
- if (patterns.some((p4) => p4.test(lines[i]))) {
6155
+ if (patterns.some((p5) => p5.test(lines[i]))) {
5951
6156
  startLine = i;
5952
6157
  break;
5953
6158
  }
@@ -7409,16 +7614,18 @@ var EnvIndexer = class {
7409
7614
 
7410
7615
  // src/indexer/pipeline.ts
7411
7616
  var IndexingPipeline = class _IndexingPipeline {
7412
- constructor(store, registry, config, rootPath) {
7617
+ constructor(store, registry, config, rootPath, progress) {
7413
7618
  this.store = store;
7414
7619
  this.registry = registry;
7415
7620
  this.config = config;
7416
7621
  this.rootPath = rootPath;
7622
+ this.progress = progress;
7417
7623
  }
7418
7624
  store;
7419
7625
  registry;
7420
7626
  config;
7421
7627
  rootPath;
7628
+ progress;
7422
7629
  workspaces = [];
7423
7630
  _lock = Promise.resolve();
7424
7631
  _projectContext;
@@ -7503,6 +7710,13 @@ var IndexingPipeline = class _IndexingPipeline {
7503
7710
  errors: 0,
7504
7711
  durationMs: 0
7505
7712
  };
7713
+ this.progress?.update("indexing", {
7714
+ phase: "running",
7715
+ processed: 0,
7716
+ total: relPaths.length,
7717
+ startedAt: Date.now(),
7718
+ completedAt: 0
7719
+ });
7506
7720
  this._projectContext = void 0;
7507
7721
  this.registry.clearCaches();
7508
7722
  this._changedFileIds.clear();
@@ -7549,6 +7763,8 @@ var IndexingPipeline = class _IndexingPipeline {
7549
7763
  persister.persistBatch(extractions);
7550
7764
  result.indexed += extractions.length;
7551
7765
  }
7766
+ const processed = result.indexed + result.skipped + result.errors;
7767
+ this.progress?.update("indexing", { processed });
7552
7768
  }
7553
7769
  enableFts5Triggers(this.store.db);
7554
7770
  const edgeResolver = new EdgeResolver(this.getPipelineState());
@@ -7574,6 +7790,11 @@ var IndexingPipeline = class _IndexingPipeline {
7574
7790
  }
7575
7791
  result.durationMs = Date.now() - startMs;
7576
7792
  result.incremental = this._isIncremental;
7793
+ this.progress?.update("indexing", {
7794
+ phase: "completed",
7795
+ processed: result.indexed + result.skipped + result.errors,
7796
+ completedAt: Date.now()
7797
+ });
7577
7798
  logger.info(result, "Indexing pipeline completed");
7578
7799
  return result;
7579
7800
  }
@@ -8169,14 +8390,16 @@ async function hybridSearch(db, query, vectorStore, embeddingService, limit, rer
8169
8390
  // src/ai/embedding-pipeline.ts
8170
8391
  var DEFAULT_BATCH_SIZE = 50;
8171
8392
  var EmbeddingPipeline = class {
8172
- constructor(store, embeddingService, vectorStore) {
8393
+ constructor(store, embeddingService, vectorStore, progress) {
8173
8394
  this.store = store;
8174
8395
  this.embeddingService = embeddingService;
8175
8396
  this.vectorStore = vectorStore;
8397
+ this.progress = progress;
8176
8398
  }
8177
8399
  store;
8178
8400
  embeddingService;
8179
8401
  vectorStore;
8402
+ progress;
8180
8403
  async indexSymbol(symbolId, text) {
8181
8404
  const embedding = await this.embeddingService.embed(text);
8182
8405
  if (embedding.length > 0) {
@@ -8184,10 +8407,48 @@ var EmbeddingPipeline = class {
8184
8407
  }
8185
8408
  }
8186
8409
  /**
8187
- * Find symbols that don't have embeddings yet and embed them.
8188
- * Returns the number of newly embedded symbols.
8410
+ * Find symbols that don't have embeddings yet and embed them in a loop.
8411
+ * Reports progress and returns the total number of newly embedded symbols.
8189
8412
  */
8190
8413
  async indexUnembedded(batchSize = DEFAULT_BATCH_SIZE) {
8414
+ const totalToEmbed = this.store.countUnembeddedSymbols();
8415
+ if (totalToEmbed === 0) return 0;
8416
+ this.progress?.update("embedding", {
8417
+ phase: "running",
8418
+ processed: 0,
8419
+ total: totalToEmbed,
8420
+ startedAt: Date.now(),
8421
+ completedAt: 0
8422
+ });
8423
+ let totalIndexed = 0;
8424
+ try {
8425
+ let batch;
8426
+ do {
8427
+ batch = await this.embedBatch(batchSize);
8428
+ totalIndexed += batch;
8429
+ if (batch > 0) {
8430
+ this.progress?.update("embedding", { processed: totalIndexed });
8431
+ }
8432
+ } while (batch > 0);
8433
+ this.progress?.update("embedding", {
8434
+ phase: "completed",
8435
+ processed: totalIndexed,
8436
+ completedAt: Date.now()
8437
+ });
8438
+ } catch (e) {
8439
+ this.progress?.update("embedding", {
8440
+ phase: "error",
8441
+ error: e instanceof Error ? e.message : String(e)
8442
+ });
8443
+ throw e;
8444
+ }
8445
+ return totalIndexed;
8446
+ }
8447
+ /**
8448
+ * Embed a single batch of unembedded symbols.
8449
+ * Returns the number of symbols embedded in this batch.
8450
+ */
8451
+ async embedBatch(batchSize) {
8191
8452
  const unembedded = this.store.db.prepare(`
8192
8453
  SELECT s.id, s.name, s.fqn, s.kind, s.signature, s.summary
8193
8454
  FROM symbols s
@@ -8218,13 +8479,7 @@ var EmbeddingPipeline = class {
8218
8479
  */
8219
8480
  async reindexAll() {
8220
8481
  this.store.db.exec("DELETE FROM symbol_embeddings");
8221
- let total = 0;
8222
- let batch;
8223
- do {
8224
- batch = await this.indexUnembedded(DEFAULT_BATCH_SIZE);
8225
- total += batch;
8226
- } while (batch > 0);
8227
- return total;
8482
+ return this.indexUnembedded(DEFAULT_BATCH_SIZE);
8228
8483
  }
8229
8484
  };
8230
8485
  function buildEmbeddingText(symbol) {
@@ -8406,31 +8661,53 @@ import fs12 from "fs";
8406
8661
  import path12 from "path";
8407
8662
  var MAX_SOURCE_LINES = 80;
8408
8663
  var SummarizationPipeline = class {
8409
- constructor(store, inferenceService, rootPath, config) {
8664
+ constructor(store, inferenceService, rootPath, config, progress) {
8410
8665
  this.store = store;
8411
8666
  this.inferenceService = inferenceService;
8412
8667
  this.rootPath = rootPath;
8413
8668
  this.config = config;
8669
+ this.progress = progress;
8414
8670
  }
8415
8671
  store;
8416
8672
  inferenceService;
8417
8673
  rootPath;
8418
8674
  config;
8675
+ progress;
8419
8676
  async summarizeUnsummarized() {
8420
8677
  let totalSummarized = 0;
8421
8678
  let batch;
8422
- do {
8423
- batch = this.store.getUnsummarizedSymbols(this.config.kinds, this.config.batchSize);
8424
- if (batch.length === 0) break;
8425
- const results = await this.summarizeBatch(batch);
8426
- for (const { id, summary } of results) {
8427
- this.store.updateSymbolSummary(id, summary);
8428
- totalSummarized++;
8429
- }
8430
- logger.debug({ batch: batch.length, total: totalSummarized }, "Summarization batch complete");
8431
- } while (batch.length === this.config.batchSize);
8432
- if (totalSummarized > 0) {
8433
- logger.info({ totalSummarized }, "Symbol summarization complete");
8679
+ const total = this.store.countUnsummarizedSymbols(this.config.kinds);
8680
+ if (total === 0) return 0;
8681
+ this.progress?.update("summarization", {
8682
+ phase: "running",
8683
+ processed: 0,
8684
+ total,
8685
+ startedAt: Date.now(),
8686
+ completedAt: 0
8687
+ });
8688
+ try {
8689
+ do {
8690
+ batch = this.store.getUnsummarizedSymbols(this.config.kinds, this.config.batchSize);
8691
+ if (batch.length === 0) break;
8692
+ const results = await this.summarizeBatch(batch);
8693
+ for (const { id, summary } of results) {
8694
+ this.store.updateSymbolSummary(id, summary);
8695
+ totalSummarized++;
8696
+ }
8697
+ this.progress?.update("summarization", { processed: totalSummarized });
8698
+ logger.debug({ batch: batch.length, total: totalSummarized }, "Summarization batch complete");
8699
+ } while (batch.length === this.config.batchSize);
8700
+ this.progress?.update("summarization", {
8701
+ phase: "completed",
8702
+ processed: totalSummarized,
8703
+ completedAt: Date.now()
8704
+ });
8705
+ } catch (e) {
8706
+ this.progress?.update("summarization", {
8707
+ phase: "error",
8708
+ error: e instanceof Error ? e.message : String(e)
8709
+ });
8710
+ throw e;
8434
8711
  }
8435
8712
  return totalSummarized;
8436
8713
  }
@@ -9232,8 +9509,8 @@ var SessionJournal = class _SessionJournal {
9232
9509
  };
9233
9510
  this.entries.push(entry);
9234
9511
  if (tool === "get_symbol" || tool === "get_outline") {
9235
- const path107 = params.path ?? params.file_path ?? "";
9236
- if (path107) this.filesRead.add(path107);
9512
+ const path108 = params.path ?? params.file_path ?? "";
9513
+ if (path108) this.filesRead.add(path108);
9237
9514
  }
9238
9515
  if (resultCount === 0 && this.isSearchTool(tool)) {
9239
9516
  this.zeroResultQueries.set(hash, summary);
@@ -9389,7 +9666,7 @@ var SessionJournal = class _SessionJournal {
9389
9666
  fileReads.set(file, existing);
9390
9667
  }
9391
9668
  }
9392
- const focusFiles = [...fileReads.entries()].sort((a, b) => b[1].reads - a[1].reads).slice(0, maxFiles).map(([p4, v]) => ({ path: p4, reads: v.reads, last_tool: v.lastTool }));
9669
+ const focusFiles = [...fileReads.entries()].sort((a, b) => b[1].reads - a[1].reads).slice(0, maxFiles).map(([p5, v]) => ({ path: p5, reads: v.reads, last_tool: v.lastTool }));
9393
9670
  const editedSet = /* @__PURE__ */ new Set();
9394
9671
  for (const entry of this.entries) {
9395
9672
  if (entry.tool === "register_edit") {
@@ -10020,7 +10297,7 @@ function getIndexHealth(store, config) {
10020
10297
  }
10021
10298
  function getProjectMap(store, registry, summaryOnly, projectContext) {
10022
10299
  const stats = store.getStats();
10023
- const frameworks = registry.getAllFrameworkPlugins().map((p4) => p4.manifest.name);
10300
+ const frameworks = registry.getAllFrameworkPlugins().map((p5) => p5.manifest.name);
10024
10301
  const detectedVersions = projectContext?.detectedVersions;
10025
10302
  if (summaryOnly) {
10026
10303
  const languageRows2 = store.db.prepare(
@@ -10133,7 +10410,7 @@ var W_KIND = 0.15;
10133
10410
  var W_SIGNATURE = 0.25;
10134
10411
  var W_TOKEN = 0.15;
10135
10412
  function tokenizeName(name) {
10136
- const parts = name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase().split(/[_\-./:]+/).filter((p4) => p4.length > 1);
10413
+ const parts = name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase().split(/[_\-./:]+/).filter((p5) => p5.length > 1);
10137
10414
  return new Set(parts);
10138
10415
  }
10139
10416
  function jaccard(a, b) {
@@ -10304,10 +10581,13 @@ function registerCoreTools(server, ctx) {
10304
10581
  const { store, registry, config, projectRoot, guardPath, j: j3, jh, journal } = ctx;
10305
10582
  server.tool(
10306
10583
  "get_index_health",
10307
- "Get index status, statistics, and health information",
10584
+ "Get index status, statistics, health information, and pipeline progress (indexing, summarization, embedding)",
10308
10585
  {},
10309
10586
  async () => {
10310
10587
  const result = getIndexHealth(store, config);
10588
+ if (ctx.progress) {
10589
+ result.progress = ctx.progress.snapshot();
10590
+ }
10311
10591
  return { content: [{ type: "text", text: j3(result) }] };
10312
10592
  }
10313
10593
  );
@@ -10699,8 +10979,8 @@ function getCoChangesForFile(store, filePath, graphFiles) {
10699
10979
  function findAffectedTests(store, targetPath, dependentPaths) {
10700
10980
  const seen = /* @__PURE__ */ new Set();
10701
10981
  const allPaths = [targetPath, ...dependentPaths];
10702
- for (const p4 of allPaths) {
10703
- const file = store.getFile(p4);
10982
+ for (const p5 of allPaths) {
10983
+ const file = store.getFile(p5);
10704
10984
  if (!file) continue;
10705
10985
  const fileNodeId = store.getNodeId("file", file.id);
10706
10986
  if (fileNodeId != null) {
@@ -10998,9 +11278,9 @@ function deduplicateByFile(rawDeps) {
10998
11278
  }
10999
11279
  }
11000
11280
  const result = [];
11001
- for (const [path107, entry] of fileMap) {
11281
+ for (const [path108, entry] of fileMap) {
11002
11282
  const dep = {
11003
- path: path107,
11283
+ path: path108,
11004
11284
  edgeTypes: [...entry.edgeTypes],
11005
11285
  depth: entry.depth
11006
11286
  };
@@ -11582,7 +11862,7 @@ function getContextBundle(store, rootPath, opts) {
11582
11862
  }
11583
11863
  primarySymbols.push({ sym, file });
11584
11864
  }
11585
- const primaryInternalIds = primarySymbols.map((p4) => p4.sym.id);
11865
+ const primaryInternalIds = primarySymbols.map((p5) => p5.sym.id);
11586
11866
  const primaryNodeMap = store.getNodeIdsBatch("symbol", primaryInternalIds);
11587
11867
  const primaryNodeIds = primaryInternalIds.map((id) => primaryNodeMap.get(id)).filter((n) => n != null);
11588
11868
  const seenDepIds = new Set(primaryInternalIds);
@@ -11657,7 +11937,7 @@ function getContextBundle(store, rootPath, opts) {
11657
11937
  }
11658
11938
  }
11659
11939
  const primaryItems = primarySymbols.map(
11660
- (p4, i) => toContextItem(p4.sym, p4.file, rootPath, 1 - i * 0.01)
11940
+ (p5, i) => toContextItem(p5.sym, p5.file, rootPath, 1 - i * 0.01)
11661
11941
  );
11662
11942
  const depItems = depSymbols.map(
11663
11943
  (d, i) => toContextItem(d.sym, d.file, rootPath, 0.8 - i * 5e-3)
@@ -11673,7 +11953,7 @@ function getContextBundle(store, rootPath, opts) {
11673
11953
  totalBudget: budget
11674
11954
  });
11675
11955
  const result = {
11676
- primary: primarySymbols.map((p4) => toBundleItem(p4.sym, p4.file)),
11956
+ primary: primarySymbols.map((p5) => toBundleItem(p5.sym, p5.file)),
11677
11957
  dependencies: depSymbols.slice(0, assembled.dependencies.length).map((d) => toBundleItem(d.sym, d.file)),
11678
11958
  callers: callerSymbols.slice(0, assembled.callers.length).map((c) => toBundleItem(c.sym, c.file)),
11679
11959
  totalTokens: assembled.totalTokens,
@@ -12096,7 +12376,7 @@ function suggestQueries(store) {
12096
12376
  // src/tools/navigation/related.ts
12097
12377
  import { ok as ok4, err as err5 } from "neverthrow";
12098
12378
  function tokenizeName2(name) {
12099
- const parts = name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase().split(/[_\-.]/).filter((p4) => p4.length > 1);
12379
+ const parts = name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase().split(/[_\-.]/).filter((p5) => p5.length > 1);
12100
12380
  return new Set(parts);
12101
12381
  }
12102
12382
  function jaccard2(a, b) {
@@ -13470,8 +13750,8 @@ var NestJSPlugin = class {
13470
13750
  const patterns = extractMicroservicePatterns(source);
13471
13751
  if (patterns.length > 0) {
13472
13752
  if (!result.routes) result.routes = [];
13473
- for (const p4 of patterns) {
13474
- result.routes.push({ method: p4.type === "message" ? "MSG" : "EVT", uri: p4.pattern });
13753
+ for (const p5 of patterns) {
13754
+ result.routes.push({ method: p5.type === "message" ? "MSG" : "EVT", uri: p5.pattern });
13475
13755
  }
13476
13756
  }
13477
13757
  }
@@ -14381,8 +14661,8 @@ function getLivewireContext(store, componentName) {
14381
14661
  const properties = [];
14382
14662
  const actions = [];
14383
14663
  if (Array.isArray(meta.properties)) {
14384
- for (const p4 of meta.properties) {
14385
- properties.push({ name: String(p4.name ?? ""), type: p4.type });
14664
+ for (const p5 of meta.properties) {
14665
+ properties.push({ name: String(p5.name ?? ""), type: p5.type });
14386
14666
  }
14387
14667
  }
14388
14668
  if (Array.isArray(meta.actions)) {
@@ -15026,18 +15306,18 @@ function getApiSurface(store, filePattern) {
15026
15306
  };
15027
15307
  }
15028
15308
  function getPluginRegistry(store, registry, activeFrameworkNames) {
15029
- const languagePlugins = registry.getLanguagePlugins().map((p4) => ({
15030
- name: p4.manifest.name,
15031
- version: p4.manifest.version,
15032
- priority: p4.manifest.priority,
15033
- extensions: p4.supportedExtensions
15309
+ const languagePlugins = registry.getLanguagePlugins().map((p5) => ({
15310
+ name: p5.manifest.name,
15311
+ version: p5.manifest.version,
15312
+ priority: p5.manifest.priority,
15313
+ extensions: p5.supportedExtensions
15034
15314
  }));
15035
- const frameworkPlugins = registry.getAllFrameworkPlugins().map((p4) => ({
15036
- name: p4.manifest.name,
15037
- version: p4.manifest.version,
15038
- priority: p4.manifest.priority,
15039
- dependencies: p4.manifest.dependencies ?? [],
15040
- active: activeFrameworkNames.has(p4.manifest.name)
15315
+ const frameworkPlugins = registry.getAllFrameworkPlugins().map((p5) => ({
15316
+ name: p5.manifest.name,
15317
+ version: p5.manifest.version,
15318
+ priority: p5.manifest.priority,
15319
+ dependencies: p5.manifest.dependencies ?? [],
15320
+ active: activeFrameworkNames.has(p5.manifest.name)
15041
15321
  }));
15042
15322
  const edgeTypes = store.getEdgeTypes();
15043
15323
  return {
@@ -15580,7 +15860,7 @@ function getComplexityTrend(store, cwd, filePath, options = {}) {
15580
15860
  function registerAnalysisTools(server, ctx) {
15581
15861
  const { store, registry, projectRoot, guardPath, j: j3, jh } = ctx;
15582
15862
  const frameworkNames = new Set(
15583
- registry.getAllFrameworkPlugins().map((p4) => p4.manifest.name)
15863
+ registry.getAllFrameworkPlugins().map((p5) => p5.manifest.name)
15584
15864
  );
15585
15865
  server.tool(
15586
15866
  "get_implementations",
@@ -15890,7 +16170,7 @@ var BARREL_PATTERNS = [
15890
16170
  ];
15891
16171
  function isBarrelFile(filePath) {
15892
16172
  const base = path30.basename(filePath);
15893
- return BARREL_PATTERNS.some((p4) => p4.test(base));
16173
+ return BARREL_PATTERNS.some((p5) => p5.test(base));
15894
16174
  }
15895
16175
  function buildImportedNamesSet(store) {
15896
16176
  const importedNames = /* @__PURE__ */ new Set();
@@ -17057,7 +17337,7 @@ function detectEmptyFunctions(content, lines, symbols, filePath, language) {
17057
17337
  const strippedLines = body.split("\n").map((l) => l.trim()).filter((l) => l.length > 0).filter((l) => !/^(?:\/\/|#|--|\/\*|\*\/|\*)\s*$/.test(l));
17058
17338
  const joined = strippedLines.join("\n");
17059
17339
  const isEmpty = strippedLines.length === 0;
17060
- const isStub = !isEmpty && strippedLines.length <= 2 && STUB_BODY_PATTERNS.some((p4) => p4.test(joined));
17340
+ const isStub = !isEmpty && strippedLines.length <= 2 && STUB_BODY_PATTERNS.some((p5) => p5.test(joined));
17061
17341
  if (isEmpty || isStub) {
17062
17342
  const description = isEmpty ? `Empty ${sym.kind} '${sym.name}' \u2014 no implementation` : `Stub ${sym.kind} '${sym.name}' \u2014 placeholder implementation`;
17063
17343
  findings.push({
@@ -17189,8 +17469,8 @@ function detectHardcodedValues(lines, filePath) {
17189
17469
  return findings;
17190
17470
  }
17191
17471
  var PRIORITY_ORDER = { high: 0, medium: 1, low: 2 };
17192
- function priorityRank(p4) {
17193
- return PRIORITY_ORDER[p4];
17472
+ function priorityRank(p5) {
17473
+ return PRIORITY_ORDER[p5];
17194
17474
  }
17195
17475
  var BATCH_SIZE2 = 100;
17196
17476
  var MAX_FILE_SIZE2 = 512 * 1024;
@@ -17659,8 +17939,8 @@ function interProceduralAnalysis(store, projectRoot, perFileResults, limit) {
17659
17939
  const sig = sym.signature ?? "";
17660
17940
  const paramMatch = sig.match(/\(([^)]*)\)/);
17661
17941
  if (paramMatch) {
17662
- const params = paramMatch[1].split(",").map((p4) => {
17663
- const trimmed = p4.trim();
17942
+ const params = paramMatch[1].split(",").map((p5) => {
17943
+ const trimmed = p5.trim();
17664
17944
  const parts = trimmed.split(/[\s:]+/);
17665
17945
  return parts[0].replace(/^[&*$]/, "").trim();
17666
17946
  });
@@ -17698,7 +17978,7 @@ function taintAnalysis(store, projectRoot, opts = {}) {
17698
17978
  const sinkFilter = opts.sinks ? new Set(opts.sinks) : null;
17699
17979
  const scope = opts.scope?.replace(/\/+$/, "");
17700
17980
  const files = scope ? store.db.prepare("SELECT path, language FROM files WHERE path LIKE ? AND (status = 'ok' OR status IS NULL)").all(`${scope}%`) : store.db.prepare("SELECT path, language FROM files WHERE (status = 'ok' OR status IS NULL)").all();
17701
- const isTest = (p4) => /(?:^|\/)(?:tests?|__tests__|spec)\/|\.(?:test|spec)\.\w+$/.test(p4);
17981
+ const isTest = (p5) => /(?:^|\/)(?:tests?|__tests__|spec)\/|\.(?:test|spec)\.\w+$/.test(p5);
17702
17982
  const sourceFiles = files.filter((f) => !isTest(f.path) && f.language);
17703
17983
  let allFlows = [];
17704
17984
  let analyzed = 0;
@@ -19221,7 +19501,7 @@ ${bodyLines.join("\n")}`;
19221
19501
  callSite = `${baseIndent}${functionName}(${paramStr})`;
19222
19502
  }
19223
19503
  } else if (lang === "go") {
19224
- const paramStr = params.map((p4) => `${p4} interface{}`).join(", ");
19504
+ const paramStr = params.map((p5) => `${p5} interface{}`).join(", ");
19225
19505
  const returnTypes = returns.length > 0 ? ` (${returns.map(() => "interface{}").join(", ")})` : "";
19226
19506
  functionDef = `func ${functionName}(${paramStr})${returnTypes} {
19227
19507
  ${bodyLines.join("\n")}`;
@@ -19842,7 +20122,7 @@ CREATE INDEX IF NOT EXISTS idx_client_calls_target ON client_calls(target_repo_i
19842
20122
  CREATE INDEX IF NOT EXISTS idx_client_calls_endpoint ON client_calls(matched_endpoint_id);
19843
20123
  `;
19844
20124
  function findBestEndpointMatch(urlPattern, method, endpoints) {
19845
- const normalize = (p4) => p4.replace(/\{[^}]+\}/g, "{*}").replace(/:[\w]+/g, "{*}").replace(/\/+$/, "");
20125
+ const normalize = (p5) => p5.replace(/\{[^}]+\}/g, "{*}").replace(/:[\w]+/g, "{*}").replace(/\/+$/, "");
19846
20126
  const normalizedUrl = normalize(urlPattern);
19847
20127
  let bestMatch = null;
19848
20128
  let bestScore = 0;
@@ -20216,9 +20496,9 @@ function detectFromDockerCompose(root) {
20216
20496
  const composeFiles = ["docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"];
20217
20497
  let composePath;
20218
20498
  for (const f of composeFiles) {
20219
- const p4 = path36.join(root, f);
20220
- if (fs25.existsSync(p4)) {
20221
- composePath = p4;
20499
+ const p5 = path36.join(root, f);
20500
+ if (fs25.existsSync(p5)) {
20501
+ composePath = p5;
20222
20502
  break;
20223
20503
  }
20224
20504
  }
@@ -21692,9 +21972,9 @@ var RuntimeAggregator = class {
21692
21972
  return { bucketsUpdated: rows.length, nodesAffected: nodesAffected.size };
21693
21973
  }
21694
21974
  };
21695
- function percentile(sorted, p4) {
21975
+ function percentile(sorted, p5) {
21696
21976
  if (sorted.length === 0) return 0;
21697
- const idx = Math.ceil(p4 * sorted.length) - 1;
21977
+ const idx = Math.ceil(p5 * sorted.length) - 1;
21698
21978
  return sorted[Math.max(0, idx)];
21699
21979
  }
21700
21980
 
@@ -21706,7 +21986,7 @@ var RuntimeIntelligence = class {
21706
21986
  this.ingester = new SpanIngester(store.db, config.retention.prune_interval);
21707
21987
  this.mapper = new SpanMapper(store, {
21708
21988
  fqnAttributes: config.mapping.fqn_attributes,
21709
- routePatterns: config.mapping.route_patterns.map((p4) => new RegExp(p4))
21989
+ routePatterns: config.mapping.route_patterns.map((p5) => new RegExp(p5))
21710
21990
  });
21711
21991
  this.aggregator = new RuntimeAggregator(store.db);
21712
21992
  }
@@ -22889,16 +23169,16 @@ function findShortestPath(store, startNodeId, endNodeId, maxDepth) {
22889
23169
  parent.set(nodeId, { from, edgeType: edge.edge_type_name });
22890
23170
  nextFrontier.push(nodeId);
22891
23171
  if (nodeId === endNodeId) {
22892
- const path107 = [endNodeId];
23172
+ const path108 = [endNodeId];
22893
23173
  const edgeTypes = [];
22894
23174
  let cur = endNodeId;
22895
23175
  while (cur !== startNodeId) {
22896
- const p4 = parent.get(cur);
22897
- path107.unshift(p4.from);
22898
- edgeTypes.unshift(p4.edgeType);
22899
- cur = p4.from;
23176
+ const p5 = parent.get(cur);
23177
+ path108.unshift(p5.from);
23178
+ edgeTypes.unshift(p5.edgeType);
23179
+ cur = p5.from;
22900
23180
  }
22901
- return { path: path107, edgeTypes };
23181
+ return { path: path108, edgeTypes };
22902
23182
  }
22903
23183
  }
22904
23184
  }
@@ -22920,7 +23200,7 @@ function generateMermaid(nodes, edges, paths) {
22920
23200
  lines.push(` ${id}["${label}"]`);
22921
23201
  }
22922
23202
  if (paths && paths.length > 0) {
22923
- const pathSymbols = new Set(paths.flatMap((p4) => p4.map((s) => s.symbol_id)));
23203
+ const pathSymbols = new Set(paths.flatMap((p5) => p5.map((s) => s.symbol_id)));
22924
23204
  const pathIds = [...pathSymbols].map((sid) => idMap.get(sid)).filter(Boolean);
22925
23205
  if (pathIds.length > 0) {
22926
23206
  lines.push(` style ${pathIds.join(",")} fill:#f9f,stroke:#333,stroke-width:2px`);
@@ -23182,7 +23462,7 @@ function getDataflow(store, projectRoot, opts) {
23182
23462
  const retMatch = line.match(/\breturn\s+(.+?)(?:;|\s*$)/);
23183
23463
  if (retMatch) {
23184
23464
  const expr = retMatch[1].trim();
23185
- const sources = params.map((p4) => p4.name).filter((name) => new RegExp(`\\b${escapeRegex3(name)}\\b`).test(expr));
23465
+ const sources = params.map((p5) => p5.name).filter((name) => new RegExp(`\\b${escapeRegex3(name)}\\b`).test(expr));
23186
23466
  returns.push({ expression: expr, line: absLine, sources });
23187
23467
  }
23188
23468
  }
@@ -23193,7 +23473,7 @@ function getDataflow(store, projectRoot, opts) {
23193
23473
  const assignMatch = line.match(/(?:const|let|var)\s+(\w+)\s*=\s*(.+?)(?:;|\s*$)/);
23194
23474
  if (assignMatch) {
23195
23475
  const [, varName, source] = assignMatch;
23196
- const involvesParam = params.some((p4) => new RegExp(`\\b${escapeRegex3(p4.name)}\\b`).test(source));
23476
+ const involvesParam = params.some((p5) => new RegExp(`\\b${escapeRegex3(p5.name)}\\b`).test(source));
23197
23477
  if (involvesParam || /\w+\s*\(/.test(source)) {
23198
23478
  localAssignments.push({
23199
23479
  name: varName,
@@ -23220,15 +23500,15 @@ function parseParameters(signature) {
23220
23500
  if (!match) return [];
23221
23501
  const paramStr = match[1].trim();
23222
23502
  if (!paramStr) return [];
23223
- return paramStr.split(",").map((p4) => {
23224
- const trimmed = p4.trim();
23503
+ return paramStr.split(",").map((p5) => {
23504
+ const trimmed = p5.trim();
23225
23505
  const tsMatch = trimmed.match(/^(\w+)\??\s*:\s*(.+)$/);
23226
23506
  if (tsMatch) return { name: tsMatch[1], type: tsMatch[2].trim() };
23227
23507
  const pyMatch = trimmed.match(/^(\w+)(?:\s*:\s*(.+))?$/);
23228
23508
  if (pyMatch) return { name: pyMatch[1], type: pyMatch[2]?.trim() ?? null };
23229
23509
  const word = trimmed.split(/\s/)[0];
23230
23510
  return { name: word, type: null };
23231
- }).filter((p4) => p4.name && p4.name !== "...");
23511
+ }).filter((p5) => p5.name && p5.name !== "...");
23232
23512
  }
23233
23513
  function buildCalleeMap(store, symbol) {
23234
23514
  const map = /* @__PURE__ */ new Map();
@@ -23859,7 +24139,7 @@ function predictBugs(store, cwd, options = {}) {
23859
24139
  const couplingMap = /* @__PURE__ */ new Map();
23860
24140
  for (const c of couplingResults) couplingMap.set(c.file, c);
23861
24141
  const pagerankMap = /* @__PURE__ */ new Map();
23862
- for (const p4 of pagerankResults) pagerankMap.set(p4.file, p4);
24142
+ for (const p5 of pagerankResults) pagerankMap.set(p5.file, p5);
23863
24143
  const complexityRows = store.db.prepare(`
23864
24144
  SELECT f.path, MAX(s.cyclomatic) as max_cyclomatic
23865
24145
  FROM symbols s JOIN files f ON s.file_id = f.id
@@ -25365,7 +25645,7 @@ function autoLabel(files) {
25365
25645
  const segments = /* @__PURE__ */ new Map();
25366
25646
  const ignore = /* @__PURE__ */ new Set(["src", "lib", "app", "dist", "build", "node_modules", "vendor", "index"]);
25367
25647
  for (const file of files) {
25368
- const parts = file.split("/").filter((p4) => !ignore.has(p4) && !p4.includes("."));
25648
+ const parts = file.split("/").filter((p5) => !ignore.has(p5) && !p5.includes("."));
25369
25649
  for (const part of parts) {
25370
25650
  segments.set(part, (segments.get(part) ?? 0) + 1);
25371
25651
  }
@@ -26099,8 +26379,8 @@ function getPackageDeps(options) {
26099
26379
  const manifest = readManifest(absPath);
26100
26380
  repoManifests.set(repoPath, manifest);
26101
26381
  const allPublishes = [...entry.publishes ?? [], ...manifest.publishes];
26102
- for (const p4 of new Set(allPublishes)) {
26103
- publishMap.set(p4, { repo: entry.name ?? path44.basename(repoPath), repoPath });
26382
+ for (const p5 of new Set(allPublishes)) {
26383
+ publishMap.set(p5, { repo: entry.name ?? path44.basename(repoPath), repoPath });
26104
26384
  }
26105
26385
  }
26106
26386
  const results = [];
@@ -26111,8 +26391,8 @@ function getPackageDeps(options) {
26111
26391
  for (const [repoPath, manifest] of repoManifests) {
26112
26392
  const entry = registry[repoPath];
26113
26393
  if (entry?.name === project || path44.basename(repoPath) === project) {
26114
- for (const p4 of manifest.publishes) {
26115
- targetPackages.add(p4);
26394
+ for (const p5 of manifest.publishes) {
26395
+ targetPackages.add(p5);
26116
26396
  }
26117
26397
  }
26118
26398
  }
@@ -26149,7 +26429,7 @@ function getPackageDeps(options) {
26149
26429
  for (const [repoPath, manifest] of repoManifests) {
26150
26430
  const entry = registry[repoPath];
26151
26431
  const repoName = entry?.name ?? path44.basename(repoPath);
26152
- const isTarget = manifest.publishes.some((p4) => targetPackages.has(p4));
26432
+ const isTarget = manifest.publishes.some((p5) => targetPackages.has(p5));
26153
26433
  if (!isTarget && targetPackages.size > 0) continue;
26154
26434
  for (const [dep, info] of manifest.deps) {
26155
26435
  const publisher = publishMap.get(dep);
@@ -26685,10 +26965,10 @@ function getScopeFiles(store, scope, scopePath, query, limit) {
26685
26965
  function buildFileTree(paths) {
26686
26966
  const lines = [];
26687
26967
  const sorted = paths.sort();
26688
- for (const p4 of sorted.slice(0, 200)) {
26689
- const depth = p4.split("/").length - 1;
26968
+ for (const p5 of sorted.slice(0, 200)) {
26969
+ const depth = p5.split("/").length - 1;
26690
26970
  const indent = " ".repeat(Math.min(depth, 6));
26691
- lines.push(`${indent}${path45.basename(p4)}`);
26971
+ lines.push(`${indent}${path45.basename(p5)}`);
26692
26972
  }
26693
26973
  if (sorted.length > 200) {
26694
26974
  lines.push(` ... and ${sorted.length - 200} more files`);
@@ -27462,10 +27742,10 @@ function getManifestPath() {
27462
27742
  return path47.join(BUNDLES_DIR, "manifest.json");
27463
27743
  }
27464
27744
  function loadManifest() {
27465
- const p4 = getManifestPath();
27466
- if (!fs35.existsSync(p4)) return { bundles: [] };
27745
+ const p5 = getManifestPath();
27746
+ if (!fs35.existsSync(p5)) return { bundles: [] };
27467
27747
  try {
27468
- return JSON.parse(fs35.readFileSync(p4, "utf-8"));
27748
+ return JSON.parse(fs35.readFileSync(p5, "utf-8"));
27469
27749
  } catch {
27470
27750
  return { bundles: [] };
27471
27751
  }
@@ -27761,8 +28041,8 @@ var AnalyticsStore = class {
27761
28041
  db;
27762
28042
  constructor(dbPath) {
27763
28043
  ensureGlobalDirs();
27764
- const p4 = dbPath ?? ANALYTICS_DB_PATH;
27765
- this.db = new Database5(p4);
28044
+ const p5 = dbPath ?? ANALYTICS_DB_PATH;
28045
+ this.db = new Database5(p5);
27766
28046
  this.db.pragma("journal_mode = WAL");
27767
28047
  this.db.pragma("foreign_keys = OFF");
27768
28048
  this.db.exec(SCHEMA_SQL);
@@ -28751,6 +29031,94 @@ function benchmarkTaskContext(store, symbols, files, count, rand) {
28751
29031
  });
28752
29032
  return buildScenario("composite_task", "NL task \u2192 optimal code context (baseline: search + read 5-8 files + grep)", details);
28753
29033
  }
29034
+ function benchmarkFindUsages(store, symbols, count, rand) {
29035
+ const sampled = sample(symbols.filter((s) => s.kind === "function" || s.kind === "method"), count, rand);
29036
+ const details = sampled.map((s) => {
29037
+ const grepMatches = 10;
29038
+ const grepContextLines = 5;
29039
+ const grepChars = grepMatches * grepContextLines * 80;
29040
+ const bl = estimateTokens3(grepChars);
29041
+ const tm = estimateTokens3(grepMatches * 60);
29042
+ return { query: s.name, file: s.file_path, baseline_tokens: bl, trace_mcp_tokens: tm, reduction_pct: reductionPct(bl, tm) };
29043
+ });
29044
+ return buildScenario("find_usages", "All usages of a symbol (baseline: grep with context lines)", details);
29045
+ }
29046
+ function benchmarkContextBundle(store, symbols, count, rand) {
29047
+ const funcs = symbols.filter((s) => s.kind === "function" || s.kind === "method");
29048
+ const batchSize = 3;
29049
+ const batches = Math.min(count, Math.floor(funcs.length / batchSize));
29050
+ const sampled = sample(funcs, batches * batchSize, rand);
29051
+ const details = [];
29052
+ for (let i = 0; i < batches; i++) {
29053
+ const group = sampled.slice(i * batchSize, (i + 1) * batchSize);
29054
+ const totalSourceBytes = group.reduce((s, sym) => s + (sym.source_bytes || Math.round(sym.file_byte_length * 0.08)), 0);
29055
+ const importOverhead = group.length * 200;
29056
+ const bl = estimateTokens3(totalSourceBytes + importOverhead);
29057
+ const tm = estimateTokens3(Math.round((totalSourceBytes + importOverhead) * 0.6));
29058
+ const names = group.map((g) => g.name).join(", ");
29059
+ details.push({ query: names, file: group[0].file_path, baseline_tokens: bl, trace_mcp_tokens: tm, reduction_pct: reductionPct(bl, tm) });
29060
+ }
29061
+ return buildScenario("context_bundle", "Batch symbol+imports lookup (baseline: N \xD7 get_symbol + Read imports)", details);
29062
+ }
29063
+ function benchmarkBatchOverhead(symbols, files, count, rand) {
29064
+ const batchSize = 3;
29065
+ const batches = Math.min(count, Math.floor(symbols.length / batchSize));
29066
+ const sampledSymbols = sample(symbols, batches * batchSize, rand);
29067
+ const details = [];
29068
+ for (let i = 0; i < batches; i++) {
29069
+ const group = sampledSymbols.slice(i * batchSize, (i + 1) * batchSize);
29070
+ const perCallOverhead = 150;
29071
+ const contentTokens = group.reduce((s, sym) => s + estimateTokens3(sym.source_bytes || 200), 0);
29072
+ const bl = contentTokens + batchSize * perCallOverhead;
29073
+ const tm = contentTokens + perCallOverhead;
29074
+ const names = group.map((g) => g.name).join(", ");
29075
+ details.push({ query: names, file: group[0].file_path, baseline_tokens: bl, trace_mcp_tokens: tm, reduction_pct: reductionPct(bl, tm) });
29076
+ }
29077
+ return buildScenario("batch_overhead", "N independent queries batched (baseline: N separate MCP round-trips)", details);
29078
+ }
29079
+ function benchmarkTypeHierarchy(store, symbols, count, rand) {
29080
+ const types = symbols.filter((s) => s.kind === "interface" || s.kind === "class");
29081
+ const sampled = sample(types, count, rand);
29082
+ const details = sampled.map((s) => {
29083
+ const nodeRow = store.db.prepare(
29084
+ "SELECT n.id FROM nodes n JOIN symbols sym ON n.ref_id = sym.id AND n.node_type = ? WHERE sym.symbol_id = ?"
29085
+ ).get("symbol", s.symbol_id);
29086
+ let implFileBytes = 0;
29087
+ let implCount = 0;
29088
+ if (nodeRow) {
29089
+ const impls = store.db.prepare(`
29090
+ SELECT DISTINCT f.byte_length FROM edges e
29091
+ JOIN edge_types et ON e.edge_type_id = et.id
29092
+ JOIN nodes n2 ON e.source_node_id = n2.id AND n2.node_type = 'symbol'
29093
+ JOIN symbols s2 ON n2.ref_id = s2.id
29094
+ JOIN files f ON s2.file_id = f.id
29095
+ WHERE e.target_node_id = ?
29096
+ AND et.name IN ('implements', 'extends')
29097
+ LIMIT 10
29098
+ `).all(nodeRow.id);
29099
+ implFileBytes = impls.reduce((sum, d) => sum + (d.byte_length || 0), 0);
29100
+ implCount = impls.length;
29101
+ }
29102
+ const grepChars = Math.max(implCount, 3) * 5 * 80;
29103
+ const readChars = implFileBytes || s.file_byte_length * 3;
29104
+ const bl = estimateTokens3(grepChars + readChars);
29105
+ const tm = estimateTokens3(Math.max(implCount, 3) * 120);
29106
+ return { query: s.name, file: s.file_path, baseline_tokens: bl, trace_mcp_tokens: tm, reduction_pct: reductionPct(bl, tm) };
29107
+ });
29108
+ return buildScenario("type_hierarchy", "Find all implementations of interface/class (baseline: grep + read files)", details);
29109
+ }
29110
+ function benchmarkTestsFor(store, symbols, count, rand) {
29111
+ const sampled = sample(symbols.filter((s) => s.kind === "function" || s.kind === "method"), count, rand);
29112
+ const details = sampled.map((s) => {
29113
+ const globTokens = 200;
29114
+ const grepTokens = 3 * 5 * 80 / 3.5;
29115
+ const testFileReadTokens = 2 * estimateTokens3(3e3);
29116
+ const bl = Math.round(globTokens + grepTokens + testFileReadTokens);
29117
+ const tm = estimateTokens3(400);
29118
+ return { query: s.name, file: s.file_path, baseline_tokens: bl, trace_mcp_tokens: tm, reduction_pct: reductionPct(bl, tm) };
29119
+ });
29120
+ return buildScenario("tests_for", "Find tests for a symbol (baseline: glob + grep + read test files)", details);
29121
+ }
28754
29122
  function runBenchmark(store, opts = {}) {
28755
29123
  const n = opts.queries ?? 10;
28756
29124
  const rand = seededRandom(opts.seed ?? 42);
@@ -28760,8 +29128,13 @@ function runBenchmark(store, opts = {}) {
28760
29128
  benchmarkSymbolLookup(symbols, n, rand),
28761
29129
  benchmarkFileExploration(files, n, rand),
28762
29130
  benchmarkSearch(symbols, n, rand),
29131
+ benchmarkFindUsages(store, symbols, n, rand),
29132
+ benchmarkContextBundle(store, symbols, n, rand),
29133
+ benchmarkBatchOverhead(symbols, files, n, rand),
28763
29134
  benchmarkImpactAnalysis(store, symbols, n, rand),
28764
29135
  benchmarkCallGraph(store, symbols, n, rand),
29136
+ benchmarkTypeHierarchy(store, symbols, n, rand),
29137
+ benchmarkTestsFor(store, symbols, n, rand),
28765
29138
  benchmarkTaskContext(store, symbols, files, n, rand)
28766
29139
  ];
28767
29140
  const totalQueries = scenarios.reduce((s, sc) => s + sc.queries, 0);
@@ -29329,11 +29702,11 @@ function registerPrompts(server, ctx) {
29329
29702
  sections.push("");
29330
29703
  }
29331
29704
  }
29332
- const deadCode = safe2(() => getDeadCodeV2(store, { threshold: 0.5, limit: 10 }), { items: [] });
29333
- if (deadCode.items && deadCode.items.length > 0) {
29334
- sections.push(`## Dead Code Candidates (${deadCode.items.length})
29705
+ const deadCode = safe2(() => getDeadCodeV2(store, { threshold: 0.5, limit: 10 }), { dead_symbols: [], file_pattern: null, total_exports: 0, total_dead: 0, threshold: 0.5 });
29706
+ if (deadCode.dead_symbols && deadCode.dead_symbols.length > 0) {
29707
+ sections.push(`## Dead Code Candidates (${deadCode.dead_symbols.length})
29335
29708
  `);
29336
- for (const d of deadCode.items.slice(0, 5)) {
29709
+ for (const d of deadCode.dead_symbols.slice(0, 5)) {
29337
29710
  sections.push(`- ${d.name} in ${d.file} (confidence: ${d.confidence})`);
29338
29711
  }
29339
29712
  sections.push("");
@@ -29372,10 +29745,10 @@ Provide a thorough code review with risk assessment, suggested tests, and archit
29372
29745
  sections.push("```json");
29373
29746
  sections.push(JSON.stringify(map, null, 2));
29374
29747
  sections.push("```\n");
29375
- const health = safe2(() => getRepoHealth(store), {});
29748
+ const health = safe2(() => getRepoHealth(store), null);
29376
29749
  sections.push("## Architecture Health\n");
29377
- sections.push(`- Coupling metrics: ${health.coupling_summary?.total ?? "N/A"} files analyzed`);
29378
- sections.push(`- Dependency cycles: ${health.cycles?.length ?? 0}`);
29750
+ sections.push(`- Files in graph: ${health?.summary?.files_in_graph ?? "N/A"}`);
29751
+ sections.push(`- Dependency cycles: ${health?.cycles?.length ?? 0}`);
29379
29752
  sections.push("");
29380
29753
  sections.push("## Key Entry Points\n");
29381
29754
  const context = safe2(() => getFeatureContext(store, projectRoot, "main entry point application startup", 4e3), { symbols: [] });
@@ -29458,7 +29831,7 @@ Analyze this bug. Identify the most likely failure point, suggest debugging step
29458
29831
  sections.push(`## Dependency Cycles: ${cycles.length}
29459
29832
  `);
29460
29833
  for (const c of cycles.slice(0, 5)) {
29461
- sections.push(`- ${c.join(" \u2192 ")}`);
29834
+ sections.push(`- ${c.files.join(" \u2192 ")}`);
29462
29835
  }
29463
29836
  sections.push("");
29464
29837
  const debt = safe2(() => getTechDebt(store, projectRoot, {
@@ -29539,11 +29912,11 @@ Analyze this project's architecture health. Identify the most critical issues an
29539
29912
  sections.push(...riskFiles);
29540
29913
  sections.push("");
29541
29914
  }
29542
- const dead = safe2(() => getDeadCodeV2(store, { threshold: 0.6, limit: 10 }), { items: [] });
29543
- if (dead.items && dead.items.length > 0) {
29544
- sections.push(`## Potential Dead Code: ${dead.items.length}
29915
+ const dead = safe2(() => getDeadCodeV2(store, { threshold: 0.6, limit: 10 }), { dead_symbols: [], file_pattern: null, total_exports: 0, total_dead: 0, threshold: 0.6 });
29916
+ if (dead.dead_symbols && dead.dead_symbols.length > 0) {
29917
+ sections.push(`## Potential Dead Code: ${dead.dead_symbols.length}
29545
29918
  `);
29546
- for (const d of dead.items.slice(0, 5)) {
29919
+ for (const d of dead.dead_symbols.slice(0, 5)) {
29547
29920
  sections.push(`- ${d.name} (${d.file})`);
29548
29921
  }
29549
29922
  sections.push("");
@@ -29884,7 +30257,7 @@ function registerSessionTools(server, ctx) {
29884
30257
  }
29885
30258
 
29886
30259
  // src/server/server.ts
29887
- var PKG_VERSION = true ? "1.6.1" : "0.0.0-dev";
30260
+ var PKG_VERSION = true ? "1.7.0" : "0.0.0-dev";
29888
30261
  function j2(value) {
29889
30262
  return JSON.stringify(value, (_key, val) => val === null || val === void 0 ? void 0 : val);
29890
30263
  }
@@ -29988,10 +30361,10 @@ function extractCompactResult(toolName, response) {
29988
30361
  return void 0;
29989
30362
  }
29990
30363
  }
29991
- function createServer2(store, registry, config, rootPath) {
30364
+ function createServer2(store, registry, config, rootPath, progress) {
29992
30365
  const projectRoot = rootPath ?? process.cwd();
29993
30366
  const frameworkNames = new Set(
29994
- registry.getAllFrameworkPlugins().map((p4) => p4.manifest.name)
30367
+ registry.getAllFrameworkPlugins().map((p5) => p5.manifest.name)
29995
30368
  );
29996
30369
  const has = (...names) => names.some((n) => frameworkNames.has(n));
29997
30370
  const detectedFrameworks = [...frameworkNames].join(", ") || "none";
@@ -30097,7 +30470,7 @@ function createServer2(store, registry, config, rootPath) {
30097
30470
  const embeddingService = config.ai?.enabled ? aiProvider.embedding() : null;
30098
30471
  const reranker = config.ai?.enabled ? new LLMReranker(aiProvider.fastInference()) : null;
30099
30472
  if (config.watch?.enabled !== false) {
30100
- const knownExtensions = new Set(registry.getLanguagePlugins().flatMap((p4) => p4.supportedExtensions));
30473
+ const knownExtensions = new Set(registry.getLanguagePlugins().flatMap((p5) => p5.supportedExtensions));
30101
30474
  const fileWatcher = new FileWatcher(
30102
30475
  projectRoot,
30103
30476
  async (files) => {
@@ -30135,7 +30508,8 @@ function createServer2(store, registry, config, rootPath) {
30135
30508
  guardPath,
30136
30509
  j: j2,
30137
30510
  jh,
30138
- markExplored: explored.markExplored
30511
+ markExplored: explored.markExplored,
30512
+ progress: progress ?? null
30139
30513
  };
30140
30514
  const metaCtx = {
30141
30515
  ...ctx,
@@ -31535,8 +31909,8 @@ function extractProps(scriptContent) {
31535
31909
  const body = typeMatch[1];
31536
31910
  const propNames = body.match(/(\w+)\s*[?]?\s*:/g);
31537
31911
  if (propNames) {
31538
- for (const p4 of propNames) {
31539
- props.push(p4.replace(/\s*[?]?\s*:/, ""));
31912
+ for (const p5 of propNames) {
31913
+ props.push(p5.replace(/\s*[?]?\s*:/, ""));
31540
31914
  }
31541
31915
  }
31542
31916
  return props;
@@ -31556,8 +31930,8 @@ function extractProps(scriptContent) {
31556
31930
  const body = objectMatch[1];
31557
31931
  const propNames = body.match(/(\w+)\s*:/g);
31558
31932
  if (propNames) {
31559
- for (const p4 of propNames) {
31560
- const name = p4.replace(/\s*:/, "");
31933
+ for (const p5 of propNames) {
31934
+ const name = p5.replace(/\s*:/, "");
31561
31935
  if (!["type", "default", "required", "validator"].includes(name)) {
31562
31936
  props.push(name);
31563
31937
  }
@@ -31692,7 +32066,7 @@ var VueLanguagePlugin = class {
31692
32066
  kind: "component",
31693
32067
  framework: "vue",
31694
32068
  ...props.length > 0 && {
31695
- props: Object.fromEntries(props.map((p4) => [p4, { type: "unknown" }]))
32069
+ props: Object.fromEntries(props.map((p5) => [p5, { type: "unknown" }]))
31696
32070
  },
31697
32071
  ...emits.length > 0 && { emits },
31698
32072
  ...composables.length > 0 && { composables }
@@ -34281,9 +34655,9 @@ function extractImplMethods(body, filePath, typeName, typeSymbolId) {
34281
34655
  if (isPublic(child)) meta.exported = 1;
34282
34656
  const params = child.childForFieldName("parameters");
34283
34657
  if (params) {
34284
- for (const p4 of params.namedChildren) {
34285
- if (p4.type === "self_parameter") {
34286
- meta.receiver = p4.text;
34658
+ for (const p5 of params.namedChildren) {
34659
+ if (p5.type === "self_parameter") {
34660
+ meta.receiver = p5.text;
34287
34661
  break;
34288
34662
  }
34289
34663
  }
@@ -36575,7 +36949,7 @@ var ObjCLanguagePlugin = class {
36575
36949
  const selectorLine = m[2].trim();
36576
36950
  const parts = selectorLine.match(/\w+(?=\s*:)|^\w+$/gm);
36577
36951
  if (!parts) continue;
36578
- const selector = parts.length === 1 && !selectorLine.includes(":") ? parts[0] : parts.map((p4) => p4 + ":").join("");
36952
+ const selector = parts.length === 1 && !selectorLine.includes(":") ? parts[0] : parts.map((p5) => p5 + ":").join("");
36579
36953
  const isStatic = prefix === "+";
36580
36954
  add(selector, "method", m.index, m[0], { static: isStatic });
36581
36955
  }
@@ -38675,9 +39049,9 @@ var XmlLanguagePlugin = class {
38675
39049
  }
38676
39050
  const schemaLoc = getAttr(attrs, "schemaLocation");
38677
39051
  if (schemaLoc) {
38678
- for (const p4 of schemaLoc.trim().split(/\s+/)) {
38679
- if (p4.endsWith(".xsd") || p4.endsWith(".wsdl") || p4.startsWith("http")) {
38680
- edges.push({ edgeType: "imports", metadata: { module: p4, tag } });
39052
+ for (const p5 of schemaLoc.trim().split(/\s+/)) {
39053
+ if (p5.endsWith(".xsd") || p5.endsWith(".wsdl") || p5.startsWith("http")) {
39054
+ edges.push({ edgeType: "imports", metadata: { module: p5, tag } });
38681
39055
  }
38682
39056
  }
38683
39057
  }
@@ -38741,7 +39115,7 @@ var PrismaPlugin = class {
38741
39115
  path51.join(ctx.rootPath, "prisma", "schema.prisma"),
38742
39116
  path51.join(ctx.rootPath, "schema.prisma")
38743
39117
  ];
38744
- return candidates.some((p4) => fs38.existsSync(p4));
39118
+ return candidates.some((p5) => fs38.existsSync(p5));
38745
39119
  } catch {
38746
39120
  return false;
38747
39121
  }
@@ -38812,7 +39186,7 @@ function parsePrismaSchema(source) {
38812
39186
  const fieldName = fieldMatch[1];
38813
39187
  const fieldType = fieldMatch[2];
38814
39188
  const attrs = fieldMatch[3] ?? "";
38815
- if (["@@", "//"].some((p4) => fieldName.startsWith(p4))) continue;
39189
+ if (["@@", "//"].some((p5) => fieldName.startsWith(p5))) continue;
38816
39190
  const field = {
38817
39191
  name: fieldName,
38818
39192
  type: fieldType.replace("?", "").replace("[]", ""),
@@ -40197,7 +40571,7 @@ import { ok as ok32 } from "neverthrow";
40197
40571
  function symId3(filePath, name, kind) {
40198
40572
  return `${filePath}::${name}#${kind}`;
40199
40573
  }
40200
- function stripJsonComments(source) {
40574
+ function stripJsonComments2(source) {
40201
40575
  let result = "";
40202
40576
  let i = 0;
40203
40577
  let inString = false;
@@ -40316,8 +40690,8 @@ function extractEslint(obj, add, edges) {
40316
40690
  }
40317
40691
  }
40318
40692
  if (Array.isArray(obj.plugins)) {
40319
- for (const p4 of obj.plugins) {
40320
- if (typeof p4 === "string") edges.push({ edgeType: "imports", metadata: { module: p4, dialect: "eslint" } });
40693
+ for (const p5 of obj.plugins) {
40694
+ if (typeof p5 === "string") edges.push({ edgeType: "imports", metadata: { module: p5, dialect: "eslint" } });
40321
40695
  }
40322
40696
  }
40323
40697
  }
@@ -40347,14 +40721,14 @@ function extractLerna(obj, add) {
40347
40721
  }
40348
40722
  function extractBabel(obj, add, edges) {
40349
40723
  if (Array.isArray(obj.presets)) {
40350
- for (const p4 of obj.presets) {
40351
- const name = typeof p4 === "string" ? p4 : Array.isArray(p4) && typeof p4[0] === "string" ? p4[0] : null;
40724
+ for (const p5 of obj.presets) {
40725
+ const name = typeof p5 === "string" ? p5 : Array.isArray(p5) && typeof p5[0] === "string" ? p5[0] : null;
40352
40726
  if (name) edges.push({ edgeType: "imports", metadata: { module: name, dialect: "babel" } });
40353
40727
  }
40354
40728
  }
40355
40729
  if (Array.isArray(obj.plugins)) {
40356
- for (const p4 of obj.plugins) {
40357
- const name = typeof p4 === "string" ? p4 : Array.isArray(p4) && typeof p4[0] === "string" ? p4[0] : null;
40730
+ for (const p5 of obj.plugins) {
40731
+ const name = typeof p5 === "string" ? p5 : Array.isArray(p5) && typeof p5[0] === "string" ? p5[0] : null;
40358
40732
  if (name) edges.push({ edgeType: "imports", metadata: { module: name, dialect: "babel" } });
40359
40733
  }
40360
40734
  }
@@ -40450,7 +40824,7 @@ var JsonLanguagePlugin = class {
40450
40824
  obj = JSON.parse(source);
40451
40825
  } catch {
40452
40826
  try {
40453
- obj = JSON.parse(stripJsonComments(source));
40827
+ obj = JSON.parse(stripJsonComments2(source));
40454
40828
  } catch {
40455
40829
  return ok32({ language: "json", status: "ok", symbols: [] });
40456
40830
  }
@@ -45345,8 +45719,8 @@ var SpringPlugin = class {
45345
45719
  const re = new RegExp(`@${annotation}\\s*(?:\\(\\s*(?:value\\s*=\\s*)?["']([^"']*)["']\\s*\\))?`, "g");
45346
45720
  let m;
45347
45721
  while ((m = re.exec(source)) !== null) {
45348
- const path107 = m[1] ?? "";
45349
- const uri = normalizePath(classPrefix + "/" + path107);
45722
+ const path108 = m[1] ?? "";
45723
+ const uri = normalizePath(classPrefix + "/" + path108);
45350
45724
  result.routes.push({ method, uri, line: source.substring(0, m.index).split("\n").length });
45351
45725
  }
45352
45726
  }
@@ -45406,8 +45780,8 @@ var SpringPlugin = class {
45406
45780
  }
45407
45781
  }
45408
45782
  };
45409
- function normalizePath(path107) {
45410
- return "/" + path107.replace(/\/+/g, "/").replace(/^\/|\/$/g, "");
45783
+ function normalizePath(path108) {
45784
+ return "/" + path108.replace(/\/+/g, "/").replace(/^\/|\/$/g, "");
45411
45785
  }
45412
45786
 
45413
45787
  // src/indexer/plugins/integration/framework/express/index.ts
@@ -46915,8 +47289,8 @@ var NextJSPlugin = class {
46915
47289
  const fileSymbols = ctx.getSymbolsByFile(file.id);
46916
47290
  const fileSym = fileSymbols.find((s) => s.kind === "function" || s.kind === "class");
46917
47291
  if (!fileSym) continue;
46918
- const targetPage = pages.find((p4) => {
46919
- const pageRoute = appRouterPathToRoute(p4.path);
47292
+ const targetPage = pages.find((p5) => {
47293
+ const pageRoute = appRouterPathToRoute(p5.path);
46920
47294
  return pageRoute === info.interceptedRoute;
46921
47295
  });
46922
47296
  if (targetPage) {
@@ -48675,8 +49049,8 @@ var RawSqlPlugin = class {
48675
49049
  ...pkg.dependencies,
48676
49050
  ...pkg.devDependencies
48677
49051
  };
48678
- for (const p4 of RAW_SQL_PACKAGES) {
48679
- if (p4 in deps) return true;
49052
+ for (const p5 of RAW_SQL_PACKAGES) {
49053
+ if (p5 in deps) return true;
48680
49054
  }
48681
49055
  } catch {
48682
49056
  }
@@ -49363,8 +49737,8 @@ function extractExpoNavigationCalls(source) {
49363
49737
  }
49364
49738
  const templateRegex = /router\.(push|replace|navigate)\s*\(\s*`([^`]+)`/g;
49365
49739
  while ((match = templateRegex.exec(source)) !== null) {
49366
- const path107 = match[2].replace(/\$\{[^}]+\}/g, ":param");
49367
- paths.push(path107);
49740
+ const path108 = match[2].replace(/\$\{[^}]+\}/g, ":param");
49741
+ paths.push(path108);
49368
49742
  }
49369
49743
  const linkRegex = /<Link\s+[^>]*href\s*=\s*(?:\{?\s*)?['"]([^'"]+)['"]/g;
49370
49744
  while ((match = linkRegex.exec(source)) !== null) {
@@ -49376,9 +49750,9 @@ function extractExpoNavigationCalls(source) {
49376
49750
  }
49377
49751
  return [...new Set(paths)];
49378
49752
  }
49379
- function matchExpoRoute(path107, routePattern) {
49380
- if (path107 === routePattern) return true;
49381
- const pathParts = path107.split("/").filter(Boolean);
49753
+ function matchExpoRoute(path108, routePattern) {
49754
+ if (path108 === routePattern) return true;
49755
+ const pathParts = path108.split("/").filter(Boolean);
49382
49756
  const routeParts = routePattern.split("/").filter(Boolean);
49383
49757
  if (pathParts.length !== routeParts.length) {
49384
49758
  if (routeParts[routeParts.length - 1] === "*" && pathParts.length >= routeParts.length - 1) {
@@ -50336,7 +50710,7 @@ var ShadcnPlugin = class {
50336
50710
  name: comp.name,
50337
50711
  kind: "component",
50338
50712
  framework: "shadcn-vue",
50339
- props: Object.fromEntries(comp.props.map((p4) => [p4, true])),
50713
+ props: Object.fromEntries(comp.props.map((p5) => [p5, true])),
50340
50714
  emits: comp.emits,
50341
50715
  slots: comp.slots
50342
50716
  });
@@ -51323,7 +51697,7 @@ var NuxtUiPlugin = class {
51323
51697
  name: usage.name,
51324
51698
  kind: "component",
51325
51699
  framework: isPro ? "nuxt-ui-pro" : "nuxt-ui",
51326
- props: Object.fromEntries(usage.propsUsed.map((p4) => [p4, true]))
51700
+ props: Object.fromEntries(usage.propsUsed.map((p5) => [p5, true]))
51327
51701
  });
51328
51702
  result.edges.push({
51329
51703
  edgeType: isPro ? "nuxt_ui_pro_component" : "nuxt_ui_component",
@@ -53466,11 +53840,11 @@ function detectTestFramework(source, _filePath) {
53466
53840
  function extractTestedRoutes(source) {
53467
53841
  const routes = [];
53468
53842
  const seen = /* @__PURE__ */ new Set();
53469
- function add(p4, method) {
53470
- const key = `${method ?? ""}:${p4}`;
53843
+ function add(p5, method) {
53844
+ const key = `${method ?? ""}:${p5}`;
53471
53845
  if (!seen.has(key)) {
53472
53846
  seen.add(key);
53473
- routes.push({ path: p4, method });
53847
+ routes.push({ path: p5, method });
53474
53848
  }
53475
53849
  }
53476
53850
  let m;
@@ -55374,8 +55748,8 @@ var CommanderPlugin = class {
55374
55748
  ...pkg.dependencies,
55375
55749
  ...pkg.devDependencies
55376
55750
  };
55377
- for (const p4 of CLI_PACKAGES) {
55378
- if (p4 in deps) return true;
55751
+ for (const p5 of CLI_PACKAGES) {
55752
+ if (p5 in deps) return true;
55379
55753
  }
55380
55754
  } catch {
55381
55755
  return false;
@@ -55472,8 +55846,8 @@ var TreeSitterPlugin = class {
55472
55846
  ...pkg.dependencies,
55473
55847
  ...pkg.devDependencies
55474
55848
  };
55475
- for (const p4 of TREE_SITTER_PACKAGES) {
55476
- if (p4 in deps) return true;
55849
+ for (const p5 of TREE_SITTER_PACKAGES) {
55850
+ if (p5 in deps) return true;
55477
55851
  }
55478
55852
  } catch {
55479
55853
  return false;
@@ -55603,8 +55977,8 @@ var BuildToolsPlugin = class {
55603
55977
  ...pkg.dependencies,
55604
55978
  ...pkg.devDependencies
55605
55979
  };
55606
- for (const p4 of BUILD_PACKAGES) {
55607
- if (p4 in deps) return true;
55980
+ for (const p5 of BUILD_PACKAGES) {
55981
+ if (p5 in deps) return true;
55608
55982
  }
55609
55983
  } catch {
55610
55984
  return false;
@@ -55750,8 +56124,8 @@ var PinoPlugin = class {
55750
56124
  ...pkg.dependencies,
55751
56125
  ...pkg.devDependencies
55752
56126
  };
55753
- for (const p4 of LOGGING_PACKAGES) {
55754
- if (p4 in deps) return true;
56127
+ for (const p5 of LOGGING_PACKAGES) {
56128
+ if (p5 in deps) return true;
55755
56129
  }
55756
56130
  } catch {
55757
56131
  return false;
@@ -55825,8 +56199,8 @@ var CosmiconfigPlugin = class {
55825
56199
  ...pkg.dependencies,
55826
56200
  ...pkg.devDependencies
55827
56201
  };
55828
- for (const p4 of CONFIG_PACKAGES) {
55829
- if (p4 in deps) return true;
56202
+ for (const p5 of CONFIG_PACKAGES) {
56203
+ if (p5 in deps) return true;
55830
56204
  }
55831
56205
  } catch {
55832
56206
  return false;
@@ -55897,8 +56271,8 @@ var NeverthrowPlugin = class {
55897
56271
  ...pkg.dependencies,
55898
56272
  ...pkg.devDependencies
55899
56273
  };
55900
- for (const p4 of RESULT_PACKAGES) {
55901
- if (p4 in deps) return true;
56274
+ for (const p5 of RESULT_PACKAGES) {
56275
+ if (p5 in deps) return true;
55902
56276
  }
55903
56277
  } catch {
55904
56278
  return false;
@@ -55978,8 +56352,8 @@ var ClackPlugin = class {
55978
56352
  ...pkg.dependencies,
55979
56353
  ...pkg.devDependencies
55980
56354
  };
55981
- for (const p4 of PROMPT_PACKAGES) {
55982
- if (p4 in deps) return true;
56355
+ for (const p5 of PROMPT_PACKAGES) {
56356
+ if (p5 in deps) return true;
55983
56357
  }
55984
56358
  } catch {
55985
56359
  return false;
@@ -56109,9 +56483,9 @@ var FileWatcher2 = class {
56109
56483
  logger.error({ error: err32 }, "Watcher error");
56110
56484
  return;
56111
56485
  }
56112
- const notIgnored = (p4) => {
56113
- if (ignoreDirs.some((d) => p4.startsWith(d))) return false;
56114
- const rel = path92.relative(rootPath, p4);
56486
+ const notIgnored = (p5) => {
56487
+ if (ignoreDirs.some((d) => p5.startsWith(d))) return false;
56488
+ const rel = path92.relative(rootPath, p5);
56115
56489
  return !traceignore.isIgnored(rel);
56116
56490
  };
56117
56491
  const changed = events.filter((e) => e.type === "create" || e.type === "update").map((e) => e.path).filter(notIgnored);
@@ -56121,7 +56495,7 @@ var FileWatcher2 = class {
56121
56495
  await onDeletes(deleted);
56122
56496
  }
56123
56497
  if (changed.length === 0) return;
56124
- for (const p4 of changed) this.pendingPaths.add(p4);
56498
+ for (const p5 of changed) this.pendingPaths.add(p5);
56125
56499
  if (this.debounceTimer) this._clearTimeout(this.debounceTimer);
56126
56500
  this.debounceTimer = this._setTimeout(async () => {
56127
56501
  const paths = Array.from(this.pendingPaths);
@@ -56155,6 +56529,122 @@ var FileWatcher2 = class {
56155
56529
  }
56156
56530
  };
56157
56531
 
56532
+ // src/progress.ts
56533
+ function idleProgress() {
56534
+ return { phase: "idle", processed: 0, total: 0, startedAt: 0, completedAt: 0 };
56535
+ }
56536
+ function snapOne(p5) {
56537
+ const now = Date.now();
56538
+ const endTime = p5.completedAt > 0 ? p5.completedAt : now;
56539
+ return {
56540
+ ...p5,
56541
+ percentage: p5.total > 0 ? Math.round(p5.processed / p5.total * 100) : null,
56542
+ elapsedMs: p5.startedAt > 0 ? endTime - p5.startedAt : 0
56543
+ };
56544
+ }
56545
+ var ProgressState = class {
56546
+ indexing = idleProgress();
56547
+ summarization = idleProgress();
56548
+ embedding = idleProgress();
56549
+ db;
56550
+ constructor(db) {
56551
+ this.db = db ?? null;
56552
+ if (this.db) {
56553
+ this.loadFromDb();
56554
+ }
56555
+ }
56556
+ update(name, partial) {
56557
+ const current = this[name];
56558
+ Object.assign(current, partial);
56559
+ this.persist(name);
56560
+ if (partial.phase === "running" && partial.total !== void 0) {
56561
+ logger.info({ pipeline: name, total: current.total }, "%s started: %d items to process", name, current.total);
56562
+ } else if (partial.phase === "completed") {
56563
+ const elapsed = current.completedAt > 0 && current.startedAt > 0 ? Math.round((current.completedAt - current.startedAt) / 1e3) : 0;
56564
+ logger.info({ pipeline: name, processed: current.processed, elapsed }, "%s completed: %d items in %ds", name, current.processed, elapsed);
56565
+ } else if (partial.phase === "error") {
56566
+ logger.error({ pipeline: name, error: current.error }, "%s failed: %s", name, current.error);
56567
+ } else if (partial.processed !== void 0 && current.total > 0) {
56568
+ const pct = Math.round(current.processed / current.total * 100);
56569
+ logger.info({ pipeline: name, processed: current.processed, total: current.total, pct }, "%s progress: %d/%d (%d%%)", name, current.processed, current.total, pct);
56570
+ }
56571
+ }
56572
+ snapshot() {
56573
+ return {
56574
+ indexing: snapOne(this.indexing),
56575
+ summarization: snapOne(this.summarization),
56576
+ embedding: snapOne(this.embedding)
56577
+ };
56578
+ }
56579
+ persist(name) {
56580
+ if (!this.db) return;
56581
+ const p5 = this[name];
56582
+ try {
56583
+ this.db.prepare(`
56584
+ INSERT OR REPLACE INTO indexing_progress
56585
+ (pipeline, phase, processed, total, started_at, completed_at, error, updated_at)
56586
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
56587
+ `).run(
56588
+ name,
56589
+ p5.phase,
56590
+ p5.processed,
56591
+ p5.total,
56592
+ p5.startedAt,
56593
+ p5.completedAt,
56594
+ p5.error ?? null,
56595
+ Date.now()
56596
+ );
56597
+ } catch (e) {
56598
+ logger.debug({ error: e }, "Failed to persist progress");
56599
+ }
56600
+ }
56601
+ loadFromDb() {
56602
+ if (!this.db) return;
56603
+ try {
56604
+ const rows = this.db.prepare("SELECT * FROM indexing_progress").all();
56605
+ for (const row of rows) {
56606
+ if (row.pipeline in this) {
56607
+ this[row.pipeline] = {
56608
+ phase: row.phase,
56609
+ processed: row.processed,
56610
+ total: row.total,
56611
+ startedAt: row.started_at,
56612
+ completedAt: row.completed_at,
56613
+ error: row.error ?? void 0
56614
+ };
56615
+ }
56616
+ }
56617
+ } catch {
56618
+ }
56619
+ }
56620
+ };
56621
+ function readProgressFromDb(db) {
56622
+ try {
56623
+ const rows = db.prepare("SELECT * FROM indexing_progress").all();
56624
+ if (rows.length === 0) return null;
56625
+ const map = new Map(rows.map((r) => [r.pipeline, r]));
56626
+ const toProgress = (name) => {
56627
+ const r = map.get(name);
56628
+ if (!r) return idleProgress();
56629
+ return {
56630
+ phase: r.phase,
56631
+ processed: r.processed,
56632
+ total: r.total,
56633
+ startedAt: r.started_at,
56634
+ completedAt: r.completed_at,
56635
+ error: r.error ?? void 0
56636
+ };
56637
+ };
56638
+ return {
56639
+ indexing: snapOne(toProgress("indexing")),
56640
+ summarization: snapOne(toProgress("summarization")),
56641
+ embedding: snapOne(toProgress("embedding"))
56642
+ };
56643
+ } catch {
56644
+ return null;
56645
+ }
56646
+ }
56647
+
56158
56648
  // src/cli.ts
56159
56649
  import http from "http";
56160
56650
 
@@ -56430,7 +56920,7 @@ import path95 from "path";
56430
56920
  import os6 from "os";
56431
56921
 
56432
56922
  // src/init/types.ts
56433
- var GUARD_HOOK_VERSION = "0.4.0";
56923
+ var GUARD_HOOK_VERSION = "0.5.0";
56434
56924
  var REINDEX_HOOK_VERSION = "0.1.0";
56435
56925
  var PRECOMPACT_HOOK_VERSION = "0.1.0";
56436
56926
  var WORKTREE_HOOK_VERSION = "0.1.0";
@@ -56452,7 +56942,7 @@ var CLIENTS = [
56452
56942
  var GUARD_HOOK = {
56453
56943
  scriptName: "trace-mcp-guard",
56454
56944
  settingsKey: "PreToolUse",
56455
- matcher: "Read|Grep|Glob|Bash",
56945
+ matcher: "Read|Grep|Glob|Bash|Agent",
56456
56946
  version: GUARD_HOOK_VERSION,
56457
56947
  dryRunLabel: "Would install guard hook"
56458
56948
  };
@@ -56542,7 +57032,10 @@ function removeHookEntry(settings, desc) {
56542
57032
  const entries = hooks[desc.settingsKey];
56543
57033
  if (!Array.isArray(entries)) return;
56544
57034
  hooks[desc.settingsKey] = entries.filter(
56545
- (h) => !h.hooks?.some((hh) => hh.command?.includes(desc.scriptName))
57035
+ (h) => {
57036
+ const entry = h;
57037
+ return !entry.hooks?.some((hh) => hh.command?.includes(desc.scriptName));
57038
+ }
56546
57039
  );
56547
57040
  if (hooks[desc.settingsKey].length === 0) delete hooks[desc.settingsKey];
56548
57041
  if (Object.keys(hooks).length === 0) delete settings.hooks;
@@ -56720,12 +57213,12 @@ function detectProject(dir) {
56720
57213
  const ctx = buildProjectContext(projectRoot);
56721
57214
  const packageManagers = detectPackageManagers(projectRoot);
56722
57215
  const registry = new PluginRegistry();
56723
- for (const p4 of createAllLanguagePlugins()) registry.registerLanguagePlugin(p4);
56724
- for (const p4 of createAllIntegrationPlugins()) registry.registerFrameworkPlugin(p4);
57216
+ for (const p5 of createAllLanguagePlugins()) registry.registerLanguagePlugin(p5);
57217
+ for (const p5 of createAllIntegrationPlugins()) registry.registerFrameworkPlugin(p5);
56725
57218
  const activeResult = registry.getActiveFrameworkPlugins(ctx);
56726
- const frameworks = activeResult.isOk() ? activeResult.value.map((p4) => {
56727
- const dep = ctx.allDependencies.find((d) => d.name === p4.manifest.name);
56728
- return { name: p4.manifest.name, version: dep?.version, category: p4.manifest.category };
57219
+ const frameworks = activeResult.isOk() ? activeResult.value.map((p5) => {
57220
+ const dep = ctx.allDependencies.find((d) => d.name === p5.manifest.name);
57221
+ return { name: p5.manifest.name, version: dep?.version, category: p5.manifest.category };
56729
57222
  }) : [];
56730
57223
  const languageMap = {
56731
57224
  node: "TypeScript",
@@ -56843,8 +57336,8 @@ function detectExistingConfig(root) {
56843
57336
  path97.join(root, ".trace-mcp.json"),
56844
57337
  path97.join(root, ".config", "trace-mcp.json")
56845
57338
  ];
56846
- for (const p4 of candidates) {
56847
- if (fs84.existsSync(p4)) return { path: p4 };
57339
+ for (const p5 of candidates) {
57340
+ if (fs84.existsSync(p5)) return { path: p5 };
56848
57341
  }
56849
57342
  const pkgPath = path97.join(root, "package.json");
56850
57343
  if (fs84.existsSync(pkgPath)) {
@@ -56858,7 +57351,7 @@ function detectExistingConfig(root) {
56858
57351
  }
56859
57352
  function detectExistingDb(root, globalDbPath) {
56860
57353
  const candidates = globalDbPath ? [globalDbPath, path97.join(root, ".trace-mcp", "index.db")] : [path97.join(root, ".trace-mcp", "index.db")];
56861
- const dbPath = candidates.find((p4) => fs84.existsSync(p4));
57354
+ const dbPath = candidates.find((p5) => fs84.existsSync(p5));
56862
57355
  if (!dbPath) return null;
56863
57356
  try {
56864
57357
  const db = new Database6(dbPath, { readonly: true });
@@ -57469,9 +57962,9 @@ function scanGlobalArtifacts() {
57469
57962
  }
57470
57963
  return conflicts;
57471
57964
  }
57472
- function shortPath(p4) {
57473
- if (p4.startsWith(HOME4)) return "~" + p4.slice(HOME4.length);
57474
- return p4;
57965
+ function shortPath(p5) {
57966
+ if (p5.startsWith(HOME4)) return "~" + p5.slice(HOME4.length);
57967
+ return p5;
57475
57968
  }
57476
57969
  function truncate(s, maxLen) {
57477
57970
  return s.length > maxLen ? s.slice(0, maxLen - 1) + "\u2026" : s;
@@ -57754,10 +58247,10 @@ function fixGlobalArtifact(conflict, opts) {
57754
58247
  return { conflictId: conflict.id, action: "skipped", detail: `Failed to remove: ${err32.message}`, target: dirPath };
57755
58248
  }
57756
58249
  }
57757
- function shortPath2(p4) {
58250
+ function shortPath2(p5) {
57758
58251
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
57759
- if (home && p4.startsWith(home)) return "~" + p4.slice(home.length);
57760
- return p4;
58252
+ if (home && p5.startsWith(home)) return "~" + p5.slice(home.length);
58253
+ return p5;
57761
58254
  }
57762
58255
 
57763
58256
  // src/project-root.ts
@@ -58024,16 +58517,16 @@ function generateConfig(detection) {
58024
58517
  for (const fw of detection.frameworks) {
58025
58518
  const preset = FRAMEWORK_PRESETS[fw.name];
58026
58519
  if (preset) {
58027
- for (const p4 of preset.include) include.add(p4);
58028
- for (const p4 of preset.exclude) exclude.add(p4);
58520
+ for (const p5 of preset.include) include.add(p5);
58521
+ for (const p5 of preset.exclude) exclude.add(p5);
58029
58522
  }
58030
58523
  }
58031
58524
  if (include.size === 0) {
58032
58525
  for (const lang of detection.languages) {
58033
58526
  const preset = LANGUAGE_PRESETS[lang.toLowerCase()];
58034
58527
  if (preset) {
58035
- for (const p4 of preset.include) include.add(p4);
58036
- for (const p4 of preset.exclude) exclude.add(p4);
58528
+ for (const p5 of preset.include) include.add(p5);
58529
+ for (const p5 of preset.exclude) exclude.add(p5);
58037
58530
  }
58038
58531
  }
58039
58532
  }
@@ -58283,7 +58776,16 @@ var initCommand = new Command("init").description("One-time global setup: config
58283
58776
  }
58284
58777
  }
58285
58778
  if (indexProject) {
58286
- const indexStep = registerAndIndexProject(process.cwd(), { dryRun: opts.dryRun, force: opts.force });
58779
+ const spin = !nonInteractive ? p.spinner() : null;
58780
+ spin?.start("Registering and indexing current project...");
58781
+ const indexStep = await registerAndIndexProject(process.cwd(), { dryRun: opts.dryRun, force: opts.force });
58782
+ if (spin) {
58783
+ if (indexStep.detail?.includes("Indexed")) {
58784
+ spin.stop(indexStep.detail);
58785
+ } else {
58786
+ spin.stop(indexStep.detail ?? indexStep.action);
58787
+ }
58788
+ }
58287
58789
  steps.push(indexStep);
58288
58790
  }
58289
58791
  const existingProjects = listProjects();
@@ -58365,7 +58867,7 @@ var initCommand = new Command("init").description("One-time global setup: config
58365
58867
  p.note(lines.join("\n"), "Already configured");
58366
58868
  }
58367
58869
  p.outro(
58368
- indexProject ? "Ready! Project registered and will be indexed when trace-mcp serve starts." : "Ready! Run `trace-mcp add` in a project directory to register it for indexing."
58870
+ indexProject ? "Ready! Project registered and indexed." : "Ready! Run `trace-mcp add` in a project directory to register it for indexing."
58369
58871
  );
58370
58872
  }
58371
58873
  });
@@ -58397,7 +58899,31 @@ function executeSteps(steps, opts) {
58397
58899
  steps.push(mdResult);
58398
58900
  }
58399
58901
  }
58400
- function registerAndIndexProject(dir, opts) {
58902
+ function formatDuration(ms) {
58903
+ if (ms < 1e3) return `${ms}ms`;
58904
+ return `${(ms / 1e3).toFixed(1)}s`;
58905
+ }
58906
+ async function runIndexingForProject(projectRoot) {
58907
+ const configResult = await loadConfig(projectRoot);
58908
+ if (configResult.isErr()) return null;
58909
+ const dbPath = getDbPath(projectRoot);
58910
+ const db = initializeDatabase(dbPath);
58911
+ const store = new Store(db);
58912
+ const registry = new PluginRegistry();
58913
+ for (const lp of createAllLanguagePlugins()) registry.registerLanguagePlugin(lp);
58914
+ for (const fp of createAllIntegrationPlugins()) registry.registerFrameworkPlugin(fp);
58915
+ const pipeline = new IndexingPipeline(store, registry, configResult.value, projectRoot);
58916
+ try {
58917
+ const result = await pipeline.indexAll(true);
58918
+ updateLastIndexed(projectRoot);
58919
+ return { indexed: result.indexed, skipped: result.skipped, errors: result.errors, durationMs: result.durationMs };
58920
+ } catch {
58921
+ return null;
58922
+ } finally {
58923
+ db.close();
58924
+ }
58925
+ }
58926
+ async function registerAndIndexProject(dir, opts) {
58401
58927
  let projectRoot = null;
58402
58928
  try {
58403
58929
  projectRoot = findProjectRoot(dir);
@@ -58408,7 +58934,7 @@ function registerAndIndexProject(dir, opts) {
58408
58934
  if (childRoots.length === 0) {
58409
58935
  return { target: dir, action: "skipped", detail: "Could not detect project root or child projects" };
58410
58936
  }
58411
- return registerMultiRootProject(dir, childRoots, opts);
58937
+ return await registerMultiRootProject(dir, childRoots, opts);
58412
58938
  }
58413
58939
  if (opts.dryRun) {
58414
58940
  return { target: projectRoot, action: "skipped", detail: "Would register and index project" };
@@ -58428,13 +58954,15 @@ function registerAndIndexProject(dir, opts) {
58428
58954
  const db = initializeDatabase(dbPath);
58429
58955
  db.close();
58430
58956
  const entry = registerProject(projectRoot);
58957
+ const indexResult = await runIndexingForProject(projectRoot);
58958
+ const detail = indexResult ? `Registered and indexed: ${entry.name} \u2014 ${indexResult.indexed} files in ${formatDuration(indexResult.durationMs)} (${indexResult.skipped} skipped, ${indexResult.errors} errors)` : `Registered project: ${entry.name} (indexing failed)`;
58431
58959
  return {
58432
58960
  target: projectRoot,
58433
58961
  action: existing ? "updated" : "created",
58434
- detail: `Registered project: ${entry.name}`
58962
+ detail
58435
58963
  };
58436
58964
  }
58437
- function registerMultiRootProject(parentDir, childRoots, opts) {
58965
+ async function registerMultiRootProject(parentDir, childRoots, opts) {
58438
58966
  if (opts.dryRun) {
58439
58967
  return {
58440
58968
  target: parentDir,
@@ -58474,10 +59002,12 @@ function registerMultiRootProject(parentDir, childRoots, opts) {
58474
59002
  const db = initializeDatabase(dbPath);
58475
59003
  db.close();
58476
59004
  const entry = registerProject(parentDir, { type: "multi-root", children: childRoots });
59005
+ const indexResult = await runIndexingForProject(parentDir);
59006
+ const detail = indexResult ? `Registered and indexed multi-root: ${entry.name} (${childRoots.length} children) \u2014 ${indexResult.indexed} files in ${formatDuration(indexResult.durationMs)}` : `Registered multi-root (${childRoots.length} children): ${childRoots.map((r) => path102.basename(r)).join(", ")} (indexing failed)`;
58477
59007
  return {
58478
59008
  target: parentDir,
58479
59009
  action: existing ? "updated" : "created",
58480
- detail: `Registered multi-root (${childRoots.length} children): ${childRoots.map((r) => path102.basename(r)).join(", ")}`
59010
+ detail
58481
59011
  };
58482
59012
  }
58483
59013
  function formatClientName(name) {
@@ -58491,12 +59021,12 @@ function formatClientName(name) {
58491
59021
  };
58492
59022
  return names[name] ?? name;
58493
59023
  }
58494
- function shortPath3(p4) {
59024
+ function shortPath3(p5) {
58495
59025
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
58496
- if (home && p4.startsWith(home)) return "~" + p4.slice(home.length);
59026
+ if (home && p5.startsWith(home)) return "~" + p5.slice(home.length);
58497
59027
  const cwd = process.cwd();
58498
- if (p4.startsWith(cwd)) return p4.slice(cwd.length + 1) || ".";
58499
- return p4;
59028
+ if (p5.startsWith(cwd)) return p5.slice(cwd.length + 1) || ".";
59029
+ return p5;
58500
59030
  }
58501
59031
 
58502
59032
  // src/cli/upgrade.ts
@@ -58513,11 +59043,11 @@ var upgradeCommand = new Command2("upgrade").description("Upgrade trace-mcp: run
58513
59043
  console.error("No registered projects. Run `trace-mcp add` first, or specify a directory.");
58514
59044
  process.exit(1);
58515
59045
  }
58516
- for (const p4 of projects) {
58517
- if (fs90.existsSync(p4.root)) {
58518
- projectRoots.push(p4.root);
59046
+ for (const p5 of projects) {
59047
+ if (fs90.existsSync(p5.root)) {
59048
+ projectRoots.push(p5.root);
58519
59049
  } else {
58520
- logger.warn({ root: p4.root }, "Skipping stale project (directory not found)");
59050
+ logger.warn({ root: p5.root }, "Skipping stale project (directory not found)");
58521
59051
  }
58522
59052
  }
58523
59053
  }
@@ -58547,8 +59077,8 @@ var upgradeCommand = new Command2("upgrade").description("Upgrade trace-mcp: run
58547
59077
  });
58548
59078
  if (!opts.skipReindex) {
58549
59079
  const registry = new PluginRegistry();
58550
- for (const p4 of createAllLanguagePlugins()) registry.registerLanguagePlugin(p4);
58551
- for (const p4 of createAllIntegrationPlugins()) registry.registerFrameworkPlugin(p4);
59080
+ for (const p5 of createAllLanguagePlugins()) registry.registerLanguagePlugin(p5);
59081
+ for (const p5 of createAllIntegrationPlugins()) registry.registerFrameworkPlugin(p5);
58552
59082
  const pipeline = new IndexingPipeline(store, registry, config, projectRoot);
58553
59083
  const result = await pipeline.indexAll(true);
58554
59084
  steps.push({
@@ -58611,7 +59141,39 @@ import { Command as Command3 } from "commander";
58611
59141
  import fs91 from "fs";
58612
59142
  import path104 from "path";
58613
59143
  import * as p2 from "@clack/prompts";
58614
- var addCommand = new Command3("add").description("Register a project for indexing: detect root, create DB, add to registry").argument("[dir]", "Project directory (default: current directory)", ".").option("--force", "Re-register even if already registered").option("--json", "Output results as JSON").action(async (dir, opts) => {
59144
+ async function runIndexing(projectRoot, opts) {
59145
+ const configResult = await loadConfig(projectRoot);
59146
+ if (configResult.isErr()) {
59147
+ if (!opts.json) {
59148
+ p2.log.warn(`Could not load config for indexing: ${configResult.error}`);
59149
+ }
59150
+ return null;
59151
+ }
59152
+ const dbPath = getDbPath(projectRoot);
59153
+ const db = initializeDatabase(dbPath);
59154
+ const store = new Store(db);
59155
+ const registry = new PluginRegistry();
59156
+ for (const lp of createAllLanguagePlugins()) registry.registerLanguagePlugin(lp);
59157
+ for (const fp of createAllIntegrationPlugins()) registry.registerFrameworkPlugin(fp);
59158
+ const pipeline = new IndexingPipeline(store, registry, configResult.value, projectRoot);
59159
+ try {
59160
+ const result = await pipeline.indexAll(true);
59161
+ updateLastIndexed(projectRoot);
59162
+ return { indexed: result.indexed, skipped: result.skipped, errors: result.errors, durationMs: result.durationMs };
59163
+ } catch (err32) {
59164
+ if (!opts.json) {
59165
+ p2.log.warn(`Indexing failed: ${err32.message}`);
59166
+ }
59167
+ return null;
59168
+ } finally {
59169
+ db.close();
59170
+ }
59171
+ }
59172
+ function formatDuration2(ms) {
59173
+ if (ms < 1e3) return `${ms}ms`;
59174
+ return `${(ms / 1e3).toFixed(1)}s`;
59175
+ }
59176
+ var addCommand = new Command3("add").description("Register a project for indexing: detect root, create DB, add to registry").argument("[dir]", "Project directory (default: current directory)", ".").option("--force", "Re-register even if already registered").option("--no-index", "Skip indexing after registration").option("--json", "Output results as JSON").action(async (dir, opts) => {
58615
59177
  const resolvedDir = path104.resolve(dir);
58616
59178
  if (!fs91.existsSync(resolvedDir)) {
58617
59179
  console.error(`Directory does not exist: ${resolvedDir}`);
@@ -58697,6 +59259,21 @@ DB: ${shortPath4(existing.dbPath)}`, "Existing");
58697
59259
  const db = initializeDatabase(dbPath);
58698
59260
  db.close();
58699
59261
  const entry = registerProject(projectRoot);
59262
+ let indexResult = null;
59263
+ if (opts.index !== false) {
59264
+ if (isInteractive) {
59265
+ const spin = p2.spinner();
59266
+ spin.start("Indexing project...");
59267
+ indexResult = await runIndexing(projectRoot, opts);
59268
+ if (indexResult) {
59269
+ spin.stop(`Indexed ${indexResult.indexed} files in ${formatDuration2(indexResult.durationMs)}`);
59270
+ } else {
59271
+ spin.stop("Indexing skipped");
59272
+ }
59273
+ } else {
59274
+ indexResult = await runIndexing(projectRoot, opts);
59275
+ }
59276
+ }
58700
59277
  if (opts.json) {
58701
59278
  console.log(JSON.stringify({
58702
59279
  status: existing ? "re-registered" : "registered",
@@ -58705,7 +59282,8 @@ DB: ${shortPath4(existing.dbPath)}`, "Existing");
58705
59282
  detection: {
58706
59283
  languages: detection.languages,
58707
59284
  frameworks: detection.frameworks.map((f) => f.name)
58708
- }
59285
+ },
59286
+ indexing: indexResult ?? void 0
58709
59287
  }, null, 2));
58710
59288
  } else {
58711
59289
  const lines = [];
@@ -58715,8 +59293,16 @@ DB: ${shortPath4(existing.dbPath)}`, "Existing");
58715
59293
  if (migrated) {
58716
59294
  lines.push(`Migrated existing index from .trace-mcp/index.db`);
58717
59295
  }
59296
+ if (indexResult) {
59297
+ lines.push(`Indexed: ${indexResult.indexed} files (${indexResult.skipped} skipped, ${indexResult.errors} errors)`);
59298
+ lines.push(`Duration: ${formatDuration2(indexResult.durationMs)}`);
59299
+ }
58718
59300
  p2.note(lines.join("\n"), existing ? "Re-registered" : "Registered");
58719
- p2.outro("Project registered. It will be indexed when trace-mcp serve starts.");
59301
+ if (indexResult) {
59302
+ p2.outro("Project registered and indexed.");
59303
+ } else {
59304
+ p2.outro("Project registered. Run `trace-mcp index` to index it.");
59305
+ }
58720
59306
  }
58721
59307
  });
58722
59308
  async function handleMultiRoot(parentDir, childRoots, opts) {
@@ -58799,13 +59385,29 @@ Discovered ${childRoots.length} child project(s):
58799
59385
  type: "multi-root",
58800
59386
  children: childRoots
58801
59387
  });
59388
+ let indexResult = null;
59389
+ if (opts.index !== false) {
59390
+ if (isInteractive) {
59391
+ const spin = p2.spinner();
59392
+ spin.start("Indexing multi-root project...");
59393
+ indexResult = await runIndexing(parentDir, opts);
59394
+ if (indexResult) {
59395
+ spin.stop(`Indexed ${indexResult.indexed} files in ${formatDuration2(indexResult.durationMs)}`);
59396
+ } else {
59397
+ spin.stop("Indexing skipped");
59398
+ }
59399
+ } else {
59400
+ indexResult = await runIndexing(parentDir, opts);
59401
+ }
59402
+ }
58802
59403
  if (opts.json) {
58803
59404
  console.log(JSON.stringify({
58804
59405
  status: existing ? "re-registered" : "registered",
58805
59406
  type: "multi-root",
58806
59407
  project: entry,
58807
59408
  children: childRoots.map((r) => path104.basename(r)),
58808
- cleaned
59409
+ cleaned,
59410
+ indexing: indexResult ?? void 0
58809
59411
  }, null, 2));
58810
59412
  } else {
58811
59413
  const lines = [];
@@ -58813,14 +59415,22 @@ Discovered ${childRoots.length} child project(s):
58813
59415
  lines.push(`Root: ${parentDir}`);
58814
59416
  lines.push(`DB: ${shortPath4(dbPath)}`);
58815
59417
  lines.push(`Children: ${childRoots.map((r) => path104.basename(r)).join(", ")}`);
59418
+ if (indexResult) {
59419
+ lines.push(`Indexed: ${indexResult.indexed} files (${indexResult.skipped} skipped, ${indexResult.errors} errors)`);
59420
+ lines.push(`Duration: ${formatDuration2(indexResult.durationMs)}`);
59421
+ }
58816
59422
  p2.note(lines.join("\n"), existing ? "Re-registered" : "Registered");
58817
- p2.outro("Multi-root project registered. It will be indexed when trace-mcp serve starts.");
59423
+ if (indexResult) {
59424
+ p2.outro("Multi-root project registered and indexed.");
59425
+ } else {
59426
+ p2.outro("Multi-root project registered. Run `trace-mcp index` to index it.");
59427
+ }
58818
59428
  }
58819
59429
  }
58820
- function shortPath4(p4) {
59430
+ function shortPath4(p5) {
58821
59431
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
58822
- if (home && p4.startsWith(home)) return "~" + p4.slice(home.length);
58823
- return p4;
59432
+ if (home && p5.startsWith(home)) return "~" + p5.slice(home.length);
59433
+ return p5;
58824
59434
  }
58825
59435
 
58826
59436
  // src/cli/doctor.ts
@@ -58882,11 +59492,11 @@ var doctorCommand = new Command4("doctor").description("Check for competing tool
58882
59492
  return;
58883
59493
  }
58884
59494
  if (!opts.dryRun) {
58885
- const confirm3 = await p3.confirm({
59495
+ const confirm4 = await p3.confirm({
58886
59496
  message: `Fix ${fixable2.length} conflict${fixable2.length > 1 ? "s" : ""} automatically?`,
58887
59497
  initialValue: true
58888
59498
  });
58889
- if (p3.isCancel(confirm3) || !confirm3) {
59499
+ if (p3.isCancel(confirm4) || !confirm4) {
58890
59500
  p3.cancel("No changes made.");
58891
59501
  return;
58892
59502
  }
@@ -58952,12 +59562,12 @@ function printFixResults(results, dryRun) {
58952
59562
  p3.outro("Dry run complete \u2014 no changes made. Run with --fix to apply.");
58953
59563
  }
58954
59564
  }
58955
- function shortPath5(p4) {
59565
+ function shortPath5(p5) {
58956
59566
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
58957
- if (home && p4.startsWith(home)) return "~" + p4.slice(home.length);
59567
+ if (home && p5.startsWith(home)) return "~" + p5.slice(home.length);
58958
59568
  const cwd = process.cwd();
58959
- if (p4.startsWith(cwd)) return p4.slice(cwd.length + 1) || ".";
58960
- return p4;
59569
+ if (p5.startsWith(cwd)) return p5.slice(cwd.length + 1) || ".";
59570
+ return p5;
58961
59571
  }
58962
59572
 
58963
59573
  // src/cli/ci.ts
@@ -59321,8 +59931,8 @@ ${msg}
59321
59931
  const store = new Store(db);
59322
59932
  if (opts.index) {
59323
59933
  const registry = new PluginRegistry();
59324
- for (const p4 of createAllLanguagePlugins()) registry.registerLanguagePlugin(p4);
59325
- for (const p4 of createAllIntegrationPlugins()) registry.registerFrameworkPlugin(p4);
59934
+ for (const p5 of createAllLanguagePlugins()) registry.registerLanguagePlugin(p5);
59935
+ for (const p5 of createAllIntegrationPlugins()) registry.registerFrameworkPlugin(p5);
59326
59936
  logger.info("CI report: indexing project...");
59327
59937
  const pipeline = new IndexingPipeline(store, registry, config, projectRoot);
59328
59938
  await pipeline.indexAll(false);
@@ -59445,8 +60055,8 @@ var checkCommand = new Command6("check").description("Run quality gate checks ag
59445
60055
  const store = new Store(db);
59446
60056
  if (opts.index) {
59447
60057
  const registry = new PluginRegistry();
59448
- for (const p4 of createAllLanguagePlugins()) registry.registerLanguagePlugin(p4);
59449
- for (const p4 of createAllIntegrationPlugins()) registry.registerFrameworkPlugin(p4);
60058
+ for (const p5 of createAllLanguagePlugins()) registry.registerLanguagePlugin(p5);
60059
+ for (const p5 of createAllIntegrationPlugins()) registry.registerFrameworkPlugin(p5);
59450
60060
  logger.info("Indexing project before quality check...");
59451
60061
  const pipeline = new IndexingPipeline(store, registry, config, projectRoot);
59452
60062
  await pipeline.indexAll(false);
@@ -59928,13 +60538,269 @@ analyticsCommand.command("trends").description("Show daily usage trends: tokens,
59928
60538
  }
59929
60539
  });
59930
60540
 
60541
+ // src/cli/remove.ts
60542
+ import { Command as Command10 } from "commander";
60543
+ import fs94 from "fs";
60544
+ import path106 from "path";
60545
+ import * as p4 from "@clack/prompts";
60546
+ var removeCommand = new Command10("remove").description("Unregister a project and delete its index").argument("[dir]", "Project directory (default: current directory)", ".").option("--force", "Remove without confirmation").option("--keep-db", "Keep the database file (only unregister)").option("--json", "Output results as JSON").action(async (dir, opts) => {
60547
+ const resolvedDir = path106.resolve(dir);
60548
+ const isInteractive = !opts.json;
60549
+ let projectRoot;
60550
+ try {
60551
+ projectRoot = findProjectRoot(resolvedDir);
60552
+ } catch {
60553
+ projectRoot = resolvedDir;
60554
+ }
60555
+ const parentEntry = findParentProject(projectRoot);
60556
+ if (parentEntry) {
60557
+ await handleRemoveFromMultiRoot(projectRoot, parentEntry, opts);
60558
+ return;
60559
+ }
60560
+ const entry = getProject(projectRoot);
60561
+ if (!entry) {
60562
+ if (opts.json) {
60563
+ console.log(JSON.stringify({ status: "not_registered", dir: projectRoot }));
60564
+ } else {
60565
+ if (isInteractive) p4.intro("trace-mcp remove");
60566
+ p4.log.warn(`Project not registered: ${projectRoot}`);
60567
+ p4.log.info("Use `trace-mcp list` to see registered projects.");
60568
+ }
60569
+ return;
60570
+ }
60571
+ if (isInteractive) {
60572
+ p4.intro("trace-mcp remove");
60573
+ const lines = [];
60574
+ lines.push(`Project: ${entry.name}`);
60575
+ lines.push(`Root: ${entry.root}`);
60576
+ lines.push(`DB: ${shortPath6(entry.dbPath)}`);
60577
+ if (entry.type === "multi-root" && entry.children) {
60578
+ lines.push(`Children: ${entry.children.map((c) => path106.basename(c)).join(", ")}`);
60579
+ }
60580
+ p4.note(lines.join("\n"), "Project to remove");
60581
+ }
60582
+ if (!opts.force && isInteractive) {
60583
+ const confirm4 = await p4.confirm({
60584
+ message: entry.type === "multi-root" ? `Remove multi-root project "${entry.name}" and its unified index?` : `Remove project "${entry.name}" and delete its index?`,
60585
+ initialValue: false
60586
+ });
60587
+ if (p4.isCancel(confirm4) || !confirm4) {
60588
+ p4.cancel("Cancelled.");
60589
+ return;
60590
+ }
60591
+ }
60592
+ let dbDeleted = false;
60593
+ if (!opts.keepDb && fs94.existsSync(entry.dbPath)) {
60594
+ fs94.unlinkSync(entry.dbPath);
60595
+ dbDeleted = true;
60596
+ }
60597
+ removeProjectConfig(entry.root);
60598
+ unregisterProject(entry.root);
60599
+ if (opts.json) {
60600
+ console.log(JSON.stringify({
60601
+ status: "removed",
60602
+ project: entry.name,
60603
+ root: entry.root,
60604
+ dbDeleted
60605
+ }, null, 2));
60606
+ } else {
60607
+ const lines = [];
60608
+ lines.push(`Project: ${entry.name}`);
60609
+ if (dbDeleted) {
60610
+ lines.push(`Database deleted: ${shortPath6(entry.dbPath)}`);
60611
+ } else if (opts.keepDb) {
60612
+ lines.push(`Database kept: ${shortPath6(entry.dbPath)}`);
60613
+ }
60614
+ lines.push("Config removed");
60615
+ p4.note(lines.join("\n"), "Removed");
60616
+ p4.outro("Project unregistered.");
60617
+ }
60618
+ });
60619
+ async function handleRemoveFromMultiRoot(childRoot, parent, opts) {
60620
+ const isInteractive = !opts.json;
60621
+ if (isInteractive) {
60622
+ p4.intro("trace-mcp remove (from multi-root)");
60623
+ p4.note(
60624
+ `This project is part of multi-root index: ${parent.name}
60625
+ Parent root: ${parent.root}
60626
+ Child to exclude: ${path106.basename(childRoot)}`,
60627
+ "Multi-root"
60628
+ );
60629
+ }
60630
+ if (!opts.force && isInteractive) {
60631
+ const confirm4 = await p4.confirm({
60632
+ message: `Exclude "${path106.basename(childRoot)}" from multi-root "${parent.name}"? (The parent index will be re-registered without this child.)`,
60633
+ initialValue: false
60634
+ });
60635
+ if (p4.isCancel(confirm4) || !confirm4) {
60636
+ p4.cancel("Cancelled.");
60637
+ return;
60638
+ }
60639
+ }
60640
+ const currentChildren = parent.children ?? [];
60641
+ const newChildren = currentChildren.filter((c) => path106.resolve(c) !== path106.resolve(childRoot));
60642
+ if (newChildren.length === 0) {
60643
+ if (!opts.keepDb && fs94.existsSync(parent.dbPath)) {
60644
+ fs94.unlinkSync(parent.dbPath);
60645
+ }
60646
+ removeProjectConfig(parent.root);
60647
+ unregisterProject(parent.root);
60648
+ if (opts.json) {
60649
+ console.log(JSON.stringify({
60650
+ status: "removed_multi_root",
60651
+ reason: "no children remaining",
60652
+ parent: parent.name
60653
+ }, null, 2));
60654
+ } else {
60655
+ p4.note("No children remaining \u2014 entire multi-root project removed.", "Removed");
60656
+ p4.outro("Multi-root project unregistered.");
60657
+ }
60658
+ return;
60659
+ }
60660
+ if (newChildren.length === 1) {
60661
+ const remainingChild = newChildren[0];
60662
+ if (!opts.keepDb && fs94.existsSync(parent.dbPath)) {
60663
+ fs94.unlinkSync(parent.dbPath);
60664
+ }
60665
+ removeProjectConfig(parent.root);
60666
+ unregisterProject(parent.root);
60667
+ if (opts.json) {
60668
+ console.log(JSON.stringify({
60669
+ status: "excluded_from_multi_root",
60670
+ excluded: path106.basename(childRoot),
60671
+ remaining: path106.basename(remainingChild),
60672
+ hint: `Run \`trace-mcp add ${remainingChild}\` to re-register the remaining project individually.`
60673
+ }, null, 2));
60674
+ } else {
60675
+ p4.note(
60676
+ `Excluded: ${path106.basename(childRoot)}
60677
+ Only one child remaining: ${path106.basename(remainingChild)}
60678
+ Multi-root removed. Run \`trace-mcp add ${remainingChild}\` to re-register individually.`,
60679
+ "Converted"
60680
+ );
60681
+ p4.outro("Child excluded from multi-root.");
60682
+ }
60683
+ return;
60684
+ }
60685
+ if (!opts.keepDb && fs94.existsSync(parent.dbPath)) {
60686
+ fs94.unlinkSync(parent.dbPath);
60687
+ }
60688
+ removeProjectConfig(parent.root);
60689
+ unregisterProject(parent.root);
60690
+ if (opts.json) {
60691
+ console.log(JSON.stringify({
60692
+ status: "excluded_from_multi_root",
60693
+ excluded: path106.basename(childRoot),
60694
+ remaining: newChildren.map((c) => path106.basename(c)),
60695
+ hint: `Run \`trace-mcp add ${parent.root}\` to re-register with ${newChildren.length} children.`
60696
+ }, null, 2));
60697
+ } else {
60698
+ p4.note(
60699
+ `Excluded: ${path106.basename(childRoot)}
60700
+ Remaining children: ${newChildren.map((c) => path106.basename(c)).join(", ")}
60701
+ Run \`trace-mcp add ${parent.root}\` to re-register the multi-root.`,
60702
+ "Excluded"
60703
+ );
60704
+ p4.outro("Child excluded. Re-add the parent to rebuild the index.");
60705
+ }
60706
+ }
60707
+ function shortPath6(p5) {
60708
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
60709
+ if (home && p5.startsWith(home)) return "~" + p5.slice(home.length);
60710
+ return p5;
60711
+ }
60712
+
60713
+ // src/cli/status.ts
60714
+ import { Command as Command11 } from "commander";
60715
+ import fs95 from "fs";
60716
+ import Database7 from "better-sqlite3";
60717
+ function resolveDbPath5(projectRoot) {
60718
+ const entry = getProject(projectRoot);
60719
+ if (entry) return entry.dbPath;
60720
+ return getDbPath(projectRoot);
60721
+ }
60722
+ function formatDuration3(ms) {
60723
+ if (ms < 1e3) return `${ms}ms`;
60724
+ const s = Math.round(ms / 1e3);
60725
+ if (s < 60) return `${s}s`;
60726
+ const m = Math.floor(s / 60);
60727
+ const rem = s % 60;
60728
+ return rem > 0 ? `${m}m ${rem}s` : `${m}m`;
60729
+ }
60730
+ function formatPipeline(name, p5) {
60731
+ const label = name.padEnd(16);
60732
+ switch (p5.phase) {
60733
+ case "idle":
60734
+ return ` ${label} idle`;
60735
+ case "running": {
60736
+ const pct = p5.percentage !== null ? `(${p5.percentage}%)` : "";
60737
+ const count = p5.total > 0 ? `${p5.processed}/${p5.total}` : `${p5.processed}`;
60738
+ const elapsed = p5.elapsedMs > 0 ? ` ${formatDuration3(p5.elapsedMs)} elapsed` : "";
60739
+ return ` ${label} running ${count} ${pct}${elapsed}`;
60740
+ }
60741
+ case "completed": {
60742
+ const count = p5.total > 0 ? `${p5.processed}/${p5.total}` : `${p5.processed}`;
60743
+ const elapsed = p5.elapsedMs > 0 ? ` ${formatDuration3(p5.elapsedMs)}` : "";
60744
+ return ` ${label} completed ${count} (100%)${elapsed}`;
60745
+ }
60746
+ case "error":
60747
+ return ` ${label} error ${p5.error ?? "unknown error"}`;
60748
+ default:
60749
+ return ` ${label} ${p5.phase}`;
60750
+ }
60751
+ }
60752
+ var statusCommand = new Command11("status").description("Show indexing progress for the current project").option("--json", "Output as JSON").action((opts) => {
60753
+ let projectRoot;
60754
+ try {
60755
+ projectRoot = findProjectRoot(process.cwd());
60756
+ } catch {
60757
+ projectRoot = process.cwd();
60758
+ }
60759
+ const dbPath = resolveDbPath5(projectRoot);
60760
+ if (!fs95.existsSync(dbPath)) {
60761
+ console.log(`No index found for ${projectRoot}`);
60762
+ console.log("Run `trace-mcp serve` or `trace-mcp index` first.");
60763
+ process.exit(1);
60764
+ }
60765
+ const db = new Database7(dbPath, { readonly: true });
60766
+ db.pragma("journal_mode = WAL");
60767
+ try {
60768
+ const progress = readProgressFromDb(db);
60769
+ const stats = db.prepare(`
60770
+ SELECT
60771
+ (SELECT COUNT(*) FROM files) as files,
60772
+ (SELECT COUNT(*) FROM symbols) as symbols,
60773
+ (SELECT COUNT(*) FROM edges) as edges
60774
+ `).get();
60775
+ if (opts.json) {
60776
+ console.log(JSON.stringify({ projectRoot, stats, progress }, null, 2));
60777
+ return;
60778
+ }
60779
+ console.log(`
60780
+ trace-mcp status \u2014 ${projectRoot}
60781
+ `);
60782
+ if (progress) {
60783
+ console.log(formatPipeline("Indexing:", progress.indexing));
60784
+ console.log(formatPipeline("Summarization:", progress.summarization));
60785
+ console.log(formatPipeline("Embedding:", progress.embedding));
60786
+ } else {
60787
+ console.log(" No progress data available (server may not have run yet)");
60788
+ }
60789
+ console.log(`
60790
+ Stats: ${stats.files} files \xB7 ${stats.symbols} symbols \xB7 ${stats.edges} edges
60791
+ `);
60792
+ } finally {
60793
+ db.close();
60794
+ }
60795
+ });
60796
+
59931
60797
  // src/cli.ts
59932
- var PKG_VERSION2 = true ? "1.6.1" : "0.0.0-dev";
60798
+ var PKG_VERSION2 = true ? "1.7.0" : "0.0.0-dev";
59933
60799
  function registerDefaultPlugins(registry) {
59934
- for (const p4 of createAllLanguagePlugins()) registry.registerLanguagePlugin(p4);
59935
- for (const p4 of createAllIntegrationPlugins()) registry.registerFrameworkPlugin(p4);
60800
+ for (const p5 of createAllLanguagePlugins()) registry.registerLanguagePlugin(p5);
60801
+ for (const p5 of createAllIntegrationPlugins()) registry.registerFrameworkPlugin(p5);
59936
60802
  }
59937
- function resolveDbPath5(projectRoot) {
60803
+ function resolveDbPath6(projectRoot) {
59938
60804
  const entry = getProject(projectRoot);
59939
60805
  if (entry) return entry.dbPath;
59940
60806
  return getDbPath(projectRoot);
@@ -59968,7 +60834,7 @@ function runFederationAutoSync(projectRoot, config) {
59968
60834
  logger.warn({ error: e }, "Federation auto-sync failed (non-fatal)");
59969
60835
  }
59970
60836
  }
59971
- var program = new Command10();
60837
+ var program = new Command12();
59972
60838
  program.name("trace-mcp").description("Framework-Aware Code Intelligence for Laravel/Vue/Inertia/Nuxt").version(PKG_VERSION2);
59973
60839
  program.command("serve").description("Start MCP server (stdio transport)").action(async () => {
59974
60840
  const projectRoot = process.cwd();
@@ -59988,18 +60854,19 @@ program.command("serve").description("Start MCP server (stdio transport)").actio
59988
60854
  process.exit(1);
59989
60855
  }
59990
60856
  const config = configResult.value;
59991
- const dbPath = resolveDbPath5(projectRoot);
60857
+ const dbPath = resolveDbPath6(projectRoot);
59992
60858
  ensureGlobalDirs();
59993
60859
  const db = initializeDatabase(dbPath);
59994
60860
  const store = new Store(db);
59995
60861
  const registry = new PluginRegistry();
59996
60862
  registerDefaultPlugins(registry);
59997
- const pipeline = new IndexingPipeline(store, registry, config, projectRoot);
60863
+ const progress = new ProgressState(db);
60864
+ const pipeline = new IndexingPipeline(store, registry, config, projectRoot, progress);
59998
60865
  const watcher = new FileWatcher2();
59999
60866
  const aiProvider = createAIProvider(config);
60000
60867
  const vectorStore = config.ai?.enabled ? new BlobVectorStore(store.db) : null;
60001
60868
  const embeddingService = config.ai?.enabled ? aiProvider.embedding() : null;
60002
- const embeddingPipeline = vectorStore && embeddingService ? new EmbeddingPipeline(store, embeddingService, vectorStore) : null;
60869
+ const embeddingPipeline = vectorStore && embeddingService ? new EmbeddingPipeline(store, embeddingService, vectorStore, progress) : null;
60003
60870
  const inferenceCache = config.ai?.enabled ? new InferenceCache(store.db) : null;
60004
60871
  inferenceCache?.evictExpired();
60005
60872
  const summarizationPipeline = config.ai?.enabled && config.ai.summarize_on_index !== false ? new SummarizationPipeline(
@@ -60010,7 +60877,8 @@ program.command("serve").description("Start MCP server (stdio transport)").actio
60010
60877
  batchSize: config.ai.summarize_batch_size ?? 20,
60011
60878
  kinds: config.ai.summarize_kinds ?? ["class", "function", "method", "interface", "trait", "enum", "type"],
60012
60879
  concurrency: config.ai.concurrency ?? 1
60013
- }
60880
+ },
60881
+ progress
60014
60882
  ) : null;
60015
60883
  const runEmbeddings = () => {
60016
60884
  if (!embeddingPipeline) return;
@@ -60044,7 +60912,7 @@ program.command("serve").description("Start MCP server (stdio transport)").actio
60044
60912
  };
60045
60913
  process.on("SIGINT", shutdown);
60046
60914
  process.on("SIGTERM", shutdown);
60047
- const server = createServer2(store, registry, config, projectRoot);
60915
+ const server = createServer2(store, registry, config, projectRoot, progress);
60048
60916
  const transport = new StdioServerTransport();
60049
60917
  logger.info({ projectRoot, dbPath }, "Starting trace-mcp MCP server...");
60050
60918
  await server.connect(transport);
@@ -60057,18 +60925,19 @@ program.command("serve-http").description("Start MCP server (HTTP/SSE transport)
60057
60925
  process.exit(1);
60058
60926
  }
60059
60927
  const config = configResult.value;
60060
- const dbPath = resolveDbPath5(projectRoot);
60928
+ const dbPath = resolveDbPath6(projectRoot);
60061
60929
  ensureGlobalDirs();
60062
60930
  const db = initializeDatabase(dbPath);
60063
60931
  const store = new Store(db);
60064
60932
  const registry = new PluginRegistry();
60065
60933
  registerDefaultPlugins(registry);
60066
- const pipeline = new IndexingPipeline(store, registry, config, projectRoot);
60934
+ const progress2 = new ProgressState(db);
60935
+ const pipeline = new IndexingPipeline(store, registry, config, projectRoot, progress2);
60067
60936
  const watcher = new FileWatcher2();
60068
60937
  const aiProvider = createAIProvider(config);
60069
60938
  const vectorStore = config.ai?.enabled ? new BlobVectorStore(store.db) : null;
60070
60939
  const embeddingService = config.ai?.enabled ? aiProvider.embedding() : null;
60071
- const embeddingPipeline = vectorStore && embeddingService ? new EmbeddingPipeline(store, embeddingService, vectorStore) : null;
60940
+ const embeddingPipeline = vectorStore && embeddingService ? new EmbeddingPipeline(store, embeddingService, vectorStore, progress2) : null;
60072
60941
  const inferenceCache2 = config.ai?.enabled ? new InferenceCache(store.db) : null;
60073
60942
  inferenceCache2?.evictExpired();
60074
60943
  const summarizationPipeline2 = config.ai?.enabled && config.ai.summarize_on_index !== false ? new SummarizationPipeline(
@@ -60079,7 +60948,8 @@ program.command("serve-http").description("Start MCP server (HTTP/SSE transport)
60079
60948
  batchSize: config.ai.summarize_batch_size ?? 20,
60080
60949
  kinds: config.ai.summarize_kinds ?? ["class", "function", "method", "interface", "trait", "enum", "type"],
60081
60950
  concurrency: config.ai.concurrency ?? 1
60082
- }
60951
+ },
60952
+ progress2
60083
60953
  ) : null;
60084
60954
  const runEmbeddings = () => {
60085
60955
  if (!embeddingPipeline) return;
@@ -60109,7 +60979,7 @@ program.command("serve-http").description("Start MCP server (HTTP/SSE transport)
60109
60979
  });
60110
60980
  const port = parseInt(opts.port, 10);
60111
60981
  const host = opts.host;
60112
- const server = createServer2(store, registry, config, projectRoot);
60982
+ const server = createServer2(store, registry, config, projectRoot, progress2);
60113
60983
  const transport = new StreamableHTTPServerTransport({
60114
60984
  sessionIdGenerator: () => randomUUID()
60115
60985
  });
@@ -60211,8 +61081,8 @@ program.command("serve-http").description("Start MCP server (HTTP/SSE transport)
60211
61081
  });
60212
61082
  });
60213
61083
  program.command("index").description("Index a project directory").argument("<dir>", "Directory to index").option("-f, --force", "Force reindex all files").action(async (dir, opts) => {
60214
- const resolvedDir = path106.resolve(dir);
60215
- if (!fs94.existsSync(resolvedDir)) {
61084
+ const resolvedDir = path107.resolve(dir);
61085
+ if (!fs96.existsSync(resolvedDir)) {
60216
61086
  logger.error({ dir: resolvedDir }, "Directory does not exist");
60217
61087
  process.exit(1);
60218
61088
  }
@@ -60222,7 +61092,7 @@ program.command("index").description("Index a project directory").argument("<dir
60222
61092
  process.exit(1);
60223
61093
  }
60224
61094
  const config = configResult.value;
60225
- const dbPath = resolveDbPath5(resolvedDir);
61095
+ const dbPath = resolveDbPath6(resolvedDir);
60226
61096
  ensureGlobalDirs();
60227
61097
  const db = initializeDatabase(dbPath);
60228
61098
  const store = new Store(db);
@@ -60236,20 +61106,20 @@ program.command("index").description("Index a project directory").argument("<dir
60236
61106
  db.close();
60237
61107
  });
60238
61108
  program.command("index-file").description("Incrementally reindex a single file (called by the PostToolUse auto-reindex hook)").argument("<file>", "Absolute or relative path to the file to reindex").action(async (file) => {
60239
- const resolvedFile = path106.resolve(file);
60240
- if (!fs94.existsSync(resolvedFile)) {
61109
+ const resolvedFile = path107.resolve(file);
61110
+ if (!fs96.existsSync(resolvedFile)) {
60241
61111
  process.exit(0);
60242
61112
  }
60243
61113
  let projectRoot;
60244
61114
  try {
60245
- projectRoot = findProjectRoot(path106.dirname(resolvedFile));
61115
+ projectRoot = findProjectRoot(path107.dirname(resolvedFile));
60246
61116
  } catch {
60247
61117
  process.exit(0);
60248
61118
  }
60249
61119
  const configResult = await loadConfig(projectRoot);
60250
61120
  if (configResult.isErr()) process.exit(0);
60251
61121
  const config = configResult.value;
60252
- const dbPath = resolveDbPath5(projectRoot);
61122
+ const dbPath = resolveDbPath6(projectRoot);
60253
61123
  ensureGlobalDirs();
60254
61124
  const db = initializeDatabase(dbPath);
60255
61125
  const store = new Store(db);
@@ -60277,11 +61147,11 @@ program.command("list").description("List all registered projects").option("--js
60277
61147
  console.log("No projects registered. Run `trace-mcp add` in a project directory.");
60278
61148
  } else {
60279
61149
  console.log("Registered projects:\n");
60280
- for (const p4 of projects) {
60281
- const lastIdx = p4.lastIndexed ? new Date(p4.lastIndexed).toLocaleString() : "never";
60282
- const dbExists = fs94.existsSync(p4.dbPath) ? "ok" : "missing";
60283
- console.log(` ${p4.name}`);
60284
- console.log(` Root: ${p4.root}`);
61150
+ for (const p5 of projects) {
61151
+ const lastIdx = p5.lastIndexed ? new Date(p5.lastIndexed).toLocaleString() : "never";
61152
+ const dbExists = fs96.existsSync(p5.dbPath) ? "ok" : "missing";
61153
+ console.log(` ${p5.name}`);
61154
+ console.log(` Root: ${p5.root}`);
60285
61155
  console.log(` DB: ${dbExists}`);
60286
61156
  console.log(` Last indexed: ${lastIdx}`);
60287
61157
  console.log();
@@ -60291,11 +61161,13 @@ program.command("list").description("List all registered projects").option("--js
60291
61161
  program.addCommand(initCommand);
60292
61162
  program.addCommand(upgradeCommand);
60293
61163
  program.addCommand(addCommand);
61164
+ program.addCommand(removeCommand);
60294
61165
  program.addCommand(doctorCommand);
60295
61166
  program.addCommand(ciReportCommand);
60296
61167
  program.addCommand(checkCommand);
60297
61168
  program.addCommand(bundlesCommand);
60298
61169
  program.addCommand(federationCommand);
60299
61170
  program.addCommand(analyticsCommand);
61171
+ program.addCommand(statusCommand);
60300
61172
  program.parse();
60301
61173
  //# sourceMappingURL=cli.js.map