ctxloom-pro 1.0.31 → 1.1.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.
@@ -169,12 +169,12 @@ var init_VectorStore = __esm({
169
169
  // server/index.ts
170
170
  import express from "express";
171
171
  import cors from "cors";
172
- import path38 from "path";
173
- import fs31 from "fs";
172
+ import path40 from "path";
173
+ import fs32 from "fs";
174
174
  import { fileURLToPath as fileURLToPath2 } from "url";
175
175
 
176
176
  // server/loader.ts
177
- import path32 from "path";
177
+ import path34 from "path";
178
178
 
179
179
  // ../../packages/core/src/graph/DependencyGraph.ts
180
180
  import fs7 from "fs";
@@ -3133,8 +3133,8 @@ var CoChangeIndex = class _CoChangeIndex {
3133
3133
  if (event.isBulk || event.isMerge) return;
3134
3134
  const paths = event.files.map((f) => f.path);
3135
3135
  if (paths.length === 0) return;
3136
- for (const path39 of paths) {
3137
- this.nodeCounts.set(path39, (this.nodeCounts.get(path39) ?? 0) + 1);
3136
+ for (const path41 of paths) {
3137
+ this.nodeCounts.set(path41, (this.nodeCounts.get(path41) ?? 0) + 1);
3138
3138
  }
3139
3139
  for (let i = 0; i < paths.length; i++) {
3140
3140
  for (let j = i + 1; j < paths.length; j++) {
@@ -3281,8 +3281,8 @@ var ChurnIndex = class _ChurnIndex {
3281
3281
  */
3282
3282
  snapshot() {
3283
3283
  const nodes = {};
3284
- for (const [path39, raw] of this.nodes) {
3285
- nodes[path39] = {
3284
+ for (const [path41, raw] of this.nodes) {
3285
+ nodes[path41] = {
3286
3286
  commits: raw.commits,
3287
3287
  churnLines: raw.churnLines,
3288
3288
  bugCommits: raw.bugCommits,
@@ -3297,8 +3297,8 @@ var ChurnIndex = class _ChurnIndex {
3297
3297
  */
3298
3298
  static load(s) {
3299
3299
  const idx = new _ChurnIndex();
3300
- for (const [path39, raw] of Object.entries(s.nodes)) {
3301
- idx.nodes.set(path39, {
3300
+ for (const [path41, raw] of Object.entries(s.nodes)) {
3301
+ idx.nodes.set(path41, {
3302
3302
  commits: raw.commits,
3303
3303
  churnLines: raw.churnLines,
3304
3304
  bugCommits: raw.bugCommits,
@@ -3311,8 +3311,8 @@ var ChurnIndex = class _ChurnIndex {
3311
3311
  // -------------------------------------------------------------------------
3312
3312
  // Private helpers
3313
3313
  // -------------------------------------------------------------------------
3314
- getOrCreate(path39) {
3315
- const existing = this.nodes.get(path39);
3314
+ getOrCreate(path41) {
3315
+ const existing = this.nodes.get(path41);
3316
3316
  if (existing !== void 0) return existing;
3317
3317
  const fresh = {
3318
3318
  commits: 0,
@@ -3321,7 +3321,7 @@ var ChurnIndex = class _ChurnIndex {
3321
3321
  authorCounts: {},
3322
3322
  lastTouch: 0
3323
3323
  };
3324
- this.nodes.set(path39, fresh);
3324
+ this.nodes.set(path41, fresh);
3325
3325
  return fresh;
3326
3326
  }
3327
3327
  };
@@ -3402,12 +3402,12 @@ var OwnershipIndex = class _OwnershipIndex {
3402
3402
  */
3403
3403
  snapshot() {
3404
3404
  const nodes = {};
3405
- for (const [path39, raw] of this.nodes) {
3405
+ for (const [path41, raw] of this.nodes) {
3406
3406
  const authorWeights = {};
3407
3407
  for (const [email, entry] of Object.entries(raw.authorWeights)) {
3408
3408
  authorWeights[email] = { ...entry };
3409
3409
  }
3410
- nodes[path39] = { authorWeights, lastTouch: raw.lastTouch };
3410
+ nodes[path41] = { authorWeights, lastTouch: raw.lastTouch };
3411
3411
  }
3412
3412
  return { version: 1, nodes };
3413
3413
  }
@@ -3416,23 +3416,23 @@ var OwnershipIndex = class _OwnershipIndex {
3416
3416
  */
3417
3417
  static load(s) {
3418
3418
  const idx = new _OwnershipIndex();
3419
- for (const [path39, raw] of Object.entries(s.nodes)) {
3419
+ for (const [path41, raw] of Object.entries(s.nodes)) {
3420
3420
  const authorWeights = {};
3421
3421
  for (const [email, entry] of Object.entries(raw.authorWeights)) {
3422
3422
  authorWeights[email] = { ...entry };
3423
3423
  }
3424
- idx.nodes.set(path39, { authorWeights, lastTouch: raw.lastTouch });
3424
+ idx.nodes.set(path41, { authorWeights, lastTouch: raw.lastTouch });
3425
3425
  }
3426
3426
  return idx;
3427
3427
  }
3428
3428
  // -------------------------------------------------------------------------
3429
3429
  // Private helpers
3430
3430
  // -------------------------------------------------------------------------
3431
- getOrCreate(path39) {
3432
- const existing = this.nodes.get(path39);
3431
+ getOrCreate(path41) {
3432
+ const existing = this.nodes.get(path41);
3433
3433
  if (existing !== void 0) return existing;
3434
3434
  const fresh = { authorWeights: {}, lastTouch: 0 };
3435
- this.nodes.set(path39, fresh);
3435
+ this.nodes.set(path41, fresh);
3436
3436
  return fresh;
3437
3437
  }
3438
3438
  };
@@ -4670,8 +4670,8 @@ function getErrorMap() {
4670
4670
 
4671
4671
  // ../../node_modules/zod/v3/helpers/parseUtil.js
4672
4672
  var makeIssue = (params) => {
4673
- const { data, path: path39, errorMaps, issueData } = params;
4674
- const fullPath = [...path39, ...issueData.path || []];
4673
+ const { data, path: path41, errorMaps, issueData } = params;
4674
+ const fullPath = [...path41, ...issueData.path || []];
4675
4675
  const fullIssue = {
4676
4676
  ...issueData,
4677
4677
  path: fullPath
@@ -4787,11 +4787,11 @@ var errorUtil;
4787
4787
 
4788
4788
  // ../../node_modules/zod/v3/types.js
4789
4789
  var ParseInputLazyPath = class {
4790
- constructor(parent, value, path39, key) {
4790
+ constructor(parent, value, path41, key) {
4791
4791
  this._cachedPath = [];
4792
4792
  this.parent = parent;
4793
4793
  this.data = value;
4794
- this._path = path39;
4794
+ this._path = path41;
4795
4795
  this._key = key;
4796
4796
  }
4797
4797
  get path() {
@@ -8233,21 +8233,33 @@ var coerce = {
8233
8233
  };
8234
8234
  var NEVER = INVALID;
8235
8235
 
8236
+ // ../../packages/core/src/tools/projectRootParam.ts
8237
+ var PROJECT_ROOT_DESCRIPTION = "Absolute path or registered alias of the project to operate on. Falls back to CTXLOOM_ROOT env, then server cwd. Register aliases with `ctxloom register <path> --alias <name>`.";
8238
+ var ProjectRootField = external_exports.string().optional().describe(PROJECT_ROOT_DESCRIPTION);
8239
+
8240
+ // ../../packages/core/src/tools/registry.ts
8241
+ init_logger();
8242
+
8236
8243
  // ../../packages/core/src/tools/search.ts
8237
8244
  init_embedder();
8238
8245
  var Schema = external_exports.object({
8239
8246
  query: external_exports.string().describe("Search query \u2014 natural language or code fragment"),
8240
- limit: external_exports.number().max(100).optional().default(10).describe("Maximum results to return")
8247
+ limit: external_exports.number().max(100).optional().default(10).describe("Maximum results to return"),
8248
+ project_root: ProjectRootField
8241
8249
  });
8242
8250
 
8243
8251
  // ../../packages/core/src/tools/file.ts
8244
- var Schema2 = external_exports.object({ path: external_exports.string().describe("Relative path to the file") });
8252
+ var Schema2 = external_exports.object({
8253
+ path: external_exports.string().describe("Relative path to the file"),
8254
+ project_root: ProjectRootField
8255
+ });
8245
8256
 
8246
8257
  // ../../packages/core/src/tools/context-packet.ts
8247
8258
  import path16 from "path";
8248
8259
  var Schema3 = external_exports.object({
8249
8260
  target_file: external_exports.string().describe("Relative path to the primary file"),
8250
- mode: external_exports.enum(["edit", "read"]).optional().default("edit").describe("Context mode")
8261
+ mode: external_exports.enum(["edit", "read"]).optional().default("edit").describe("Context mode"),
8262
+ project_root: ProjectRootField
8251
8263
  });
8252
8264
 
8253
8265
  // ../../packages/core/src/tools/findCallers.ts
@@ -8258,17 +8270,25 @@ var Schema4 = external_exports.object({
8258
8270
  symbol: external_exports.string().describe("Symbol name to search for"),
8259
8271
  direction: external_exports.enum(["callers", "callees"]).optional().default("callers").describe("Traversal direction"),
8260
8272
  depth: external_exports.number().max(10).optional().default(1).describe("Transitive traversal depth (max 10)"),
8261
- target_file: external_exports.string().optional().describe("Optional: relative file path to start from")
8273
+ target_file: external_exports.string().optional().describe("Optional: relative file path to start from"),
8274
+ project_root: ProjectRootField
8262
8275
  });
8263
8276
 
8264
8277
  // ../../packages/core/src/tools/definition.ts
8265
- var Schema5 = external_exports.object({ symbol: external_exports.string().describe("Symbol name to look up") });
8278
+ var Schema5 = external_exports.object({
8279
+ symbol: external_exports.string().describe("Symbol name to look up"),
8280
+ project_root: ProjectRootField
8281
+ });
8282
+
8283
+ // ../../packages/core/src/tools/rules.ts
8284
+ var Schema6 = external_exports.object({ project_root: ProjectRootField });
8266
8285
 
8267
8286
  // ../../packages/core/src/tools/similar-files.ts
8268
8287
  init_embedder();
8269
- var Schema6 = external_exports.object({
8288
+ var Schema7 = external_exports.object({
8270
8289
  target_file: external_exports.string().describe("Relative path to the file to find similar files for"),
8271
- limit: external_exports.number().max(100).optional().default(10).describe("Maximum results to return")
8290
+ limit: external_exports.number().max(100).optional().default(10).describe("Maximum results to return"),
8291
+ project_root: ProjectRootField
8272
8292
  });
8273
8293
 
8274
8294
  // ../../packages/core/src/tools/blast-radius.ts
@@ -8276,37 +8296,40 @@ import { exec } from "child_process";
8276
8296
  import { promisify } from "util";
8277
8297
  init_logger();
8278
8298
  var execAsync = promisify(exec);
8279
- var Schema7 = external_exports.object({
8299
+ var Schema8 = external_exports.object({
8280
8300
  changed_files: external_exports.array(external_exports.string()).optional().describe("Changed file paths (relative). Defaults to git diff HEAD~1."),
8281
8301
  depth: external_exports.number().min(1).max(10).optional().default(3).describe("Traversal depth (default: 3)"),
8282
8302
  use_git: external_exports.boolean().optional().default(true).describe("Auto-detect changed files from git diff HEAD~1"),
8283
8303
  detail_level: external_exports.enum(["standard", "minimal"]).default("standard").describe(
8284
8304
  '"standard" (default) returns full per-file listings. "minimal" returns counts only \u2014 ~60% fewer tokens.'
8285
- )
8305
+ ),
8306
+ project_root: ProjectRootField
8286
8307
  });
8287
8308
 
8288
8309
  // ../../packages/core/src/tools/hub-nodes.ts
8289
- var Schema8 = external_exports.object({
8310
+ var Schema9 = external_exports.object({
8290
8311
  limit: external_exports.number().min(1).max(100).optional().default(20).describe("Number of hub nodes to return (default: 20)"),
8291
8312
  min_degree: external_exports.number().min(0).optional().default(2).describe("Minimum total degree to include (default: 2)"),
8292
8313
  detail_level: external_exports.enum(["standard", "minimal"]).default("standard").describe(
8293
8314
  '"standard" (default) returns full per-file listings. "minimal" returns counts only \u2014 ~60% fewer tokens.'
8294
- )
8315
+ ),
8316
+ project_root: ProjectRootField
8295
8317
  });
8296
8318
 
8297
8319
  // ../../packages/core/src/tools/bridge-nodes.ts
8298
- var Schema9 = external_exports.object({
8320
+ var Schema10 = external_exports.object({
8299
8321
  limit: external_exports.number().min(1).max(100).optional().default(20).describe("Number of bridge nodes to return (default: 20)"),
8300
8322
  sample: external_exports.number().min(10).max(1e3).optional().default(200).describe(
8301
8323
  "Max source nodes for BFS sampling (default: 200). Lower = faster but approximate."
8302
8324
  ),
8303
8325
  detail_level: external_exports.enum(["standard", "minimal"]).default("standard").describe(
8304
8326
  '"standard" (default) returns full per-file listings. "minimal" returns counts only \u2014 ~60% fewer tokens.'
8305
- )
8327
+ ),
8328
+ project_root: ProjectRootField
8306
8329
  });
8307
8330
 
8308
8331
  // ../../packages/core/src/tools/community-list.ts
8309
- var Schema10 = external_exports.object({
8332
+ var Schema11 = external_exports.object({
8310
8333
  show_files: external_exports.boolean().optional().default(false).describe(
8311
8334
  "Include member file paths in output (default: false for compact output)"
8312
8335
  ),
@@ -8321,22 +8344,24 @@ var Schema10 = external_exports.object({
8321
8344
  ),
8322
8345
  detail_level: external_exports.enum(["standard", "minimal"]).default("standard").describe(
8323
8346
  '"standard" (default) returns paged community list. "minimal" returns counts only \u2014 useful for a quick size check before paging.'
8324
- )
8347
+ ),
8348
+ project_root: ProjectRootField
8325
8349
  });
8326
8350
 
8327
8351
  // ../../packages/core/src/tools/architecture-overview.ts
8328
- var Schema11 = external_exports.object({
8352
+ var Schema12 = external_exports.object({
8329
8353
  hub_limit: external_exports.number().min(1).max(10).optional().default(3).describe(
8330
8354
  "Number of top hub files to show per community (default: 3)"
8331
8355
  ),
8332
8356
  detail_level: external_exports.enum(["standard", "minimal"]).default("standard").describe(
8333
8357
  '"standard" (default) returns full per-community listings. "minimal" returns counts only \u2014 ~60% fewer tokens.'
8334
- )
8358
+ ),
8359
+ project_root: ProjectRootField
8335
8360
  });
8336
8361
 
8337
8362
  // ../../packages/core/src/tools/knowledge-gaps.ts
8338
8363
  import path18 from "path";
8339
- var Schema12 = external_exports.object({
8364
+ var Schema13 = external_exports.object({
8340
8365
  min_importers: external_exports.number().min(1).max(50).optional().default(3).describe(
8341
8366
  "Minimum importers to qualify as an untested hub (default: 3)"
8342
8367
  ),
@@ -8345,11 +8370,12 @@ var Schema12 = external_exports.object({
8345
8370
  ),
8346
8371
  detail_level: external_exports.enum(["standard", "minimal"]).default("standard").describe(
8347
8372
  '"standard" (default) returns full per-file listings. "minimal" returns counts only \u2014 ~60% fewer tokens.'
8348
- )
8373
+ ),
8374
+ project_root: ProjectRootField
8349
8375
  });
8350
8376
 
8351
8377
  // ../../packages/core/src/tools/surprising-connections.ts
8352
- var Schema13 = external_exports.object({
8378
+ var Schema14 = external_exports.object({
8353
8379
  max_cycles: external_exports.number().min(1).max(100).optional().default(20).describe(
8354
8380
  "Max circular dependency cycles to report (default: 20)"
8355
8381
  ),
@@ -8358,25 +8384,28 @@ var Schema13 = external_exports.object({
8358
8384
  ),
8359
8385
  detail_level: external_exports.enum(["standard", "minimal"]).default("standard").describe(
8360
8386
  '"standard" (default) returns full per-item listings. "minimal" returns counts only \u2014 ~60% fewer tokens.'
8361
- )
8387
+ ),
8388
+ project_root: ProjectRootField
8362
8389
  });
8363
8390
 
8364
8391
  // ../../packages/core/src/tools/wiki-generate.ts
8365
8392
  import fs16 from "fs";
8366
- var Schema14 = external_exports.object({
8393
+ var Schema15 = external_exports.object({
8367
8394
  force: external_exports.boolean().optional().default(false).describe(
8368
8395
  "Regenerate all pages even if content unchanged (default: false)"
8369
8396
  ),
8370
8397
  detail_level: external_exports.enum(["standard", "minimal"]).default("standard").describe(
8371
8398
  '"standard" (default) lists each written page with size. "minimal" returns counts only.'
8372
- )
8399
+ ),
8400
+ project_root: ProjectRootField
8373
8401
  });
8374
8402
 
8375
8403
  // ../../packages/core/src/tools/graph-export.ts
8376
- var Schema15 = external_exports.object({
8404
+ var Schema16 = external_exports.object({
8377
8405
  format: external_exports.enum(["graphml", "dot", "obsidian", "svg", "html"]).describe(
8378
8406
  "Output format: graphml (Gephi/yEd), dot (Graphviz), obsidian (wikilink vault), svg (inline, no dependencies), html (interactive D3.js browser view)"
8379
- )
8407
+ ),
8408
+ project_root: ProjectRootField
8380
8409
  });
8381
8410
 
8382
8411
  // ../../packages/core/src/tools/git-diff-review.ts
@@ -8384,7 +8413,7 @@ import { execFile } from "child_process";
8384
8413
  import { promisify as promisify2 } from "util";
8385
8414
  init_logger();
8386
8415
  var execFileAsync = promisify2(execFile);
8387
- var Schema16 = external_exports.object({
8416
+ var Schema17 = external_exports.object({
8388
8417
  changed_files: external_exports.array(external_exports.string()).optional().describe(
8389
8418
  "Changed file paths (relative to project root). Omit to auto-detect from git diff HEAD~1."
8390
8419
  ),
@@ -8395,22 +8424,24 @@ var Schema16 = external_exports.object({
8395
8424
  ),
8396
8425
  max_diff_lines: external_exports.number().min(10).max(2e3).optional().default(300).describe(
8397
8426
  "Max diff lines to include per file (default: 300)"
8398
- )
8427
+ ),
8428
+ project_root: ProjectRootField
8399
8429
  });
8400
8430
 
8401
8431
  // ../../packages/core/src/tools/refactor-preview.ts
8402
8432
  import fs17 from "fs";
8403
8433
  import path19 from "path";
8404
- var Schema17 = external_exports.object({
8434
+ var Schema18 = external_exports.object({
8405
8435
  symbol: external_exports.string().min(1).describe("Symbol name to rename (exact match, case-sensitive)"),
8406
8436
  new_name: external_exports.string().min(1).describe("New name for the symbol"),
8407
8437
  max_files: external_exports.number().min(1).max(200).optional().default(50).describe(
8408
8438
  "Maximum number of files to scan for occurrences (default: 50)"
8409
- )
8439
+ ),
8440
+ project_root: ProjectRootField
8410
8441
  });
8411
8442
 
8412
8443
  // ../../packages/core/src/tools/execution-flow.ts
8413
- var Schema18 = external_exports.object({
8444
+ var Schema19 = external_exports.object({
8414
8445
  entry_point: external_exports.string().min(1).describe("Symbol name to start the execution flow from"),
8415
8446
  entry_file: external_exports.string().optional().describe(
8416
8447
  "File path containing the entry symbol (relative). Disambiguates when the same symbol name appears in multiple files."
@@ -8418,7 +8449,8 @@ var Schema18 = external_exports.object({
8418
8449
  depth: external_exports.number().min(1).max(20).optional().default(10).describe("Max traversal depth (default: 10)"),
8419
8450
  max_nodes: external_exports.number().min(1).max(200).optional().default(50).describe(
8420
8451
  "Max total steps to include in output (default: 50)"
8421
- )
8452
+ ),
8453
+ project_root: ProjectRootField
8422
8454
  });
8423
8455
 
8424
8456
  // ../../packages/core/src/tools/cross-repo-search.ts
@@ -8427,20 +8459,21 @@ init_VectorStore();
8427
8459
  init_logger();
8428
8460
  import fs18 from "fs";
8429
8461
  import path20 from "path";
8430
- var Schema19 = external_exports.object({
8462
+ var Schema20 = external_exports.object({
8431
8463
  query: external_exports.string().min(1).describe("Search query \u2014 natural language or code fragment"),
8432
8464
  limit: external_exports.number().min(1).max(100).optional().default(10).describe(
8433
8465
  "Maximum total results across all repos (default: 10)"
8434
8466
  ),
8435
8467
  repos: external_exports.array(external_exports.string()).optional().describe(
8436
8468
  "Specific repo root paths to search. Omit to search all registered repos."
8437
- )
8469
+ ),
8470
+ project_root: ProjectRootField
8438
8471
  });
8439
8472
 
8440
8473
  // ../../packages/core/src/tools/apply-refactor.ts
8441
8474
  import fs19 from "fs";
8442
8475
  import path21 from "path";
8443
- var Schema20 = external_exports.object({
8476
+ var Schema21 = external_exports.object({
8444
8477
  symbol: external_exports.string().min(1).describe("Symbol name to rename (exact, case-sensitive)"),
8445
8478
  new_name: external_exports.string().min(1).describe("New name for the symbol"),
8446
8479
  dry_run: external_exports.boolean().optional().default(false).describe(
@@ -8448,7 +8481,8 @@ var Schema20 = external_exports.object({
8448
8481
  ),
8449
8482
  max_files: external_exports.number().min(1).max(200).optional().default(50).describe(
8450
8483
  "Maximum candidate files to process (default: 50)"
8451
- )
8484
+ ),
8485
+ project_root: ProjectRootField
8452
8486
  });
8453
8487
 
8454
8488
  // ../../packages/core/src/tools/detect-changes.ts
@@ -8456,24 +8490,26 @@ import { exec as exec2 } from "child_process";
8456
8490
  import { promisify as promisify3 } from "util";
8457
8491
  init_logger();
8458
8492
  var execAsync2 = promisify3(exec2);
8459
- var Schema21 = external_exports.object({
8493
+ var Schema22 = external_exports.object({
8460
8494
  changed_files: external_exports.array(external_exports.string()).optional(),
8461
8495
  use_git: external_exports.boolean().optional().default(true),
8462
8496
  depth: external_exports.number().min(1).max(10).optional().default(3),
8463
8497
  detail_level: external_exports.enum(["standard", "minimal"]).default("standard").describe(
8464
8498
  '"standard" (default) returns full per-file risk details. "minimal" returns counts only \u2014 ~60% fewer tokens.'
8465
- )
8499
+ ),
8500
+ project_root: ProjectRootField
8466
8501
  });
8467
8502
 
8468
8503
  // ../../packages/core/src/tools/full-text-search.ts
8469
8504
  import fs20 from "fs";
8470
8505
  import path22 from "path";
8471
- var Schema22 = external_exports.object({
8506
+ var Schema23 = external_exports.object({
8472
8507
  query: external_exports.string().min(1).describe("Search term \u2014 literal or /regex/"),
8473
8508
  mode: external_exports.enum(["hybrid", "keyword", "semantic"]).optional().default("hybrid"),
8474
8509
  case_sensitive: external_exports.boolean().optional().default(false),
8475
8510
  limit: external_exports.number().min(1).max(100).optional().default(20),
8476
- context_lines: external_exports.number().min(0).max(5).optional().default(1)
8511
+ context_lines: external_exports.number().min(0).max(5).optional().default(1),
8512
+ project_root: ProjectRootField
8477
8513
  });
8478
8514
 
8479
8515
  // ../../packages/core/src/tools/suggested-questions.ts
@@ -8481,14 +8517,16 @@ init_logger();
8481
8517
  import { exec as exec3 } from "child_process";
8482
8518
  import { promisify as promisify4 } from "util";
8483
8519
  var execAsync3 = promisify4(exec3);
8484
- var Schema23 = external_exports.object({
8520
+ var Schema24 = external_exports.object({
8485
8521
  changed_files: external_exports.array(external_exports.string()).optional(),
8486
- use_git: external_exports.boolean().optional().default(true)
8522
+ use_git: external_exports.boolean().optional().default(true),
8523
+ project_root: ProjectRootField
8487
8524
  });
8488
8525
 
8489
8526
  // ../../packages/core/src/tools/get-workflow.ts
8490
- var Schema24 = external_exports.object({
8491
- workflow: external_exports.enum(["review", "debug", "onboard", "refactor", "audit"])
8527
+ var Schema25 = external_exports.object({
8528
+ workflow: external_exports.enum(["review", "debug", "onboard", "refactor", "audit"]),
8529
+ project_root: ProjectRootField
8492
8530
  });
8493
8531
 
8494
8532
  // ../../packages/core/src/tools/graph-snapshot.ts
@@ -8500,7 +8538,8 @@ var schema = external_exports.object({
8500
8538
  ),
8501
8539
  overwrite: external_exports.boolean().default(false).describe(
8502
8540
  "If true, overwrite an existing snapshot with the same name."
8503
- )
8541
+ ),
8542
+ project_root: ProjectRootField
8504
8543
  });
8505
8544
 
8506
8545
  // ../../packages/core/src/tools/graph-diff.ts
@@ -8508,7 +8547,8 @@ import fs22 from "fs";
8508
8547
  import path24 from "path";
8509
8548
  var schema2 = external_exports.object({
8510
8549
  baseline: external_exports.string().min(1).describe('Name of the baseline snapshot (the "before" state).'),
8511
- current: external_exports.string().min(1).describe('Name of the current snapshot (the "after" state).')
8550
+ current: external_exports.string().min(1).describe('Name of the current snapshot (the "after" state).'),
8551
+ project_root: ProjectRootField
8512
8552
  });
8513
8553
 
8514
8554
  // ../../packages/core/src/tools/find-large-functions.ts
@@ -8521,20 +8561,23 @@ var schema3 = external_exports.object({
8521
8561
  ),
8522
8562
  limit: external_exports.number().int().min(1).max(200).default(30).describe(
8523
8563
  "Maximum results to return (default: 30)."
8524
- )
8564
+ ),
8565
+ project_root: ProjectRootField
8525
8566
  });
8526
8567
 
8527
8568
  // ../../packages/core/src/tools/git-coupling.ts
8528
- var Schema25 = external_exports.object({
8569
+ var Schema26 = external_exports.object({
8529
8570
  file: external_exports.string().describe("File path to look up co-changed siblings for"),
8530
8571
  limit: external_exports.number().int().min(1).max(50).default(10),
8531
8572
  min_confidence: external_exports.number().min(0).max(1).default(0.05),
8532
- half_life_days: external_exports.number().int().min(1).max(3650).default(90)
8573
+ half_life_days: external_exports.number().int().min(1).max(3650).default(90),
8574
+ project_root: ProjectRootField
8533
8575
  });
8534
8576
 
8535
8577
  // ../../packages/core/src/tools/risk-overlay.ts
8536
- var Schema26 = external_exports.object({
8537
- nodes: external_exports.array(external_exports.string()).min(1).max(200).describe("File paths to score")
8578
+ var Schema27 = external_exports.object({
8579
+ nodes: external_exports.array(external_exports.string()).min(1).max(200).describe("File paths to score"),
8580
+ project_root: ProjectRootField
8538
8581
  });
8539
8582
 
8540
8583
  // ../../packages/core/src/rules/loadConfig.ts
@@ -11140,12 +11183,15 @@ var RulesConfigSchema = external_exports.object({
11140
11183
  rules: external_exports.array(RuleSchema).default([])
11141
11184
  });
11142
11185
 
11186
+ // ../../packages/core/src/tools/rules-check.ts
11187
+ var Schema28 = external_exports.object({ project_root: ProjectRootField });
11188
+
11143
11189
  // ../../packages/core/src/tools/get-affected-flows.ts
11144
11190
  init_logger();
11145
11191
  import { exec as exec4 } from "child_process";
11146
11192
  import { promisify as promisify5 } from "util";
11147
11193
  var execAsync4 = promisify5(exec4);
11148
- var Schema27 = external_exports.object({
11194
+ var Schema29 = external_exports.object({
11149
11195
  changed_files: external_exports.array(external_exports.string()).optional().describe(
11150
11196
  "Changed file paths (relative). Defaults to auto-detection from git diff HEAD~1."
11151
11197
  ),
@@ -11160,7 +11206,8 @@ var Schema27 = external_exports.object({
11160
11206
  ),
11161
11207
  max_steps_per_flow: external_exports.number().min(1).max(100).optional().default(30).describe(
11162
11208
  "Max call chain steps per flow (default: 30)"
11163
- )
11209
+ ),
11210
+ project_root: ProjectRootField
11164
11211
  });
11165
11212
 
11166
11213
  // ../../packages/core/src/tools/ruleManager.ts
@@ -11226,9 +11273,19 @@ var TELEMETRY_DISABLED = process.env["CTXLOOM_NO_TELEMETRY"] === "1" || process.
11226
11273
  var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (typeof __TELEMETRY_POSTHOG_KEY__ === "string" ? __TELEMETRY_POSTHOG_KEY__ : "");
11227
11274
  var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (typeof __TELEMETRY_SENTRY_DSN__ === "string" ? __TELEMETRY_SENTRY_DSN__ : "");
11228
11275
 
11276
+ // ../../packages/core/src/server/ProjectState.ts
11277
+ import path32 from "path";
11278
+
11279
+ // ../../packages/core/src/server/ProjectStateManager.ts
11280
+ init_logger();
11281
+
11282
+ // ../../packages/core/src/server/resolveProjectRoot.ts
11283
+ import fs29 from "fs";
11284
+ import path33 from "path";
11285
+
11229
11286
  // server/loader.ts
11230
11287
  async function loadContext(root) {
11231
- const absRoot = path32.resolve(root);
11288
+ const absRoot = path34.resolve(root);
11232
11289
  const overlay = new GitOverlayStore(absRoot);
11233
11290
  const gitEnabled = await overlay.loadSnapshot();
11234
11291
  const graph = new DependencyGraph();
@@ -11481,21 +11538,21 @@ function buildOwnershipRouter(ctx) {
11481
11538
 
11482
11539
  // server/routes/file.ts
11483
11540
  import { Router as Router7 } from "express";
11484
- import fs29 from "fs/promises";
11485
- import path33 from "path";
11541
+ import fs30 from "fs/promises";
11542
+ import path35 from "path";
11486
11543
  function buildFileRouter(ctx) {
11487
11544
  const router = Router7();
11488
11545
  router.get("/", async (req, res) => {
11489
11546
  const rel = req.query.path;
11490
11547
  if (!rel) return res.status(400).json({ error: "missing path" });
11491
- const abs = path33.resolve(ctx.root, rel);
11492
- const rootBoundary = ctx.root.endsWith(path33.sep) ? ctx.root : ctx.root + path33.sep;
11548
+ const abs = path35.resolve(ctx.root, rel);
11549
+ const rootBoundary = ctx.root.endsWith(path35.sep) ? ctx.root : ctx.root + path35.sep;
11493
11550
  if (abs !== ctx.root && !abs.startsWith(rootBoundary)) {
11494
11551
  return res.status(403).json({ error: "forbidden" });
11495
11552
  }
11496
11553
  try {
11497
- const content = await fs29.readFile(abs, "utf-8");
11498
- const ext = path33.extname(abs).slice(1);
11554
+ const content = await fs30.readFile(abs, "utf-8");
11555
+ const ext = path35.extname(abs).slice(1);
11499
11556
  res.json({ content, lines: content.split("\n").length, ext });
11500
11557
  } catch {
11501
11558
  res.status(404).json({ error: "not found" });
@@ -11507,7 +11564,7 @@ function buildFileRouter(ctx) {
11507
11564
  // server/routes/open.ts
11508
11565
  import { Router as Router8 } from "express";
11509
11566
  import { execFile as execFile2 } from "child_process";
11510
- import path34 from "path";
11567
+ import path36 from "path";
11511
11568
  function tryOpen(bin, abs) {
11512
11569
  return new Promise((resolve) => {
11513
11570
  execFile2(bin, [abs], { timeout: 5e3 }, (err) => resolve(!err));
@@ -11518,8 +11575,8 @@ function buildOpenRouter(ctx) {
11518
11575
  router.post("/", async (req, res) => {
11519
11576
  const rel = req.body?.path;
11520
11577
  if (!rel || typeof rel !== "string") return res.status(400).json({ error: "missing path" });
11521
- const abs = path34.resolve(ctx.root, rel);
11522
- const rootBoundary = ctx.root.endsWith(path34.sep) ? ctx.root : ctx.root + path34.sep;
11578
+ const abs = path36.resolve(ctx.root, rel);
11579
+ const rootBoundary = ctx.root.endsWith(path36.sep) ? ctx.root : ctx.root + path36.sep;
11523
11580
  if (abs !== ctx.root && !abs.startsWith(rootBoundary)) {
11524
11581
  return res.status(403).json({ error: "forbidden" });
11525
11582
  }
@@ -11531,8 +11588,8 @@ function buildOpenRouter(ctx) {
11531
11588
 
11532
11589
  // server/routes/tokens.ts
11533
11590
  import { Router as Router9 } from "express";
11534
- import path35 from "path";
11535
- import fs30 from "fs";
11591
+ import path37 from "path";
11592
+ import fs31 from "fs";
11536
11593
  var CHARS_PER_TOKEN = 4;
11537
11594
  var cache = null;
11538
11595
  function buildTokensRouter(ctx) {
@@ -11547,9 +11604,9 @@ function buildTokensRouter(ctx) {
11547
11604
  let fullChars = 0;
11548
11605
  let skeletonChars = 0;
11549
11606
  for (const file of files) {
11550
- const absPath = path35.join(ctx.root, file);
11607
+ const absPath = path37.join(ctx.root, file);
11551
11608
  try {
11552
- const content = fs30.readFileSync(absPath, "utf-8");
11609
+ const content = fs31.readFileSync(absPath, "utf-8");
11553
11610
  fullChars += content.length;
11554
11611
  const skeleton = await skeletonizer.skeletonize(absPath);
11555
11612
  skeletonChars += skeleton.length;
@@ -11650,17 +11707,17 @@ function buildFileTrendsRouter(ctx) {
11650
11707
 
11651
11708
  // server/routes/projects.ts
11652
11709
  import { Router as Router12 } from "express";
11653
- import path37 from "path";
11710
+ import path39 from "path";
11654
11711
 
11655
11712
  // server/projects.ts
11656
11713
  import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
11657
11714
  import os4 from "os";
11658
- import path36 from "path";
11715
+ import path38 from "path";
11659
11716
  import crypto5 from "crypto";
11660
11717
  var HOME = os4.homedir();
11661
- var REGISTRY_PATH = path36.join(HOME, ".ctxloom", "repos.json");
11718
+ var REGISTRY_PATH = path38.join(HOME, ".ctxloom", "repos.json");
11662
11719
  function slugFor(root) {
11663
- const abs = path36.resolve(root);
11720
+ const abs = path38.resolve(root);
11664
11721
  return crypto5.createHash("sha1").update(abs).digest("hex").slice(0, 12);
11665
11722
  }
11666
11723
  function readRegistry() {
@@ -11675,28 +11732,30 @@ function readRegistry() {
11675
11732
  }
11676
11733
  }
11677
11734
  function listProjects(defaultRoot) {
11678
- const absDefault = path36.resolve(defaultRoot);
11735
+ const absDefault = path38.resolve(defaultRoot);
11679
11736
  const out = [
11680
11737
  {
11681
11738
  slug: slugFor(absDefault),
11682
- name: path36.basename(absDefault) || absDefault,
11739
+ name: path38.basename(absDefault) || absDefault,
11683
11740
  root: absDefault,
11684
11741
  isDefault: true,
11685
- hasSnapshot: existsSync2(path36.join(absDefault, ".ctxloom"))
11742
+ hasSnapshot: existsSync2(path38.join(absDefault, ".ctxloom"))
11686
11743
  }
11687
11744
  ];
11688
11745
  const seen = /* @__PURE__ */ new Set([absDefault]);
11689
11746
  for (const entry of readRegistry()) {
11690
- const abs = path36.resolve(entry.root);
11747
+ const abs = path38.resolve(entry.root);
11691
11748
  if (seen.has(abs)) continue;
11692
11749
  seen.add(abs);
11693
- out.push({
11750
+ const item = {
11694
11751
  slug: slugFor(abs),
11695
- name: entry.name ?? (path36.basename(abs) || abs),
11752
+ name: entry.name ?? (path38.basename(abs) || abs),
11696
11753
  root: abs,
11697
11754
  isDefault: false,
11698
- hasSnapshot: existsSync2(path36.join(abs, ".ctxloom"))
11699
- });
11755
+ hasSnapshot: existsSync2(path38.join(abs, ".ctxloom"))
11756
+ };
11757
+ if (entry.alias !== void 0) item.alias = entry.alias;
11758
+ out.push(item);
11700
11759
  }
11701
11760
  return out;
11702
11761
  }
@@ -11746,7 +11805,7 @@ function buildProjectsRouter(deps) {
11746
11805
  } catch (err) {
11747
11806
  const detail = err instanceof Error ? err.message : String(err);
11748
11807
  res.status(500).json({
11749
- error: `failed to switch to ${path37.basename(target.root)}: ${detail}`
11808
+ error: `failed to switch to ${path39.basename(target.root)}: ${detail}`
11750
11809
  });
11751
11810
  }
11752
11811
  });
@@ -11754,7 +11813,7 @@ function buildProjectsRouter(deps) {
11754
11813
  }
11755
11814
 
11756
11815
  // server/index.ts
11757
- var __dirname2 = path38.dirname(fileURLToPath2(import.meta.url));
11816
+ var __dirname2 = path40.dirname(fileURLToPath2(import.meta.url));
11758
11817
  async function startDashboard(options) {
11759
11818
  const { root, port, open } = options;
11760
11819
  console.log(`ctxloom dashboard \u2014 loading context from ${root}...`);
@@ -11813,9 +11872,9 @@ async function startDashboard(options) {
11813
11872
  }
11814
11873
  activeWatcher = null;
11815
11874
  }
11816
- const snapshotDir = path38.join(targetRoot, ".ctxloom");
11875
+ const snapshotDir = path40.join(targetRoot, ".ctxloom");
11817
11876
  try {
11818
- activeWatcher = fs31.watch(snapshotDir, (_event, filename) => {
11877
+ activeWatcher = fs32.watch(snapshotDir, (_event, filename) => {
11819
11878
  if (!filename || !filename.includes("snapshot")) return;
11820
11879
  if (debounce) clearTimeout(debounce);
11821
11880
  debounce = setTimeout(async () => {
@@ -11843,12 +11902,12 @@ async function startDashboard(options) {
11843
11902
  attachSnapshotWatcher(newRoot);
11844
11903
  }
11845
11904
  }));
11846
- const clientDist = path38.join(__dirname2, "../dashboard/client");
11847
- const clientDistExists = fs31.existsSync(path38.join(clientDist, "index.html"));
11905
+ const clientDist = path40.join(__dirname2, "../dashboard/client");
11906
+ const clientDistExists = fs32.existsSync(path40.join(clientDist, "index.html"));
11848
11907
  if (clientDistExists) {
11849
11908
  app.use(express.static(clientDist, { dotfiles: "allow" }));
11850
11909
  app.get(/.*/, (_req, res) => {
11851
- res.sendFile(path38.join(clientDist, "index.html"), { dotfiles: "allow" });
11910
+ res.sendFile(path40.join(clientDist, "index.html"), { dotfiles: "allow" });
11852
11911
  });
11853
11912
  } else {
11854
11913
  app.get(/^\/(?!api\/).*/, (_req, res) => {