ctxloom-pro 1.7.5 → 1.7.6

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/README.md CHANGED
@@ -69,7 +69,7 @@ The full first-run flow is **one install + one trial + one init per project.** E
69
69
  npm install -g ctxloom-pro
70
70
  ```
71
71
 
72
- > **For local trial / dev use the unpinned command above is fine.** For unattended CI usage, pin to the exact version (`ctxloom-pro@1.7.5`) so future CLI releases don't silently desync your agent-spec coverage — see the workflow example below.
72
+ > **For local trial / dev use the unpinned command above is fine.** For unattended CI usage, pin to the exact version (`ctxloom-pro@1.7.6`) so future CLI releases don't silently desync your agent-spec coverage — see the workflow example below.
73
73
 
74
74
  ### 2 — Start your free trial (once per email)
75
75
 
@@ -383,7 +383,7 @@ jobs:
383
383
  # Exact pin (not `@^1`) so future CLI releases that add/remove MCP
384
384
  # tools don't silently desync your reviewer-agent specs. Bump on
385
385
  # every release; see CHANGELOG.md for the live version table.
386
- - run: npm install -g ctxloom-pro@1.7.5
386
+ - run: npm install -g ctxloom-pro@1.7.6
387
387
  - run: ctxloom index
388
388
  - run: ctxloom rules check --json
389
389
  ```
@@ -2929,7 +2929,7 @@ var CallGraphIndex = class _CallGraphIndex {
2929
2929
  var TS_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".vue"]);
2930
2930
  var PY_EXTENSIONS = /* @__PURE__ */ new Set([".py", ".ipynb"]);
2931
2931
  var AST_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".py", ".go", ".rs", ".java", ".cs", ".rb", ".kt", ".kts", ".swift", ".ipynb", ".php", ".dart"]);
2932
- var CTXLOOM_VERSION = "1.7.5".length > 0 ? "1.7.5" : "dev";
2932
+ var CTXLOOM_VERSION = "1.7.6".length > 0 ? "1.7.6" : "dev";
2933
2933
  var SNAPSHOT_SCHEMA_VERSION = 2;
2934
2934
  function compareCtxloomVersions(snapshotVer, currentVer) {
2935
2935
  if (snapshotVer === currentVer) return "same";
@@ -3306,6 +3306,19 @@ var DependencyGraph = class {
3306
3306
  logger.warn("Graph snapshot hot-reload skipped (snapshot invalid or version-stale)");
3307
3307
  }
3308
3308
  }
3309
+ /**
3310
+ * Absolute root directory this graph was built/loaded against.
3311
+ *
3312
+ * Tools that read file CONTENTS off disk (full-text scan, refactor
3313
+ * preview/apply) must join relpaths against THIS root, not against a
3314
+ * server-level default like ctx.projectRoot — otherwise a call that
3315
+ * passes an explicit project_root different from the default reads
3316
+ * from the wrong directory and silently finds nothing. Returns '' if
3317
+ * the graph was never built (defensive; callers should have a graph).
3318
+ */
3319
+ getRootDir() {
3320
+ return this.rootDir;
3321
+ }
3309
3322
  /**
3310
3323
  * Get files that the given file directly imports.
3311
3324
  */
@@ -12290,7 +12303,7 @@ function resolveTelemetryLevel() {
12290
12303
  }
12291
12304
  var TELEMETRY_LEVEL = resolveTelemetryLevel();
12292
12305
  var TELEMETRY_DISABLED = TELEMETRY_LEVEL === "off";
12293
- var CTXLOOM_VERSION2 = "1.7.5".length > 0 ? "1.7.5" : "dev";
12306
+ var CTXLOOM_VERSION2 = "1.7.6".length > 0 ? "1.7.6" : "dev";
12294
12307
  var POSTHOG_HOST = "https://eu.i.posthog.com";
12295
12308
  var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (true ? "phc_CiDkmFLcZ2K6uCpcoSUQLmFrnnUvsyXGhSxopX5TVKE6" : "");
