ctxloom-pro 1.0.26 → 1.0.28

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.
@@ -168,7 +168,7 @@ var init_VectorStore = __esm({
168
168
  import express from "express";
169
169
  import cors from "cors";
170
170
  import path38 from "path";
171
- import fs30 from "fs";
171
+ import fs31 from "fs";
172
172
  import { fileURLToPath as fileURLToPath2 } from "url";
173
173
 
174
174
  // server/loader.ts
@@ -8307,6 +8307,18 @@ var Schema9 = external_exports.object({
8307
8307
  var Schema10 = external_exports.object({
8308
8308
  show_files: external_exports.boolean().optional().default(false).describe(
8309
8309
  "Include member file paths in output (default: false for compact output)"
8310
+ ),
8311
+ limit: external_exports.number().int().min(1).max(200).optional().default(50).describe(
8312
+ "Maximum number of communities to return per call (default: 50, max: 200)"
8313
+ ),
8314
+ offset: external_exports.number().int().min(0).optional().default(0).describe(
8315
+ "Number of communities to skip before returning results \u2014 for paging (default: 0)"
8316
+ ),
8317
+ min_size: external_exports.number().int().min(1).optional().default(2).describe(
8318
+ "Skip communities smaller than this (default: 2 \u2014 single-file communities are usually noise)"
8319
+ ),
8320
+ detail_level: external_exports.enum(["standard", "minimal"]).default("standard").describe(
8321
+ '"standard" (default) returns paged community list. "minimal" returns counts only \u2014 useful for a quick size check before paging.'
8310
8322
  )
8311
8323
  });
8312
8324
 
@@ -8348,9 +8360,13 @@ var Schema13 = external_exports.object({
8348
8360
  });
8349
8361
 
8350
8362
  // ../../packages/core/src/tools/wiki-generate.ts
8363
+ import fs16 from "fs";
8351
8364
  var Schema14 = external_exports.object({
8352
8365
  force: external_exports.boolean().optional().default(false).describe(
8353
8366
  "Regenerate all pages even if content unchanged (default: false)"
8367
+ ),
8368
+ detail_level: external_exports.enum(["standard", "minimal"]).default("standard").describe(
8369
+ '"standard" (default) lists each written page with size. "minimal" returns counts only.'
8354
8370
  )
8355
8371
  });
8356
8372
 
@@ -8381,7 +8397,7 @@ var Schema16 = external_exports.object({
8381
8397
  });
8382
8398
 
8383
8399
  // ../../packages/core/src/tools/refactor-preview.ts
8384
- import fs16 from "fs";
8400
+ import fs17 from "fs";
8385
8401
  import path19 from "path";
8386
8402
  var Schema17 = external_exports.object({
8387
8403
  symbol: external_exports.string().min(1).describe("Symbol name to rename (exact match, case-sensitive)"),
@@ -8407,7 +8423,7 @@ var Schema18 = external_exports.object({
8407
8423
  init_embedder();
8408
8424
  init_VectorStore();
8409
8425
  init_logger();
8410
- import fs17 from "fs";
8426
+ import fs18 from "fs";
8411
8427
  import path20 from "path";
8412
8428
  var Schema19 = external_exports.object({
8413
8429
  query: external_exports.string().min(1).describe("Search query \u2014 natural language or code fragment"),
@@ -8420,7 +8436,7 @@ var Schema19 = external_exports.object({
8420
8436
  });
8421
8437
 
8422
8438
  // ../../packages/core/src/tools/apply-refactor.ts
8423
- import fs18 from "fs";
8439
+ import fs19 from "fs";
8424
8440
  import path21 from "path";
8425
8441
  var Schema20 = external_exports.object({
8426
8442
  symbol: external_exports.string().min(1).describe("Symbol name to rename (exact, case-sensitive)"),
@@ -8448,7 +8464,7 @@ var Schema21 = external_exports.object({
8448
8464
  });
8449
8465
 
8450
8466
  // ../../packages/core/src/tools/full-text-search.ts
8451
- import fs19 from "fs";
8467
+ import fs20 from "fs";
8452
8468
  import path22 from "path";
8453
8469
  var Schema22 = external_exports.object({
8454
8470
  query: external_exports.string().min(1).describe("Search term \u2014 literal or /regex/"),
@@ -8474,7 +8490,7 @@ var Schema24 = external_exports.object({
8474
8490
  });
8475
8491
 
8476
8492
  // ../../packages/core/src/tools/graph-snapshot.ts
8477
- import fs20 from "fs";
8493
+ import fs21 from "fs";
8478
8494
  import path23 from "path";
8479
8495
  var schema = external_exports.object({
8480
8496
  name: external_exports.string().min(1).max(64).regex(/^[\w.-]+$/, "Name may only contain letters, digits, dots, underscores, hyphens").describe(
@@ -8486,7 +8502,7 @@ var schema = external_exports.object({
8486
8502
  });
8487
8503
 
8488
8504
  // ../../packages/core/src/tools/graph-diff.ts
8489
- import fs21 from "fs";
8505
+ import fs22 from "fs";
8490
8506
  import path24 from "path";
8491
8507
  var schema2 = external_exports.object({
8492
8508
  baseline: external_exports.string().min(1).describe('Name of the baseline snapshot (the "before" state).'),
@@ -8520,7 +8536,7 @@ var Schema26 = external_exports.object({
8520
8536
  });
8521
8537
 
8522
8538
  // ../../packages/core/src/rules/loadConfig.ts
8523
- import fs22 from "fs/promises";
8539
+ import fs23 from "fs/promises";
8524
8540
  import path25 from "path";
8525
8541
 
8526
8542
  // ../../node_modules/js-yaml/dist/js-yaml.mjs
@@ -11146,24 +11162,24 @@ var Schema27 = external_exports.object({
11146
11162
  });
11147
11163
 
11148
11164
  // ../../packages/core/src/tools/ruleManager.ts
11149
- import fs23 from "fs";
11165
+ import fs24 from "fs";
11150
11166
  import path26 from "path";
11151
11167
 
11152
11168
  // ../../packages/core/src/review/AuthorResolver.ts
11153
- import fs24 from "fs/promises";
11169
+ import fs25 from "fs/promises";
11154
11170
  import path27 from "path";
11155
11171
 
11156
11172
  // ../../packages/core/src/review/CodeownersWriter.ts
11157
- import fs25 from "fs/promises";
11173
+ import fs26 from "fs/promises";
11158
11174
  import path28 from "path";
11159
11175
 
11160
11176
  // ../../packages/core/src/review/loadConfig.ts
11161
- import fs26 from "fs/promises";
11177
+ import fs27 from "fs/promises";
11162
11178
  import path29 from "path";
11163
11179
 
11164
11180
  // ../../packages/core/src/security/PathValidator.ts
11165
11181
  import path30 from "path";
11166
- import fs27 from "fs";
11182
+ import fs28 from "fs";
11167
11183
  var MAX_FILE_SIZE = 5 * 1024 * 1024;
11168
11184
 
11169
11185
  // ../../packages/core/src/index.ts
@@ -11463,7 +11479,7 @@ function buildOwnershipRouter(ctx) {
11463
11479
 
11464
11480
  // server/routes/file.ts
11465
11481
  import { Router as Router7 } from "express";
11466
- import fs28 from "fs/promises";
11482
+ import fs29 from "fs/promises";
11467
11483
  import path33 from "path";
11468
11484
  function buildFileRouter(ctx) {
11469
11485
  const router = Router7();
@@ -11476,7 +11492,7 @@ function buildFileRouter(ctx) {
11476
11492
  return res.status(403).json({ error: "forbidden" });
11477
11493
  }
11478
11494
  try {
11479
- const content = await fs28.readFile(abs, "utf-8");
11495
+ const content = await fs29.readFile(abs, "utf-8");
11480
11496
  const ext = path33.extname(abs).slice(1);
11481
11497
  res.json({ content, lines: content.split("\n").length, ext });
11482
11498
  } catch {
@@ -11514,7 +11530,7 @@ function buildOpenRouter(ctx) {
11514
11530
  // server/routes/tokens.ts
11515
11531
  import { Router as Router9 } from "express";
11516
11532
  import path35 from "path";
11517
- import fs29 from "fs";
11533
+ import fs30 from "fs";
11518
11534
  var CHARS_PER_TOKEN = 4;
11519
11535
  var cache = null;
11520
11536
  function buildTokensRouter(ctx) {
@@ -11531,7 +11547,7 @@ function buildTokensRouter(ctx) {
11531
11547
  for (const file of files) {
11532
11548
  const absPath = path35.join(ctx.root, file);
11533
11549
  try {
11534
- const content = fs29.readFileSync(absPath, "utf-8");
11550
+ const content = fs30.readFileSync(absPath, "utf-8");
11535
11551
  fullChars += content.length;
11536
11552
  const skeleton = await skeletonizer.skeletonize(absPath);
11537
11553
  skeletonChars += skeleton.length;
@@ -11797,7 +11813,7 @@ async function startDashboard(options) {
11797
11813
  }
11798
11814
  const snapshotDir = path38.join(targetRoot, ".ctxloom");
11799
11815
  try {
11800
- activeWatcher = fs30.watch(snapshotDir, (_event, filename) => {
11816
+ activeWatcher = fs31.watch(snapshotDir, (_event, filename) => {
11801
11817
  if (!filename || !filename.includes("snapshot")) return;
11802
11818
  if (debounce) clearTimeout(debounce);
11803
11819
  debounce = setTimeout(async () => {
@@ -11826,7 +11842,7 @@ async function startDashboard(options) {
11826
11842
  }
11827
11843
  }));
11828
11844
  const clientDist = path38.join(__dirname2, "../dashboard/client");
11829
- const clientDistExists = fs30.existsSync(path38.join(clientDist, "index.html"));
11845
+ const clientDistExists = fs31.existsSync(path38.join(clientDist, "index.html"));
11830
11846
  if (clientDistExists) {
11831
11847
  app.use(express.static(clientDist, { dotfiles: "allow" }));
11832
11848
  app.get(/.*/, (_req, res) => {
@@ -5269,6 +5269,18 @@ import { z as z10 } from "zod";
5269
5269
  var Schema10 = z10.object({
5270
5270
  show_files: z10.boolean().optional().default(false).describe(
5271
5271
  "Include member file paths in output (default: false for compact output)"
5272
+ ),
5273
+ limit: z10.number().int().min(1).max(200).optional().default(50).describe(
5274
+ "Maximum number of communities to return per call (default: 50, max: 200)"
5275
+ ),
5276
+ offset: z10.number().int().min(0).optional().default(0).describe(
5277
+ "Number of communities to skip before returning results \u2014 for paging (default: 0)"
5278
+ ),
5279
+ min_size: z10.number().int().min(1).optional().default(2).describe(
5280
+ "Skip communities smaller than this (default: 2 \u2014 single-file communities are usually noise)"
5281
+ ),
5282
+ detail_level: z10.enum(["standard", "minimal"]).default("standard").describe(
5283
+ '"standard" (default) returns paged community list. "minimal" returns counts only \u2014 useful for a quick size check before paging.'
5272
5284
  )
5273
5285
  });
5274
5286
  function escapeXML10(text) {
@@ -5279,30 +5291,53 @@ function registerCommunityListTool(registry, ctx) {
5279
5291
  "ctx_community_list",
5280
5292
  {
5281
5293
  name: "ctx_community_list",
5282
- description: "Return all architectural communities detected via Louvain clustering of the import graph. Each community is a cluster of tightly-coupled files (a feature area, module, or layer). Use this to understand high-level codebase structure before diving into details.",
5294
+ description: "Return architectural communities detected via Louvain clustering of the import graph. Each community is a cluster of tightly-coupled files (a feature area, module, or layer). Paged by default (limit=50, min_size=2). Use this to understand high-level codebase structure before diving into details; raise `limit` or page via `offset` for full coverage.",
5283
5295
  inputSchema: {
5284
5296
  type: "object",
5285
5297
  properties: {
5286
5298
  show_files: {
5287
5299
  type: "boolean",
5288
5300
  description: "Include member file paths in output (default: false)"
5301
+ },
5302
+ limit: {
5303
+ type: "number",
5304
+ description: "Maximum communities to return (default: 50, max: 200)"
5305
+ },
5306
+ offset: {
5307
+ type: "number",
5308
+ description: "Number of communities to skip for paging (default: 0)"
5309
+ },
5310
+ min_size: {
5311
+ type: "number",
5312
+ description: "Skip communities smaller than this (default: 2)"
5313
+ },
5314
+ detail_level: {
5315
+ type: "string",
5316
+ enum: ["standard", "minimal"],
5317
+ description: '"standard" returns the paged list. "minimal" returns counts only.'
5289
5318
  }
5290
5319
  }
5291
5320
  }
5292
5321
  },
5293
5322
  async (args) => {
5294
- const { show_files } = Schema10.parse(args);
5323
+ const { show_files, limit, offset, min_size, detail_level } = Schema10.parse(args);
5295
5324
  const graph = await ctx.getGraph();
5296
5325
  const files = graph.allFiles();
5297
5326
  if (files.length === 0) {
5298
5327
  return '<communities total="0" edge_count="0" />';
5299
5328
  }
5300
5329
  const detector = new CommunityDetector(graph);
5301
- const communities = detector.detect();
5330
+ const allCommunities = detector.detect();
5331
+ const filtered = allCommunities.filter((c) => c.files.length >= min_size).sort((a, b) => b.files.length - a.files.length);
5332
+ if (detail_level === "minimal") {
5333
+ return `<communities detail_level="minimal" total="${allCommunities.length}" filtered_total="${filtered.length}" edge_count="${graph.edgeCount()}" total_files="${files.length}" />`;
5334
+ }
5335
+ const page = filtered.slice(offset, offset + limit);
5336
+ const hasMore = offset + page.length < filtered.length;
5302
5337
  const lines = [
5303
- `<communities total="${communities.length}" edge_count="${graph.edgeCount()}" total_files="${files.length}">`
5338
+ `<communities total="${allCommunities.length}" filtered_total="${filtered.length}" showing="${page.length}" offset="${offset}" limit="${limit}" min_size="${min_size}" has_more="${hasMore}" edge_count="${graph.edgeCount()}" total_files="${files.length}">`
5304
5339
  ];
5305
- for (const c of communities.sort((a, b) => b.files.length - a.files.length)) {
5340
+ for (const c of page) {
5306
5341
  if (show_files) {
5307
5342
  lines.push(` <community id="${c.id}" name="${escapeXML10(c.name)}" size="${c.files.length}">`);
5308
5343
  for (const f of c.files.sort()) {
@@ -5668,43 +5703,62 @@ function registerSurprisingConnectionsTool(registry, ctx) {
5668
5703
 
5669
5704
  // packages/core/src/tools/wiki-generate.ts
5670
5705
  import { z as z14 } from "zod";
5706
+ import fs14 from "fs";
5671
5707
  var Schema14 = z14.object({
5672
5708
  force: z14.boolean().optional().default(false).describe(
5673
5709
  "Regenerate all pages even if content unchanged (default: false)"
5710
+ ),
5711
+ detail_level: z14.enum(["standard", "minimal"]).default("standard").describe(
5712
+ '"standard" (default) lists each written page with size. "minimal" returns counts only.'
5674
5713
  )
5675
5714
  });
5676
5715
  function escapeXML14(text) {
5677
5716
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
5678
5717
  }
5718
+ function safeFileSize(filePath) {
5719
+ try {
5720
+ return fs14.statSync(filePath).size;
5721
+ } catch {
5722
+ return 0;
5723
+ }
5724
+ }
5679
5725
  function registerWikiGenerateTool(registry, ctx) {
5680
5726
  registry.register(
5681
5727
  "ctx_wiki_generate",
5682
5728
  {
5683
5729
  name: "ctx_wiki_generate",
5684
- description: "Generate structural Markdown wiki pages for each Louvain community. Writes to .ctxloom/wiki/: one page per community with its files, public API, dependency map, and hub file skeleton. Pages are hash-cached \u2014 only updated when content changes. No LLM required \u2014 purely structural, always reproducible.",
5730
+ description: "Generate structural Markdown wiki pages for each Louvain community. Writes to .ctxloom/wiki/: one page per community with its files, public API, dependency map, and hub file skeleton. Pages are hash-cached \u2014 only updated when content changes. Returns per-page metadata (path, size) for written pages and a count for skipped pages; read the listed paths directly when the body is needed.",
5685
5731
  inputSchema: {
5686
5732
  type: "object",
5687
5733
  properties: {
5688
5734
  force: {
5689
5735
  type: "boolean",
5690
5736
  description: "Regenerate all pages even if content is unchanged (default: false)"
5737
+ },
5738
+ detail_level: {
5739
+ type: "string",
5740
+ enum: ["standard", "minimal"],
5741
+ description: '"standard" lists written pages with size. "minimal" returns counts only.'
5691
5742
  }
5692
5743
  }
5693
5744
  }
5694
5745
  },
5695
5746
  async (args) => {
5696
- const { force } = Schema14.parse(args);
5747
+ const { force, detail_level } = Schema14.parse(args);
5697
5748
  const [graph, skeletonizer] = await Promise.all([ctx.getGraph(), ctx.getSkeletonizer()]);
5698
5749
  const generator = new WikiGenerator(graph, ctx.projectRoot, skeletonizer);
5699
5750
  const result = await generator.generate(force);
5751
+ if (detail_level === "minimal") {
5752
+ return `<wiki_generate detail_level="minimal" wiki_dir="${escapeXML14(result.wikiDir)}" written="${result.written.length}" skipped="${result.skipped.length}" />`;
5753
+ }
5700
5754
  const lines = [
5701
5755
  `<wiki_generate wiki_dir="${escapeXML14(result.wikiDir)}" written="${result.written.length}" skipped="${result.skipped.length}">`
5702
5756
  ];
5703
5757
  for (const p of result.written) {
5704
- lines.push(` <page community="${escapeXML14(p.communityName)}" file="${escapeXML14(p.filePath)}" status="written" />`);
5705
- }
5706
- for (const p of result.skipped) {
5707
- lines.push(` <page community="${escapeXML14(p.communityName)}" file="${escapeXML14(p.filePath)}" status="skipped" />`);
5758
+ const size = safeFileSize(p.filePath);
5759
+ lines.push(
5760
+ ` <page community="${escapeXML14(p.communityName)}" file="${escapeXML14(p.filePath)}" size="${size}" status="written" />`
5761
+ );
5708
5762
  }
5709
5763
  lines.push("</wiki_generate>");
5710
5764
  return lines.join("\n");
@@ -5907,7 +5961,7 @@ function registerGitDiffReviewTool(registry, ctx) {
5907
5961
 
5908
5962
  // packages/core/src/tools/refactor-preview.ts
5909
5963
  import { z as z17 } from "zod";
5910
- import fs14 from "fs";
5964
+ import fs15 from "fs";
5911
5965
  import path17 from "path";
5912
5966
  var Schema17 = z17.object({
5913
5967
  symbol: z17.string().min(1).describe("Symbol name to rename (exact match, case-sensitive)"),
@@ -5922,7 +5976,7 @@ function escapeXML17(text) {
5922
5976
  function scanFile(filePath, symbol, newName) {
5923
5977
  let content;
5924
5978
  try {
5925
- content = fs14.readFileSync(filePath, "utf-8");
5979
+ content = fs15.readFileSync(filePath, "utf-8");
5926
5980
  } catch {
5927
5981
  return [];
5928
5982
  }
@@ -6152,7 +6206,7 @@ function registerExecutionFlowTool(registry, ctx) {
6152
6206
 
6153
6207
  // packages/core/src/tools/cross-repo-search.ts
6154
6208
  import { z as z19 } from "zod";
6155
- import fs15 from "fs";
6209
+ import fs16 from "fs";
6156
6210
  import path18 from "path";
6157
6211
  var RepoRegistry = class {
6158
6212
  filePath;
@@ -6163,16 +6217,16 @@ var RepoRegistry = class {
6163
6217
  }
6164
6218
  load() {
6165
6219
  try {
6166
- if (!fs15.existsSync(this.filePath)) return [];
6167
- return JSON.parse(fs15.readFileSync(this.filePath, "utf-8"));
6220
+ if (!fs16.existsSync(this.filePath)) return [];
6221
+ return JSON.parse(fs16.readFileSync(this.filePath, "utf-8"));
6168
6222
  } catch {
6169
6223
  return [];
6170
6224
  }
6171
6225
  }
6172
6226
  save() {
6173
6227
  const dir = path18.dirname(this.filePath);
6174
- if (!fs15.existsSync(dir)) fs15.mkdirSync(dir, { recursive: true });
6175
- fs15.writeFileSync(this.filePath, JSON.stringify(this.repos, null, 2), "utf-8");
6228
+ if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
6229
+ fs16.writeFileSync(this.filePath, JSON.stringify(this.repos, null, 2), "utf-8");
6176
6230
  }
6177
6231
  list() {
6178
6232
  return [...this.repos];
@@ -6313,7 +6367,7 @@ function registerCrossRepoSearchTool(registry, _ctx, registryFilePath) {
6313
6367
 
6314
6368
  // packages/core/src/tools/apply-refactor.ts
6315
6369
  import { z as z20 } from "zod";
6316
- import fs16 from "fs";
6370
+ import fs17 from "fs";
6317
6371
  import path19 from "path";
6318
6372
  var Schema20 = z20.object({
6319
6373
  symbol: z20.string().min(1).describe("Symbol name to rename (exact, case-sensitive)"),
@@ -6331,7 +6385,7 @@ function escapeXML20(text) {
6331
6385
  function applyToFile(absPath, symbol, newName, dryRun) {
6332
6386
  let content;
6333
6387
  try {
6334
- content = fs16.readFileSync(absPath, "utf-8");
6388
+ content = fs17.readFileSync(absPath, "utf-8");
6335
6389
  } catch {
6336
6390
  return 0;
6337
6391
  }
@@ -6340,7 +6394,7 @@ function applyToFile(absPath, symbol, newName, dryRun) {
6340
6394
  const occurrences = (content.match(regex) ?? []).length;
6341
6395
  if (occurrences === 0) return 0;
6342
6396
  if (!dryRun) {
6343
- fs16.writeFileSync(absPath, content.replace(regex, newName), "utf-8");
6397
+ fs17.writeFileSync(absPath, content.replace(regex, newName), "utf-8");
6344
6398
  }
6345
6399
  return occurrences;
6346
6400
  }
@@ -6513,7 +6567,7 @@ function registerDetectChangesTool(registry, ctx) {
6513
6567
 
6514
6568
  // packages/core/src/tools/full-text-search.ts
6515
6569
  import { z as z22 } from "zod";
6516
- import fs17 from "fs";
6570
+ import fs18 from "fs";
6517
6571
  import path20 from "path";
6518
6572
  var Schema22 = z22.object({
6519
6573
  query: z22.string().min(1).describe("Search term \u2014 literal or /regex/"),
@@ -6539,7 +6593,7 @@ function buildPattern(query, caseSensitive) {
6539
6593
  function scanFile2(absPath, pattern, contextLines) {
6540
6594
  let content;
6541
6595
  try {
6542
- content = fs17.readFileSync(absPath, "utf-8");
6596
+ content = fs18.readFileSync(absPath, "utf-8");
6543
6597
  } catch {
6544
6598
  return null;
6545
6599
  }
@@ -6825,7 +6879,7 @@ function registerGetWorkflowTool(registry, _ctx) {
6825
6879
  }
6826
6880
 
6827
6881
  // packages/core/src/tools/graph-snapshot.ts
6828
- import fs18 from "fs";
6882
+ import fs19 from "fs";
6829
6883
  import path21 from "path";
6830
6884
  import { z as z25 } from "zod";
6831
6885
  var schema = z25.object({
@@ -6838,12 +6892,12 @@ var schema = z25.object({
6838
6892
  });
6839
6893
  function saveNamedSnapshot(graph, name, rootDir, overwrite = false) {
6840
6894
  const snapshotsDir = path21.resolve(rootDir, ".ctxloom", "snapshots");
6841
- fs18.mkdirSync(snapshotsDir, { recursive: true });
6895
+ fs19.mkdirSync(snapshotsDir, { recursive: true });
6842
6896
  const snapshotPath = path21.resolve(snapshotsDir, `${name}.json`);
6843
6897
  if (!snapshotPath.startsWith(snapshotsDir + path21.sep)) {
6844
6898
  throw new Error(`Invalid snapshot name: "${name}"`);
6845
6899
  }
6846
- if (fs18.existsSync(snapshotPath) && !overwrite) {
6900
+ if (fs19.existsSync(snapshotPath) && !overwrite) {
6847
6901
  throw new Error(`Snapshot "${name}" already exists. Pass overwrite: true to replace it.`);
6848
6902
  }
6849
6903
  const files = graph.allFiles();
@@ -6859,13 +6913,13 @@ function saveNamedSnapshot(graph, name, rootDir, overwrite = false) {
6859
6913
  forwardEdges
6860
6914
  };
6861
6915
  const tmp = snapshotPath + ".tmp";
6862
- fs18.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
6863
- fs18.renameSync(tmp, snapshotPath);
6916
+ fs19.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
6917
+ fs19.renameSync(tmp, snapshotPath);
6864
6918
  }
6865
6919
  function listNamedSnapshots(rootDir) {
6866
6920
  const snapshotsDir = path21.join(rootDir, ".ctxloom", "snapshots");
6867
- if (!fs18.existsSync(snapshotsDir)) return [];
6868
- return fs18.readdirSync(snapshotsDir).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5)).sort();
6921
+ if (!fs19.existsSync(snapshotsDir)) return [];
6922
+ return fs19.readdirSync(snapshotsDir).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5)).sort();
6869
6923
  }
6870
6924
  function registerGraphSnapshotTool(registry, ctx) {
6871
6925
  registry.register(
@@ -6913,7 +6967,7 @@ function registerGraphSnapshotTool(registry, ctx) {
6913
6967
  }
6914
6968
 
6915
6969
  // packages/core/src/tools/graph-diff.ts
6916
- import fs19 from "fs";
6970
+ import fs20 from "fs";
6917
6971
  import path22 from "path";
6918
6972
  import { z as z26 } from "zod";
6919
6973
  var schema2 = z26.object({
@@ -6926,11 +6980,11 @@ function loadSnapshot(name, rootDir) {
6926
6980
  if (!snapshotPath.startsWith(snapshotsDir + path22.sep)) {
6927
6981
  throw new Error(`Invalid snapshot name: "${name}"`);
6928
6982
  }
6929
- if (!fs19.existsSync(snapshotPath)) {
6983
+ if (!fs20.existsSync(snapshotPath)) {
6930
6984
  throw new Error(`Snapshot "${name}" not found. Run ctx_graph_snapshot first.`);
6931
6985
  }
6932
6986
  try {
6933
- return JSON.parse(fs19.readFileSync(snapshotPath, "utf-8"));
6987
+ return JSON.parse(fs20.readFileSync(snapshotPath, "utf-8"));
6934
6988
  } catch (e) {
6935
6989
  throw new Error(`Snapshot "${name}" is corrupted: ${e instanceof Error ? e.message : String(e)}`);
6936
6990
  }
@@ -7295,7 +7349,7 @@ var RulesConfigError = class extends Error {
7295
7349
  };
7296
7350
 
7297
7351
  // packages/core/src/rules/loadConfig.ts
7298
- import fs20 from "fs/promises";
7352
+ import fs21 from "fs/promises";
7299
7353
  import path23 from "path";
7300
7354
  import yaml from "js-yaml";
7301
7355
  import { z as z30 } from "zod";
@@ -7314,7 +7368,7 @@ async function loadRulesConfig(rootDir) {
7314
7368
  const filePath = path23.join(rootDir, ".ctxloom", "rules.yml");
7315
7369
  let raw;
7316
7370
  try {
7317
- raw = await fs20.readFile(filePath, "utf-8");
7371
+ raw = await fs21.readFile(filePath, "utf-8");
7318
7372
  } catch (err) {
7319
7373
  if (err.code === "ENOENT") return null;
7320
7374
  throw new RulesConfigError(`Failed to read rules config: ${String(err)}`);
@@ -7679,7 +7733,7 @@ function createToolRegistry(ctx) {
7679
7733
  }
7680
7734
 
7681
7735
  // packages/core/src/tools/ruleManager.ts
7682
- import fs21 from "fs";
7736
+ import fs22 from "fs";
7683
7737
  import path24 from "path";
7684
7738
  var RULE_FILES = [
7685
7739
  ".cursorrules",
@@ -7707,17 +7761,17 @@ var RuleManager = class {
7707
7761
  const fullPath = path24.join(this.projectRoot, ruleFile);
7708
7762
  try {
7709
7763
  this.pathValidator.validate(fullPath);
7710
- if (fs21.existsSync(fullPath)) {
7711
- const stat = fs21.statSync(fullPath);
7764
+ if (fs22.existsSync(fullPath)) {
7765
+ const stat = fs22.statSync(fullPath);
7712
7766
  if (stat.isFile()) {
7713
- const content = fs21.readFileSync(fullPath, "utf-8");
7767
+ const content = fs22.readFileSync(fullPath, "utf-8");
7714
7768
  rules.push({
7715
7769
  name: ruleFile,
7716
7770
  path: ruleFile,
7717
7771
  content
7718
7772
  });
7719
7773
  } else if (stat.isDirectory()) {
7720
- const dirEntries = fs21.readdirSync(fullPath);
7774
+ const dirEntries = fs22.readdirSync(fullPath);
7721
7775
  for (const entry of dirEntries) {
7722
7776
  const entryPath = path24.join(fullPath, entry);
7723
7777
  try {
@@ -7725,9 +7779,9 @@ var RuleManager = class {
7725
7779
  } catch {
7726
7780
  continue;
7727
7781
  }
7728
- const entryStat = fs21.statSync(entryPath);
7782
+ const entryStat = fs22.statSync(entryPath);
7729
7783
  if (entryStat.isFile()) {
7730
- const content = fs21.readFileSync(entryPath, "utf-8");
7784
+ const content = fs22.readFileSync(entryPath, "utf-8");
7731
7785
  rules.push({
7732
7786
  name: `${ruleFile}/${entry}`,
7733
7787
  path: `${ruleFile}/${entry}`,
@@ -7770,7 +7824,7 @@ var RuleManager = class {
7770
7824
  };
7771
7825
 
7772
7826
  // packages/core/src/review/AuthorResolver.ts
7773
- import fs22 from "fs/promises";
7827
+ import fs23 from "fs/promises";
7774
7828
  import path25 from "path";
7775
7829
  import yaml2 from "js-yaml";
7776
7830
  var AuthorResolver = class {
@@ -7796,7 +7850,7 @@ var AuthorResolver = class {
7796
7850
  /** Write a new mapping to the cache file. */
7797
7851
  async writeCache(email, handle) {
7798
7852
  this.cache = { ...this.cache, [email]: handle };
7799
- await fs22.writeFile(
7853
+ await fs23.writeFile(
7800
7854
  path25.join(this.ctxloomDir, "authors-cache.json"),
7801
7855
  JSON.stringify(this.cache, null, 2)
7802
7856
  );
@@ -7808,7 +7862,7 @@ var AuthorResolver = class {
7808
7862
  async loadYml() {
7809
7863
  const file = path25.join(this.ctxloomDir, "authors.yml");
7810
7864
  try {
7811
- const raw = await fs22.readFile(file, "utf8");
7865
+ const raw = await fs23.readFile(file, "utf8");
7812
7866
  const parsed = yaml2.load(raw);
7813
7867
  if (!parsed) return;
7814
7868
  this.mappings = parsed.mappings ?? {};
@@ -7819,7 +7873,7 @@ var AuthorResolver = class {
7819
7873
  async loadCache() {
7820
7874
  const file = path25.join(this.ctxloomDir, "authors-cache.json");
7821
7875
  try {
7822
- const raw = await fs22.readFile(file, "utf8");
7876
+ const raw = await fs23.readFile(file, "utf8");
7823
7877
  this.cache = JSON.parse(raw);
7824
7878
  } catch {
7825
7879
  }
@@ -7844,7 +7898,7 @@ async function resolveViaGitHubApi(email, owner, repo, token) {
7844
7898
  }
7845
7899
 
7846
7900
  // packages/core/src/review/CodeownersWriter.ts
7847
- import fs23 from "fs/promises";
7901
+ import fs24 from "fs/promises";
7848
7902
  import path26 from "path";
7849
7903
  var MARKER_START = "# <ctxloom:start> \u2014 managed by ctxloom review-suggest; do not edit between markers";
7850
7904
  var MARKER_START_DETECT = "# <ctxloom:start>";
@@ -7877,15 +7931,15 @@ ${block}
7877
7931
  async function generateCODEOWNERS(codeownersPath, rules) {
7878
7932
  let existing = "";
7879
7933
  try {
7880
- existing = await fs23.readFile(codeownersPath, "utf8");
7934
+ existing = await fs24.readFile(codeownersPath, "utf8");
7881
7935
  } catch {
7882
7936
  }
7883
7937
  const block = buildCodeownersBlock(rules);
7884
7938
  return mergeIntoFile(existing, block);
7885
7939
  }
7886
7940
  async function writeCODEOWNERS(codeownersPath, content) {
7887
- await fs23.mkdir(path26.dirname(codeownersPath), { recursive: true });
7888
- await fs23.writeFile(codeownersPath, content, "utf8");
7941
+ await fs24.mkdir(path26.dirname(codeownersPath), { recursive: true });
7942
+ await fs24.writeFile(codeownersPath, content, "utf8");
7889
7943
  }
7890
7944
 
7891
7945
  // packages/core/src/review/types.ts
@@ -8070,7 +8124,7 @@ function matchGlob(pattern, value) {
8070
8124
  }
8071
8125
 
8072
8126
  // packages/core/src/review/loadConfig.ts
8073
- import fs24 from "fs/promises";
8127
+ import fs25 from "fs/promises";
8074
8128
  import path27 from "path";
8075
8129
  import yaml3 from "js-yaml";
8076
8130
  function freshDefaults() {
@@ -8084,7 +8138,7 @@ function freshDefaults() {
8084
8138
  async function loadReviewConfig(root) {
8085
8139
  const file = path27.join(root, ".ctxloom", "review.yml");
8086
8140
  try {
8087
- const raw = await fs24.readFile(file, "utf8");
8141
+ const raw = await fs25.readFile(file, "utf8");
8088
8142
  const parsed = yaml3.load(raw);
8089
8143
  if (!parsed) return freshDefaults();
8090
8144
  return {
@@ -8100,12 +8154,12 @@ async function loadReviewConfig(root) {
8100
8154
 
8101
8155
  // packages/core/src/security/PathValidator.ts
8102
8156
  import path28 from "path";
8103
- import fs25 from "fs";
8157
+ import fs26 from "fs";
8104
8158
  var MAX_FILE_SIZE = 5 * 1024 * 1024;
8105
8159
  var PathValidator = class {
8106
8160
  canonicalRoot;
8107
8161
  constructor(projectRoot) {
8108
- this.canonicalRoot = fs25.realpathSync(path28.resolve(projectRoot));
8162
+ this.canonicalRoot = fs26.realpathSync(path28.resolve(projectRoot));
8109
8163
  }
8110
8164
  /**
8111
8165
  * Validates that the given input path resolves within the project root.
@@ -8118,7 +8172,7 @@ var PathValidator = class {
8118
8172
  const resolved = path28.resolve(this.canonicalRoot, inputPath);
8119
8173
  let canonical;
8120
8174
  try {
8121
- canonical = fs25.realpathSync(resolved);
8175
+ canonical = fs26.realpathSync(resolved);
8122
8176
  } catch {
8123
8177
  canonical = resolved;
8124
8178
  }
@@ -8147,11 +8201,11 @@ var PathValidator = class {
8147
8201
  */
8148
8202
  readFile(inputPath) {
8149
8203
  const absPath = this.validate(inputPath);
8150
- const stat = fs25.statSync(absPath);
8204
+ const stat = fs26.statSync(absPath);
8151
8205
  if (stat.size > MAX_FILE_SIZE) {
8152
8206
  throw new Error(`File too large: ${inputPath} (${Math.round(stat.size / 1024)}KB, max ${MAX_FILE_SIZE / 1024 / 1024}MB)`);
8153
8207
  }
8154
- return fs25.readFileSync(absPath, "utf-8");
8208
+ return fs26.readFileSync(absPath, "utf-8");
8155
8209
  }
8156
8210
  /**
8157
8211
  * Checks if a path exists and is within the project root.
@@ -8741,4 +8795,4 @@ export {
8741
8795
  track,
8742
8796
  captureError
8743
8797
  };
8744
- //# sourceMappingURL=chunk-GI354NTO.js.map
8798
+ //# sourceMappingURL=chunk-3AF7Z7DD.js.map
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  startTrial,
35
35
  track,
36
36
  writeCODEOWNERS
37
- } from "./chunk-GI354NTO.js";
37
+ } from "./chunk-3AF7Z7DD.js";
38
38
  import {
39
39
  VectorStore
40
40
  } from "./chunk-NEHYSE2Y.js";
@@ -125,7 +125,10 @@ function buildContext() {
125
125
  if (!_ruleManager) _ruleManager = new RuleManager(PROJECT_ROOT, ctx.getPathValidator());
126
126
  return _ruleManager;
127
127
  },
128
- isStoreInitialized: () => _storePromise !== null,
128
+ isStoreInitialized: () => {
129
+ if (_storePromise !== null) return true;
130
+ return fs.existsSync(path.join(DB_PATH, "code_embeddings.lance"));
131
+ },
129
132
  isGraphInitialized: () => _graphPromise !== null,
130
133
  isParserInitialized: () => _parserPromise !== null
131
134
  };
@@ -613,7 +616,7 @@ try {
613
616
  } catch {
614
617
  }
615
618
  var args = process.argv.slice(2);
616
- var ctxloomVersion = "1.0.26".length > 0 ? "1.0.26" : "dev";
619
+ var ctxloomVersion = "1.0.28".length > 0 ? "1.0.28" : "dev";
617
620
  if (args.includes("--version") || args.includes("-v")) {
618
621
  process.stdout.write(`ctxloom ${ctxloomVersion}
619
622
  `);
@@ -686,7 +689,7 @@ async function checkLicense() {
686
689
  if (command !== void 0 && LICENSE_GATE_BYPASS_COMMANDS.has(command)) return;
687
690
  const ciKey = process.env["CTXLOOM_LICENSE_KEY"];
688
691
  if (ciKey) {
689
- const { ApiClient } = await import("./src-BRXQZ22F.js");
692
+ const { ApiClient } = await import("./src-DZ5Z7KVU.js");
690
693
  const client = new ApiClient(process.env["CTXLOOM_API_BASE"]);
691
694
  try {
692
695
  const result = await client.validate(ciKey, "ci-ephemeral");
@@ -924,11 +927,12 @@ async function main() {
924
927
  `);
925
928
  const indexStart = Date.now();
926
929
  const result = await indexDirectory(root, (file, i, total) => {
930
+ if (!isTTY) return;
927
931
  const trimmed = file.length > 60 ? "\u2026" + file.slice(-59) : file;
928
932
  process.stdout.write(`\r ${style.dim(`[${i}/${total}]`)} ${style.dim(trimmed)}\x1B[K`);
929
933
  });
930
934
  const indexMs = Date.now() - indexStart;
931
- process.stdout.write("\r\x1B[K");
935
+ if (isTTY) process.stdout.write("\r\x1B[K");
932
936
  const errLabel = result.errors === 0 ? style.dim("0 errors") : style.warn(`${result.errors} error${result.errors === 1 ? "" : "s"}`);
933
937
  process.stdout.write(` ${success(`Indexed ${style.bold(String(result.indexed))} files`)} ${style.dim("\xB7")} ${errLabel} ${style.dim(`\xB7 ${(indexMs / 1e3).toFixed(1)}s`)}
934
938
 
@@ -1247,7 +1251,7 @@ Suggested reviewers for ${files.length} file(s):`);
1247
1251
  process.stderr.write("[ctxloom] --limit must be a non-negative integer (0 for unlimited)\n");
1248
1252
  process.exit(2);
1249
1253
  }
1250
- const { loadRulesConfig, RulesChecker, formatText, formatJson, RulesConfigError } = await import("./src-BRXQZ22F.js");
1254
+ const { loadRulesConfig, RulesChecker, formatText, formatJson, RulesConfigError } = await import("./src-DZ5Z7KVU.js");
1251
1255
  let config;
1252
1256
  try {
1253
1257
  config = await loadRulesConfig(root);
@@ -1271,7 +1275,7 @@ Suggested reviewers for ${files.length} file(s):`);
1271
1275
  }
1272
1276
  let graph;
1273
1277
  if (useSnapshot) {
1274
- const { DependencyGraph: DG } = await import("./src-BRXQZ22F.js");
1278
+ const { DependencyGraph: DG } = await import("./src-DZ5Z7KVU.js");
1275
1279
  graph = new DG();
1276
1280
  const loaded = await graph.loadSnapshotOnly(root);
1277
1281
  if (!loaded) {
@@ -1280,7 +1284,7 @@ Suggested reviewers for ${files.length} file(s):`);
1280
1284
  }
1281
1285
  } else {
1282
1286
  process.stderr.write("[ctxloom] Building dependency graph...\n");
1283
- const { ASTParser: ASTParser2, DependencyGraph: DependencyGraph2 } = await import("./src-BRXQZ22F.js");
1287
+ const { ASTParser: ASTParser2, DependencyGraph: DependencyGraph2 } = await import("./src-DZ5Z7KVU.js");
1284
1288
  let parser;
1285
1289
  try {
1286
1290
  parser = new ASTParser2();
@@ -80,7 +80,7 @@ import {
80
80
  startTrial,
81
81
  track,
82
82
  writeCODEOWNERS
83
- } from "./chunk-GI354NTO.js";
83
+ } from "./chunk-3AF7Z7DD.js";
84
84
  import {
85
85
  VectorStore
86
86
  } from "./chunk-NEHYSE2Y.js";
@@ -182,4 +182,4 @@ export {
182
182
  track,
183
183
  writeCODEOWNERS
184
184
  };
185
- //# sourceMappingURL=src-BRXQZ22F.js.map
185
+ //# sourceMappingURL=src-DZ5Z7KVU.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctxloom-pro",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "description": "ctxloom — The Universal Code Context Engine. A local-first MCP server providing intelligent code context via hybrid Vector + AST + Graph search with Skeletonization (92% token reduction).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",