ctxloom-pro 1.2.1 → 1.2.2

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.
@@ -11323,7 +11323,7 @@ function resolveTelemetryLevel() {
11323
11323
  }
11324
11324
  var TELEMETRY_LEVEL = resolveTelemetryLevel();
11325
11325
  var TELEMETRY_DISABLED = TELEMETRY_LEVEL === "off";
11326
- var CTXLOOM_VERSION = "1.2.1".length > 0 ? "1.2.1" : "dev";
11326
+ var CTXLOOM_VERSION = "1.2.2".length > 0 ? "1.2.2" : "dev";
11327
11327
  var POSTHOG_HOST = "https://eu.i.posthog.com";
11328
11328
  var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (true ? "phc_CiDkmFLcZ2K6uCpcoSUQLmFrnnUvsyXGhSxopX5TVKE6" : "");
11329
11329
  var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528" : "");
@@ -8855,7 +8855,7 @@ var TELEMETRY_DISABLED = TELEMETRY_LEVEL === "off";
8855
8855
  function getTelemetryLevel() {
8856
8856
  return TELEMETRY_LEVEL;
8857
8857
  }
8858
- var CTXLOOM_VERSION = "1.2.1".length > 0 ? "1.2.1" : "dev";
8858
+ var CTXLOOM_VERSION = "1.2.2".length > 0 ? "1.2.2" : "dev";
8859
8859
  var POSTHOG_HOST = "https://eu.i.posthog.com";
8860
8860
  var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (true ? "phc_CiDkmFLcZ2K6uCpcoSUQLmFrnnUvsyXGhSxopX5TVKE6" : "");
8861
8861
  var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528" : "");
@@ -9446,4 +9446,4 @@ export {
9446
9446
  FirstTouchTracker,
9447
9447
  EmittedOnceTracker
9448
9448
  };
9449
- //# sourceMappingURL=chunk-3MA5KZYI.js.map
9449
+ //# sourceMappingURL=chunk-GLBKLUJ4.js.map
package/dist/index.js CHANGED
@@ -47,7 +47,7 @@ import {
47
47
  validateDefaultRoot,
48
48
  wrapWithIndexingEnvelope,
49
49
  writeCODEOWNERS
50
- } from "./chunk-3MA5KZYI.js";
50
+ } from "./chunk-GLBKLUJ4.js";
51
51
  import {
52
52
  VectorStore
53
53
  } from "./chunk-DVI2RWJR.js";
@@ -547,6 +547,91 @@ async function startServer(opts = {}) {
547
547
  }
548
548
  }
549
549
 