12296
12309
  var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528" : "");
@@ -2705,7 +2705,7 @@ var CallGraphIndex = class _CallGraphIndex {
2705
2705
  var TS_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".vue"]);
2706
2706
  var PY_EXTENSIONS = /* @__PURE__ */ new Set([".py", ".ipynb"]);
2707
2707
  var AST_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".py", ".go", ".rs", ".java", ".cs", ".rb", ".kt", ".kts", ".swift", ".ipynb", ".php", ".dart"]);
2708
- var CTXLOOM_VERSION = "1.7.5".length > 0 ? "1.7.5" : "dev";
2708
+ var CTXLOOM_VERSION = "1.7.6".length > 0 ? "1.7.6" : "dev";
2709
2709
  var SNAPSHOT_SCHEMA_VERSION = 2;
2710
2710
  function compareCtxloomVersions(snapshotVer, currentVer) {
2711
2711
  if (snapshotVer === currentVer) return "same";
@@ -3082,6 +3082,19 @@ var DependencyGraph = class {
3082
3082
  logger.warn("Graph snapshot hot-reload skipped (snapshot invalid or version-stale)");
3083
3083
  }
3084
3084
  }
3085
+ /**
3086
+ * Absolute root directory this graph was built/loaded against.
3087
+ *
3088
+ * Tools that read file CONTENTS off disk (full-text scan, refactor
3089
+ * preview/apply) must join relpaths against THIS root, not against a
3090
+ * server-level default like ctx.projectRoot — otherwise a call that
3091
+ * passes an explicit project_root different from the default reads
3092
+ * from the wrong directory and silently finds nothing. Returns '' if
3093
+ * the graph was never built (defensive; callers should have a graph).
3094
+ */
3095
+ getRootDir() {
3096
+ return this.rootDir;
3097
+ }
3085
3098
  /**
3086
3099
  * Get files that the given file directly imports.
3087
3100
  */
@@ -6371,10 +6384,11 @@ function registerContextPacketTool(registry, ctx) {
6371
6384
  const primaryContent = pathValidator.readFile(parsed.target_file);
6372
6385
  const imports = graph.getImports(parsed.target_file);
6373
6386
  const importers = graph.getImporters(parsed.target_file);
6387
+ const rootDir = graph.getRootDir() || ctx.projectRoot;
6374
6388
  const skeletons = await Promise.all(
6375
6389
  imports.map(async (dep) => {
6376
6390
  try {
6377
- const absDep = path15.resolve(ctx.projectRoot, dep);
6391
+ const absDep = path15.resolve(rootDir, dep);
6378
6392
  const sk = await skeletonizer.skeletonize(absDep);
6379
6393
  return `
6380
6394
  <!-- ${dep} -->
@@ -7025,15 +7039,16 @@ function registerBlastRadiusTool(registry, ctx) {
7025
7039
  },
7026
7040
  async (args) => {
7027
7041
  const { changed_files, depth, use_git, detail_level, project_root } = Schema8.parse(args);
7042
+ const graph = await ctx.getGraph(project_root);
7043
+ const gitRoot = graph.getRootDir() || ctx.projectRoot;
7028
7044
  let files = changed_files ?? [];
7029
7045
  if (files.length === 0 && use_git) {
7030
- files = await detectChangedFiles(ctx.projectRoot);
7046
+ files = await detectChangedFiles(gitRoot);
7031
7047
  }
7032
7048
  if (files.length === 0) {
7033
7049
  return '<blast_radius changed_files="0">\n <!-- No changed files detected -->\n</blast_radius>';
7034
7050
  }
7035
- const graph = await ctx.getGraph(project_root);
7036
- const result = await computeBlastRadius({ changedFiles: files, depth, projectRoot: ctx.projectRoot, graph });
7051
+ const result = await computeBlastRadius({ changedFiles: files, depth, projectRoot: gitRoot, graph });
7037
7052
  const report = getImpactRadius({ graph, overlay: ctx.overlay, changedFiles: files, depth });
7038
7053
  return buildBlastRadiusXml(result, depth, detail_level, report.historicalCoupling);
7039
7054
  }
@@ -7816,10 +7831,10 @@ async function getFileDiff(projectRoot, file) {
7816
7831
  return "";
7817
7832
  }
7818
7833
  }
7819
- async function trySkeletonize(ctx, filePath, projectRoot) {
7834
+ async function trySkeletonize(ctx, filePath, projectRoot, rootDir) {
7820
7835
  try {
7821
7836
  const sk = await ctx.getSkeletonizer(projectRoot);
7822
- const absPath = `${ctx.projectRoot}/${filePath}`;
7837
+ const absPath = `${rootDir}/${filePath}`;
7823
7838
  return await sk.skeletonize(absPath);
7824
7839
  } catch {
7825
7840
  return "";
@@ -7856,13 +7871,15 @@ function registerGitDiffReviewTool(registry, ctx) {
7856
7871
  async (args) => {
7857
7872
  const { changed_files, depth, use_git, include_skeletons, max_diff_lines, project_root } = Schema17.parse(args);
7858
7873
  const validator = ctx.getPathValidator(project_root);
7874
+ const graph = await ctx.getGraph(project_root);
7875
+ const gitRoot = graph.getRootDir() || ctx.projectRoot;
7859
7876
  let files = (changed_files ?? []).filter((f) => validator.isWithinRoot(f));
7860
7877
  if (files.length === 0 && use_git) {
7861
7878
  try {
7862
7879
  const { stdout } = await execFileAsync(
7863
7880
  "git",
7864
7881
  ["diff", "HEAD~1", "--name-only"],
7865
- { cwd: ctx.projectRoot, maxBuffer: 10 * 1024 * 1024 }
7882
+ { cwd: gitRoot, maxBuffer: 10 * 1024 * 1024 }
7866
7883
  );
7867
7884
  files = stdout.trim().split("\n").filter(Boolean);
7868
7885
  } catch {
@@ -7886,26 +7903,25 @@ function registerGitDiffReviewTool(registry, ctx) {
7886
7903
  <!-- No changed files detected -->
7887
7904
  </git_diff_review>`);
7888
7905
  }
7889
- const graph = await ctx.getGraph(project_root);
7890
7906
  const blast = await computeBlastRadius({
7891
7907
  changedFiles: files,
7892
7908
  depth,
7893
- projectRoot: ctx.projectRoot,
7909
+ projectRoot: gitRoot,
7894
7910
  graph
7895
7911
  });
7896
7912
  const changedFileData = await Promise.all(files.map(async (file) => {
7897
- const rawDiff = use_git ? await getFileDiff(ctx.projectRoot, file) : "";
7913
+ const rawDiff = use_git ? await getFileDiff(gitRoot, file) : "";
7898
7914
  const diffLines = rawDiff ? rawDiff.split("\n") : [];
7899
7915
  const truncated = diffLines.length > max_diff_lines;
7900
7916
  const diffContent = truncated ? [...diffLines.slice(0, max_diff_lines), `... (${diffLines.length - max_diff_lines} more lines)`].join("\n") : rawDiff;
7901
- const skeleton = include_skeletons ? await trySkeletonize(ctx, file, project_root) : "";
7917
+ const skeleton = include_skeletons ? await trySkeletonize(ctx, file, project_root, gitRoot) : "";
7902
7918
  return { file, diffLines, truncated, diffContent, skeleton };
7903
7919
  }));
7904
7920
  const skeletonLimit = 5;
7905
7921
  const directImporterSkeletons = await Promise.all(
7906
7922
  blast.directImporters.map(async (file, i) => ({
7907
7923
  file,
7908
- skeleton: include_skeletons && i < skeletonLimit ? await trySkeletonize(ctx, file, project_root) : ""
7924
+ skeleton: include_skeletons && i < skeletonLimit ? await trySkeletonize(ctx, file, project_root, gitRoot) : ""
7909
7925
  }))
7910
7926
  );
7911
7927
  const render2 = (withSkeletons, withTransitive) => {
@@ -8049,8 +8065,9 @@ function registerRefactorPreviewTool(registry, ctx) {
8049
8065
  const candidates = Array.from(candidateSet).slice(0, max_files);
8050
8066
  const fileChanges = [];
8051
8067
  let totalOccurrences = 0;
8068
+ const rootDir = graph.getRootDir() || ctx.projectRoot;
8052
8069
  for (const relPath of candidates) {
8053
- const absPath = path18.join(ctx.projectRoot, relPath);
8070
+ const absPath = path18.join(rootDir, relPath);
8054
8071
  const occurrences = scanFile(absPath, symbol, new_name);
8055
8072
  if (occurrences.length > 0) {
8056
8073
  fileChanges.push({ filePath: relPath, occurrences });
@@ -8567,8 +8584,9 @@ function registerApplyRefactorTool(registry, ctx) {
8567
8584
  const candidates = Array.from(candidateSet).slice(0, max_files);
8568
8585
  const results = [];
8569
8586
  let totalOccurrences = 0;
8587
+ const rootDir = graph.getRootDir() || ctx.projectRoot;
8570
8588
  for (const relPath of candidates) {
8571
- const absPath = path20.join(ctx.projectRoot, relPath);
8589
+ const absPath = path20.join(rootDir, relPath);
8572
8590
  const count = applyToFile(absPath, symbol, new_name, dry_run);
8573
8591
  if (count > 0) {
8574
8592
  results.push({ filePath: relPath, occurrences: count, written: !dry_run });
@@ -8659,14 +8677,15 @@ function registerDetectChangesTool(registry, ctx) {
8659
8677
  },
8660
8678
  async (args) => {
8661
8679
  const { changed_files, use_git, detail_level, project_root } = Schema22.parse(args);
8680
+ const graph = await ctx.getGraph(project_root);
8681
+ const gitRoot = graph.getRootDir() || ctx.projectRoot;
8662
8682
  let files = changed_files ?? [];
8663
8683
  if (files.length === 0 && use_git) {
8664
- files = await detectChangedFiles2(ctx.projectRoot);
8684
+ files = await detectChangedFiles2(gitRoot);
8665
8685
  }
8666
8686
  if (files.length === 0) {
8667
8687
  return '<detect_changes count="0">\n <!-- No changed files detected -->\n</detect_changes>';
8668
8688
  }
8669
- const graph = await ctx.getGraph(project_root);
8670
8689
  const { changedFiles: scored, summary } = detectChanges({
8671
8690
  graph,
8672
8691
  overlay: ctx.overlay,
@@ -8836,9 +8855,10 @@ function registerFullTextSearchTool(registry, ctx) {
8836
8855
  }
8837
8856
  const graph = await ctx.getGraph(project_root);
8838
8857
  const files = graph.allFiles();
8858
+ const rootDir = graph.getRootDir() || ctx.projectRoot;
8839
8859
  const keywordResults = [];
8840
8860
  for (const relPath of files) {
8841
- const absPath = path21.join(ctx.projectRoot, relPath);
8861
+ const absPath = path21.join(rootDir, relPath);
8842
8862
  const hit = scanFile2(absPath, pattern, context_lines);
8843
8863
  if (hit) {
8844
8864
  keywordResults.push({
@@ -8939,14 +8959,15 @@ function registerSuggestedQuestionsTool(registry, ctx) {
8939
8959
  },
8940
8960
  async (args) => {
8941
8961
  const { changed_files, use_git, project_root } = Schema24.parse(args);
8962
+ const graph = await ctx.getGraph(project_root);
8963
+ const gitRoot = graph.getRootDir() || ctx.projectRoot;
8942
8964
  let files = changed_files ?? [];
8943
8965
  if (files.length === 0 && use_git) {
8944
- files = await detectChangedFiles3(ctx.projectRoot);
8966
+ files = await detectChangedFiles3(gitRoot);
8945
8967
  }
8946
8968
  if (files.length === 0) {
8947
8969
  return '<suggested_questions count="1" changed_files="0"><question>No changed files detected. Are you on a git branch with commits?</question></suggested_questions>';
8948
8970
  }
8949
- const graph = await ctx.getGraph(project_root);
8950
8971
  const questions = [];
8951
8972
  const allImporters = /* @__PURE__ */ new Set();
8952
8973
  const hubFiles = [];
@@ -9856,14 +9877,15 @@ function registerGetAffectedFlowsTool(registry, ctx) {
9856
9877
  },
9857
9878
  async (args) => {
9858
9879
  const { changed_files, use_git, depth, max_flows, max_steps_per_flow, project_root } = Schema29.parse(args);
9880
+ const graph = await ctx.getGraph(project_root);
9881
+ const gitRoot = graph.getRootDir() || ctx.projectRoot;
9859
9882
  let files = changed_files ?? [];
9860
9883
  if (files.length === 0 && use_git) {
9861
- files = await detectChangedFiles4(ctx.projectRoot);
9884
+ files = await detectChangedFiles4(gitRoot);
9862
9885
  }
9863
9886
  if (files.length === 0) {
9864
9887
  return '<affected_flows changed_files="0" total_flows="0">\n <!-- No changed files detected -->\n</affected_flows>';
9865
9888
  }
9866
- const graph = await ctx.getGraph(project_root);
9867
9889
  const callIdx = graph.getCallGraphIndex();
9868
9890
  const changedSymbols = [];
9869
9891
  for (const file of files) {
@@ -11088,7 +11110,7 @@ var TELEMETRY_DISABLED = TELEMETRY_LEVEL === "off";
11088
11110
  function getTelemetryLevel() {
11089
11111
  return TELEMETRY_LEVEL;
11090
11112
  }
11091
- var CTXLOOM_VERSION2 = "1.7.5".length > 0 ? "1.7.5" : "dev";
11113
+ var CTXLOOM_VERSION2 = "1.7.6".length > 0 ? "1.7.6" : "dev";
11092
11114
  var POSTHOG_HOST = "https://eu.i.posthog.com";
11093
11115
  var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (true ? "phc_CiDkmFLcZ2K6uCpcoSUQLmFrnnUvsyXGhSxopX5TVKE6" : "");
11094
11116
  var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528" : "");
@@ -12774,4 +12796,4 @@ export {
12774
12796
  skillFilePath,
12775
12797
  installHarness
12776
12798
  };
12777
- //# sourceMappingURL=chunk-2YELCPTS.js.map
12799
+ //# sourceMappingURL=chunk-ISXDIRSN.js.map
package/dist/index.js CHANGED
@@ -47,7 +47,7 @@ import {
47
47
  validateDefaultRoot,
48
48
  wrapWithIndexingEnvelope,
49
49
  writeCODEOWNERS
50
- } from "./chunk-2YELCPTS.js";
50
+ } from "./chunk-ISXDIRSN.js";
51
51
  import {
52
52
  addCtxloomToConfig,
53
53
  detectInstalledClients
@@ -1068,7 +1068,7 @@ try {
1068
1068
  } catch {
1069
1069
  }
1070
1070
  var args = process.argv.slice(2);
1071
- var ctxloomVersion = "1.7.5".length > 0 ? "1.7.5" : "dev";
1071
+ var ctxloomVersion = "1.7.6".length > 0 ? "1.7.6" : "dev";
1072
1072
  if (args.includes("--version") || args.includes("-v")) {
1073
1073
  process.stdout.write(`ctxloom ${ctxloomVersion}
1074
1074
  `);
@@ -1163,7 +1163,7 @@ async function checkLicense() {
1163
1163
  if (command !== void 0 && LICENSE_GATE_BYPASS_COMMANDS.has(command)) return;
1164
1164
  const ciKey = process.env["CTXLOOM_LICENSE_KEY"];
1165
1165
  if (ciKey) {
1166
- const { ApiClient } = await import("./src-NCWA7NVW.js");
1166
+ const { ApiClient } = await import("./src-FB2SJJZR.js");
1167
1167
  const client = new ApiClient(process.env["CTXLOOM_API_BASE"]);
1168
1168
  try {
1169
1169
  const result = await client.validate(ciKey, "ci-ephemeral");
@@ -1556,7 +1556,7 @@ async function main() {
1556
1556
  }
1557
1557
  if (!skipHarness) {
1558
1558
  process.stdout.write("\n");
1559
- const { installHarness } = await import("./src-NCWA7NVW.js");
1559
+ const { installHarness } = await import("./src-FB2SJJZR.js");
1560
1560
  const h = installHarness({ cwd: initRoot, dryRun, force, extraHosts });
1561
1561
  const harnessFiles = [
1562
1562
  h.claudeMd,
@@ -1619,7 +1619,7 @@ async function main() {
1619
1619
  process.exit(1);
1620
1620
  }
1621
1621
  if (alias !== void 0) {
1622
- const { validateAlias } = await import("./src-NCWA7NVW.js");
1622
+ const { validateAlias } = await import("./src-FB2SJJZR.js");
1623
1623
  const v = validateAlias(alias);
1624
1624
  if (!v.ok) {
1625
1625
  console.error(`[ctxloom] Invalid alias: ${v.reason}`);
@@ -1978,7 +1978,7 @@ Suggested reviewers for ${files.length} file(s):`);
1978
1978
  process.stderr.write("[ctxloom] --limit must be a non-negative integer (0 for unlimited)\n");
1979
1979
  process.exit(2);
1980
1980
  }
1981
- const { loadRulesConfig, RulesChecker, formatText, formatJson, RulesConfigError } = await import("./src-NCWA7NVW.js");
1981
+ const { loadRulesConfig, RulesChecker, formatText, formatJson, RulesConfigError } = await import("./src-FB2SJJZR.js");
1982
1982
  let config;
1983
1983
  try {
1984
1984
  config = await loadRulesConfig(root);
@@ -2002,7 +2002,7 @@ Suggested reviewers for ${files.length} file(s):`);
2002
2002
  }
2003
2003
  let graph;
2004
2004
  if (useSnapshot) {
2005
- const { DependencyGraph: DG } = await import("./src-NCWA7NVW.js");
2005
+ const { DependencyGraph: DG } = await import("./src-FB2SJJZR.js");
2006
2006
  graph = new DG();
2007
2007
  const loaded = await graph.loadSnapshotOnly(root);
2008
2008
  if (!loaded) {
@@ -2011,7 +2011,7 @@ Suggested reviewers for ${files.length} file(s):`);
2011
2011
  }
2012
2012
  } else {
2013
2013
  process.stderr.write("[ctxloom] Building dependency graph...\n");
2014
- const { ASTParser: ASTParser2, DependencyGraph: DependencyGraph2 } = await import("./src-NCWA7NVW.js");
2014
+ const { ASTParser: ASTParser2, DependencyGraph: DependencyGraph2 } = await import("./src-FB2SJJZR.js");
2015
2015
  let parser;
2016
2016
  try {
2017
2017
  parser = new ASTParser2();
@@ -132,7 +132,7 @@ import {
132
132
  wrapBlock,
133
133
  wrapWithIndexingEnvelope,
134
134
  writeCODEOWNERS
135
- } from "./chunk-2YELCPTS.js";
135
+ } from "./chunk-ISXDIRSN.js";
136
136
  import {
137
137
  VectorStore
138
138
  } from "./chunk-XQEQLXY5.js";
@@ -304,4 +304,4 @@ export {
304
304
  wrapWithIndexingEnvelope,
305
305
  writeCODEOWNERS
306
306
  };
307
- //# sourceMappingURL=src-NCWA7NVW.js.map
307
+ //# sourceMappingURL=src-FB2SJJZR.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctxloom-pro",
3
- "version": "1.7.5",
3
+ "version": "1.7.6",
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",