550
+ // src/setup/install-pr-bot.ts
551
+ import fs2 from "fs";
552
+ import path2 from "path";
553
+ import { execFileSync } from "child_process";
554
+ var WORKFLOW_RELATIVE_PATH = ".github/workflows/ctxloom-review.yml";
555
+ function isGitRepo(cwd) {
556
+ try {
557
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
558
+ cwd,
559
+ stdio: "ignore"
560
+ });
561
+ return true;
562
+ } catch {
563
+ return false;
564
+ }
565
+ }
566
+ function detectDefaultBranch(cwd) {
567
+ try {
568
+ const out = execFileSync(
569
+ "git",
570
+ ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"],
571
+ { cwd, encoding: "utf8" }
572
+ ).trim();
573
+ if (out) return out.replace(/^origin\//, "");
574
+ } catch {
575
+ }
576
+ try {
577
+ const out = execFileSync("git", ["symbolic-ref", "--short", "HEAD"], {
578
+ cwd,
579
+ encoding: "utf8"
580
+ }).trim();
581
+ if (out) return out;
582
+ } catch {
583
+ }
584
+ return "main";
585
+ }
586
+ function renderWorkflow(ref, defaultBranch) {
587
+ return `# ctxloom PR review \u2014 risk-scored summary + inline notes on every PR.
588
+ # Runs entirely inside this repo's CI; no hosted service, no LLM calls.
589
+ # Default branch for this repo: ${defaultBranch}
590
+ # Docs: https://github.com/kodiii/ctxloom/blob/main/apps/pr-bot/README.md
591
+
592
+ name: ctxloom review
593
+
594
+ on:
595
+ pull_request:
596
+ types: [opened, synchronize, reopened]
597
+
598
+ permissions:
599
+ contents: read
600
+ pull-requests: write
601
+ checks: write
602
+
603
+ jobs:
604
+ review:
605
+ runs-on: ubuntu-latest
606
+ steps:
607
+ - uses: actions/checkout@v4
608
+ with:
609
+ fetch-depth: 0 # required: pr-bot reads git history for co-change overlay
610
+
611
+ - uses: kodiii/ctxloom/apps/pr-bot@${ref}
612
+ `;
613
+ }
614
+ function installPrBotWorkflow(opts = {}) {
615
+ const cwd = opts.cwd ?? process.cwd();
616
+ const force = opts.force ?? false;
617
+ const ref = opts.ref ?? "v1";
618
+ if (!isGitRepo(cwd)) {
619
+ return {
620
+ status: "aborted-not-git",
621
+ reason: `${cwd} is not inside a git repository. GitHub Actions only fire in repos with a remote, so this command needs one.`
622
+ };
623
+ }
624
+ const target = path2.resolve(cwd, WORKFLOW_RELATIVE_PATH);
625
+ if (fs2.existsSync(target) && !force) {
626
+ return { status: "skipped-exists", path: target };
627
+ }
628
+ const defaultBranch = detectDefaultBranch(cwd);
629
+ const contents = renderWorkflow(ref, defaultBranch);
630
+ fs2.mkdirSync(path2.dirname(target), { recursive: true });
631
+ fs2.writeFileSync(target, contents, { mode: 420 });
632
+ return { status: "installed", path: target, defaultBranch };
633
+ }
634
+
550
635
  // src/setup/setup-wizard.ts
551
636
  import { createInterface } from "readline";
552
637
  var C = {
@@ -698,8 +783,45 @@ async function runSetupWizard(options) {
698
783
  console.log(` ${C.red}${failCount} tool${failCount > 1 ? "s" : ""} failed \u2014 see errors above.${C.reset}`);
699
784
  }
700
785
  console.log("");
786
+ if (!options?.nonInteractive) {
787
+ await offerPrBotInstall();
788
+ }
701
789
  printNextSteps();
702
790
  }
791
+ async function offerPrBotInstall() {
792
+ console.log(` ${C.bold}GitHub PR review${C.reset}`);
793
+ console.log(
794
+ ` ${C.dim}Optionally drop .github/workflows/ctxloom-review.yml into${C.reset}`
795
+ );
796
+ console.log(
797
+ ` ${C.dim}this repo so every PR gets an automated risk-scored review.${C.reset}`
798
+ );
799
+ console.log("");
800
+ const answer = await ask(` Install the PR-review workflow here? [y/N]: `);
801
+ if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
802
+ console.log(` ${ICON_SKIP} Skipped. Run ${C.cyan}ctxloom install-pr-bot${C.reset} later if you change your mind.`);
803
+ console.log("");
804
+ return;
805
+ }
806
+ const result = installPrBotWorkflow();
807
+ console.log("");
808
+ switch (result.status) {
809
+ case "installed":
810
+ console.log(` ${ICON_SUCCESS} Created ${C.cyan}${result.path}${C.reset}`);
811
+ console.log(` ${C.dim}Default branch: ${result.defaultBranch}${C.reset}`);
812
+ console.log(` ${C.dim}Commit + push it, then the next PR will trigger the bot.${C.reset}`);
813
+ break;
814
+ case "skipped-exists":
815
+ console.log(` ${ICON_SKIP} A workflow already exists at ${result.path}.`);
816
+ console.log(` ${C.dim}Pass --force to ${C.cyan}ctxloom install-pr-bot${C.reset}${C.dim} to overwrite.${C.reset}`);
817
+ break;
818
+ case "aborted-not-git":
819
+ console.log(` ${ICON_FAIL} ${result.reason}`);
820
+ console.log(` ${C.dim}Run ${C.cyan}git init${C.reset}${C.dim} (and connect a remote) first, then ${C.cyan}ctxloom install-pr-bot${C.reset}${C.dim}.${C.reset}`);
821
+ break;
822
+ }
823
+ console.log("");
824
+ }
703
825
  function printNextSteps() {
704
826
  console.log(` ${C.bold}Next steps:${C.reset}`);
705
827
  console.log("");
@@ -713,16 +835,16 @@ function printNextSteps() {
713
835
  }
714
836
 
715
837
  // src/setup/init.ts
716
- import fs2 from "fs";
717
- import path2 from "path";
838
+ import fs3 from "fs";
839
+ import path3 from "path";
718
840
  function runInit(cwd = process.cwd()) {
719
- const root = path2.resolve(cwd);
720
- const stat = fs2.statSync(root);
841
+ const root = path3.resolve(cwd);
842
+ const stat = fs3.statSync(root);
721
843
  if (!stat.isDirectory()) {
722
844
  throw new Error(`ctxloom init: ${root} is not a directory`);
723
845
  }
724
846
  const warnings = [];
725
- if (!fs2.existsSync(path2.join(root, ".git"))) {
847
+ if (!fs3.existsSync(path3.join(root, ".git"))) {
726
848
  warnings.push(
727
849
  "No .git directory found here. ctxloom init still works, but most graph features (git coupling, risk overlay, churn) require git history."
728
850
  );
@@ -739,14 +861,14 @@ function buildCtxloomEntry(projectRoot) {
739
861
  };
740
862
  }
741
863
  function writeMcpJson(projectRoot) {
742
- const mcpPath = path2.join(projectRoot, ".mcp.json");
864
+ const mcpPath = path3.join(projectRoot, ".mcp.json");
743
865
  const entry = buildCtxloomEntry(projectRoot);
744
- if (!fs2.existsSync(mcpPath)) {
866
+ if (!fs3.existsSync(mcpPath)) {
745
867
  const payload = { mcpServers: { ctxloom: entry } };
746
- fs2.writeFileSync(mcpPath, JSON.stringify(payload, null, 2) + "\n", "utf-8");
868
+ fs3.writeFileSync(mcpPath, JSON.stringify(payload, null, 2) + "\n", "utf-8");
747
869
  return { path: mcpPath, created: true, merged: false, alreadyCorrect: false };
748
870
  }
749
- const raw = fs2.readFileSync(mcpPath, "utf-8");
871
+ const raw = fs3.readFileSync(mcpPath, "utf-8");
750
872
  let parsed2;
751
873
  try {
752
874
  parsed2 = JSON.parse(raw);
@@ -765,18 +887,18 @@ function writeMcpJson(projectRoot) {
765
887
  return { path: mcpPath, created: false, merged: false, alreadyCorrect: true };
766
888
  }
767
889
  servers["ctxloom"] = entry;
768
- fs2.writeFileSync(mcpPath, JSON.stringify(parsed2, null, 2) + "\n", "utf-8");
890
+ fs3.writeFileSync(mcpPath, JSON.stringify(parsed2, null, 2) + "\n", "utf-8");
769
891
  return { path: mcpPath, created: false, merged: true, alreadyCorrect: false };
770
892
  }
771
893
  var GITIGNORE_BANNER = "# ctxloom local index (machine-specific, do not commit)";
772
894
  var GITIGNORE_PATTERN = ".ctxloom/";
773
895
  function appendGitignore(projectRoot) {
774
- const gitignorePath = path2.join(projectRoot, ".gitignore");
775
- if (!fs2.existsSync(gitignorePath)) {
896
+ const gitignorePath = path3.join(projectRoot, ".gitignore");
897
+ if (!fs3.existsSync(gitignorePath)) {
776
898
  const content = `${GITIGNORE_BANNER}
777
899
  ${GITIGNORE_PATTERN}
778
900
  `;
779
- fs2.writeFileSync(gitignorePath, content, "utf-8");
901
+ fs3.writeFileSync(gitignorePath, content, "utf-8");
780
902
  return {
781
903
  path: gitignorePath,
782
904
  created: true,
@@ -784,7 +906,7 @@ ${GITIGNORE_PATTERN}
784
906
  alreadyPresent: false
785
907
  };
786
908
  }
787
- const raw = fs2.readFileSync(gitignorePath, "utf-8");
909
+ const raw = fs3.readFileSync(gitignorePath, "utf-8");
788
910
  const alreadyPresent = raw.split("\n").some((line) => {
789
911
  const trimmed = line.trim();
790
912
  if (trimmed.startsWith("#") || trimmed.startsWith("!")) return false;
@@ -803,7 +925,7 @@ ${GITIGNORE_PATTERN}
803
925
  ${GITIGNORE_BANNER}
804
926
  ${GITIGNORE_PATTERN}
805
927
  `;
806
- fs2.appendFileSync(gitignorePath, addition, "utf-8");
928
+ fs3.appendFileSync(gitignorePath, addition, "utf-8");
807
929
  return {
808
930
  path: gitignorePath,
809
931
  created: false,
@@ -883,7 +1005,7 @@ ${body}
883
1005
  import { execSync } from "child_process";
884
1006
  import * as readline from "readline";
885
1007
  import os from "os";
886
- import path3 from "path";
1008
+ import path4 from "path";
887
1009
  try {
888
1010
  const proc = process;
889
1011
  if (typeof proc.getrlimit === "function" && typeof proc.setrlimit === "function") {
@@ -894,7 +1016,7 @@ try {
894
1016
  } catch {
895
1017
  }
896
1018
  var args = process.argv.slice(2);
897
- var ctxloomVersion = "1.2.1".length > 0 ? "1.2.1" : "dev";
1019
+ var ctxloomVersion = "1.2.2".length > 0 ? "1.2.2" : "dev";
898
1020
  if (args.includes("--version") || args.includes("-v")) {
899
1021
  process.stdout.write(`ctxloom ${ctxloomVersion}
900
1022
  `);
@@ -967,7 +1089,7 @@ async function checkLicense() {
967
1089
  if (command !== void 0 && LICENSE_GATE_BYPASS_COMMANDS.has(command)) return;
968
1090
  const ciKey = process.env["CTXLOOM_LICENSE_KEY"];
969
1091
  if (ciKey) {
970
- const { ApiClient } = await import("./src-Z4U6JJBW.js");
1092
+ const { ApiClient } = await import("./src-UOVM4664.js");
971
1093
  const client = new ApiClient(process.env["CTXLOOM_API_BASE"]);
972
1094
  try {
973
1095
  const result = await client.validate(ciKey, "ci-ephemeral");
@@ -1280,6 +1402,36 @@ async function main() {
1280
1402
  await runSetupWizard();
1281
1403
  break;
1282
1404
  }
1405
+ case "install-pr-bot": {
1406
+ const force = hasFlag("--force") || hasFlag("-f");
1407
+ const ref = getFlagValue("--ref") ?? "v1";
1408
+ const result = installPrBotWorkflow({ force, ref });
1409
+ if (result.status === "aborted-not-git") {
1410
+ process.stdout.write(error(result.reason));
1411
+ process.exit(1);
1412
+ }
1413
+ if (result.status === "skipped-exists") {
1414
+ process.stdout.write(
1415
+ warn(
1416
+ `Workflow already present at ${result.path}. Pass --force to overwrite.`
1417
+ )
1418
+ );
1419
+ break;
1420
+ }
1421
+ process.stdout.write(success(`Created ${result.path}`));
1422
+ process.stdout.write(` ${style.dim(`Default branch: ${result.defaultBranch}`)}
1423
+ `);
1424
+ process.stdout.write(` ${style.dim(`Pinned to: kodiii/ctxloom/apps/pr-bot@${ref}`)}
1425
+
1426
+ `);
1427
+ process.stdout.write(
1428
+ nextStep(
1429
+ "Commit and push the workflow",
1430
+ 'git add .github/workflows/ctxloom-review.yml && git commit -m "ci: enable ctxloom pr-bot" && git push'
1431
+ )
1432
+ );
1433
+ break;
1434
+ }
1283
1435
  case "init": {
1284
1436
  process.stdout.write(header("Init"));
1285
1437
  const initRoot = process.cwd();
@@ -1327,7 +1479,7 @@ async function main() {
1327
1479
  registerArgs.splice(aliasIdx, 2);
1328
1480
  }
1329
1481
  const repoPath = registerArgs[0] ?? ".";
1330
- const absPath = path3.resolve(repoPath);
1482
+ const absPath = path4.resolve(repoPath);
1331
1483
  try {
1332
1484
  const stat = await import("fs").then((m) => m.statSync(absPath));
1333
1485
  if (!stat.isDirectory()) {
@@ -1339,15 +1491,15 @@ async function main() {
1339
1491
  process.exit(1);
1340
1492
  }
1341
1493
  if (alias !== void 0) {
1342
- const { validateAlias } = await import("./src-Z4U6JJBW.js");
1494
+ const { validateAlias } = await import("./src-UOVM4664.js");
1343
1495
  const v = validateAlias(alias);
1344
1496
  if (!v.ok) {
1345
1497
  console.error(`[ctxloom] Invalid alias: ${v.reason}`);
1346
1498
  process.exit(1);
1347
1499
  }
1348
1500
  }
1349
- const dbPath = path3.join(absPath, ".ctxloom", "vectors.lancedb");
1350
- const registryPath = path3.join(os.homedir(), ".ctxloom", "repos.json");
1501
+ const dbPath = path4.join(absPath, ".ctxloom", "vectors.lancedb");
1502
+ const registryPath = path4.join(os.homedir(), ".ctxloom", "repos.json");
1351
1503
  const reg = new RepoRegistry(registryPath);
1352
1504
  try {
1353
1505
  reg.register(absPath, dbPath, alias !== void 0 ? { alias } : {});
@@ -1367,7 +1519,7 @@ async function main() {
1367
1519
  break;
1368
1520
  }
1369
1521
  case "repos": {
1370
- const registryPath = path3.join(os.homedir(), ".ctxloom", "repos.json");
1522
+ const registryPath = path4.join(os.homedir(), ".ctxloom", "repos.json");
1371
1523
  const reg = new RepoRegistry(registryPath);
1372
1524
  const repos = reg.list();
1373
1525
  if (repos.length === 0) {
@@ -1424,7 +1576,7 @@ async function main() {
1424
1576
  }
1425
1577
  case "review-suggest": {
1426
1578
  const root = process.cwd();
1427
- const ctxloomDir = path3.join(root, ".ctxloom");
1579
+ const ctxloomDir = path4.join(root, ".ctxloom");
1428
1580
  const max = parseInt(getFlagValue("--max=") ?? "3", 10);
1429
1581
  if (isNaN(max) || max <= 0) {
1430
1582
  console.error("[ctxloom] --max must be a positive integer.");
@@ -1458,7 +1610,7 @@ async function main() {
1458
1610
  const allFiles = store.ownership.allNodes();
1459
1611
  const ruleMap = /* @__PURE__ */ new Map();
1460
1612
  for (const file of allFiles) {
1461
- const dir = path3.dirname(file);
1613
+ const dir = path4.dirname(file);
1462
1614
  const stats = store.ownership.statsFor(file);
1463
1615
  if (!stats) continue;
1464
1616
  const topOwners = stats.owners.filter((o) => o.share >= minShare).slice(0, 2);
@@ -1472,7 +1624,7 @@ async function main() {
1472
1624
  }
1473
1625
  }
1474
1626
  const rules = Array.from(ruleMap.entries()).map(([pattern, handles]) => ({ pattern, handles: Array.from(handles) })).sort((a, b) => a.pattern.localeCompare(b.pattern));
1475
- const codeownersPath = path3.join(root, ".github", "CODEOWNERS");
1627
+ const codeownersPath = path4.join(root, ".github", "CODEOWNERS");
1476
1628
  const content = await generateCODEOWNERS(codeownersPath, rules);
1477
1629
  if (writeFlag) {
1478
1630
  await writeCODEOWNERS(codeownersPath, content);
@@ -1528,7 +1680,7 @@ Suggested reviewers for ${files.length} file(s):`);
1528
1680
  }
1529
1681
  case "authors-sync": {
1530
1682
  const root = process.cwd();
1531
- const ctxloomDir = path3.join(root, ".ctxloom");
1683
+ const ctxloomDir = path4.join(root, ".ctxloom");
1532
1684
  const token = process.env.GITHUB_TOKEN;
1533
1685
  if (!token) {
1534
1686
  console.error("[ctxloom] GITHUB_TOKEN env var required for authors-sync.");
@@ -1583,7 +1735,7 @@ Suggested reviewers for ${files.length} file(s):`);
1583
1735
  process.stderr.write("[ctxloom] --limit must be a non-negative integer (0 for unlimited)\n");
1584
1736
  process.exit(2);
1585
1737
  }
1586
- const { loadRulesConfig, RulesChecker, formatText, formatJson, RulesConfigError } = await import("./src-Z4U6JJBW.js");
1738
+ const { loadRulesConfig, RulesChecker, formatText, formatJson, RulesConfigError } = await import("./src-UOVM4664.js");
1587
1739
  let config;
1588
1740
  try {
1589
1741
  config = await loadRulesConfig(root);
@@ -1607,7 +1759,7 @@ Suggested reviewers for ${files.length} file(s):`);
1607
1759
  }
1608
1760
  let graph;
1609
1761
  if (useSnapshot) {
1610
- const { DependencyGraph: DG } = await import("./src-Z4U6JJBW.js");
1762
+ const { DependencyGraph: DG } = await import("./src-UOVM4664.js");
1611
1763
  graph = new DG();
1612
1764
  const loaded = await graph.loadSnapshotOnly(root);
1613
1765
  if (!loaded) {
@@ -1616,7 +1768,7 @@ Suggested reviewers for ${files.length} file(s):`);
1616
1768
  }
1617
1769
  } else {
1618
1770
  process.stderr.write("[ctxloom] Building dependency graph...\n");
1619
- const { ASTParser: ASTParser2, DependencyGraph: DependencyGraph2 } = await import("./src-Z4U6JJBW.js");
1771
+ const { ASTParser: ASTParser2, DependencyGraph: DependencyGraph2 } = await import("./src-UOVM4664.js");
1620
1772
  let parser;
1621
1773
  try {
1622
1774
  parser = new ASTParser2();
@@ -1659,6 +1811,8 @@ Usage:
1659
1811
  ctxloom init Scaffold .mcp.json + .gitignore for this project
1660
1812
  ctxloom index Index the current directory and build dependency graph
1661
1813
  ctxloom setup Detect and configure MCP-compatible AI tools (global)
1814
+ ctxloom install-pr-bot Drop .github/workflows/ctxloom-review.yml into this repo
1815
+ (use --force to overwrite, --ref <tag> to pin a version)
1662
1816
  ctxloom grammars Show grammar cache status
1663
1817
  ctxloom grammars --download Pre-download all language grammars
1664
1818
  ctxloom register [path] Register a repo for cross-repo search (defaults to cwd)
@@ -101,7 +101,7 @@ import {
101
101
  validateDefaultRoot,
102
102
  wrapWithIndexingEnvelope,
103
103
  writeCODEOWNERS
104
- } from "./chunk-3MA5KZYI.js";
104
+ } from "./chunk-GLBKLUJ4.js";
105
105
  import {
106
106
  VectorStore
107
107
  } from "./chunk-DVI2RWJR.js";
@@ -224,4 +224,4 @@ export {
224
224
  wrapWithIndexingEnvelope,
225
225
  writeCODEOWNERS
226
226
  };
227
- //# sourceMappingURL=src-Z4U6JJBW.js.map
227
+ //# sourceMappingURL=src-UOVM4664.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctxloom-pro",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
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",